Merge remote-tracking branch 'upstream/develop' into block-behavior
This commit is contained in:
commit
1438fd9583
643 changed files with 15241 additions and 7611 deletions
|
|
@ -12,17 +12,19 @@ defmodule Mix.Pleroma do
|
|||
:cachex,
|
||||
:flake_id,
|
||||
:swoosh,
|
||||
:timex
|
||||
:timex,
|
||||
:fast_html
|
||||
]
|
||||
@cachex_children ["object", "user", "scrubber"]
|
||||
@cachex_children ["object", "user", "scrubber", "web_resp"]
|
||||
@doc "Common functions to be reused in mix tasks"
|
||||
def start_pleroma do
|
||||
Pleroma.Config.Holder.save_default()
|
||||
Pleroma.Config.Oban.warn()
|
||||
Pleroma.Application.limiters_setup()
|
||||
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
|
||||
|
||||
if Pleroma.Config.get(:env) != :test do
|
||||
Application.put_env(:logger, :console, level: :debug)
|
||||
unless System.get_env("DEBUG") do
|
||||
Logger.remove_backend(:console)
|
||||
end
|
||||
|
||||
adapter = Application.get_env(:tesla, :adapter)
|
||||
|
|
@ -36,12 +38,23 @@ defmodule Mix.Pleroma do
|
|||
|
||||
Enum.each(apps, &Application.ensure_all_started/1)
|
||||
|
||||
oban_config = [
|
||||
crontab: [],
|
||||
repo: Pleroma.Repo,
|
||||
log: false,
|
||||
queues: [],
|
||||
plugins: []
|
||||
]
|
||||
|
||||
children =
|
||||
[
|
||||
Pleroma.Repo,
|
||||
Pleroma.Emoji,
|
||||
{Pleroma.Config.TransferTask, false},
|
||||
Pleroma.Web.Endpoint,
|
||||
{Oban, Pleroma.Config.get(Oban)}
|
||||
{Oban, oban_config},
|
||||
{Majic.Pool,
|
||||
[name: Pleroma.MajicPool, pool_size: Pleroma.Config.get([:majic_pool, :size], 2)]}
|
||||
] ++
|
||||
http_children(adapter)
|
||||
|
||||
|
|
@ -97,12 +110,6 @@ defmodule Mix.Pleroma do
|
|||
end
|
||||
end
|
||||
|
||||
def shell_yes?(message) do
|
||||
if mix_shell?(),
|
||||
do: Mix.shell().yes?("Continue?"),
|
||||
else: shell_prompt(message, "Continue?") in ~w(Yn Y y)
|
||||
end
|
||||
|
||||
def shell_info(message) do
|
||||
if mix_shell?(),
|
||||
do: Mix.shell().info(message),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
defmodule Mix.Tasks.Pleroma.Config do
|
||||
use Mix.Task
|
||||
|
||||
import Ecto.Query
|
||||
import Mix.Pleroma
|
||||
|
||||
alias Pleroma.ConfigDB
|
||||
|
|
@ -14,26 +15,199 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
@moduledoc File.read!("docs/administration/CLI_tasks/config.md")
|
||||
|
||||
def run(["migrate_to_db"]) do
|
||||
start_pleroma()
|
||||
migrate_to_db()
|
||||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
migrate_to_db()
|
||||
end)
|
||||
end
|
||||
|
||||
def run(["migrate_from_db" | options]) do
|
||||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
|
||||
{opts, _} =
|
||||
OptionParser.parse!(options,
|
||||
strict: [env: :string, delete: :boolean],
|
||||
aliases: [d: :delete]
|
||||
)
|
||||
|
||||
migrate_from_db(opts)
|
||||
end)
|
||||
end
|
||||
|
||||
def run(["dump"]) do
|
||||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
|
||||
header = config_header()
|
||||
|
||||
settings =
|
||||
ConfigDB
|
||||
|> Repo.all()
|
||||
|> Enum.sort()
|
||||
|
||||
unless settings == [] do
|
||||
shell_info("#{header}")
|
||||
|
||||
Enum.each(settings, &dump(&1))
|
||||
else
|
||||
shell_error("No settings in ConfigDB.")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def run(["dump", group, key]) do
|
||||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
|
||||
group = maybe_atomize(group)
|
||||
key = maybe_atomize(key)
|
||||
|
||||
group
|
||||
|> ConfigDB.get_by_group_and_key(key)
|
||||
|> dump()
|
||||
end)
|
||||
end
|
||||
|
||||
def run(["dump", group]) do
|
||||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
|
||||
group = maybe_atomize(group)
|
||||
|
||||
dump_group(group)
|
||||
end)
|
||||
end
|
||||
|
||||
def run(["groups"]) do
|
||||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
|
||||
groups =
|
||||
ConfigDB
|
||||
|> distinct([c], true)
|
||||
|> select([c], c.group)
|
||||
|> Repo.all()
|
||||
|
||||
if length(groups) > 0 do
|
||||
shell_info("The following configuration groups are set in ConfigDB:\r\n")
|
||||
groups |> Enum.each(fn x -> shell_info("- #{x}") end)
|
||||
shell_info("\r\n")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def run(["reset", "--force"]) do
|
||||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
truncatedb()
|
||||
shell_info("The ConfigDB settings have been removed from the database.")
|
||||
end)
|
||||
end
|
||||
|
||||
def run(["reset"]) do
|
||||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
|
||||
shell_info("The following settings will be permanently removed:")
|
||||
|
||||
ConfigDB
|
||||
|> Repo.all()
|
||||
|> Enum.sort()
|
||||
|> Enum.each(&dump(&1))
|
||||
|
||||
shell_error("\nTHIS CANNOT BE UNDONE!")
|
||||
|
||||
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
|
||||
truncatedb()
|
||||
|
||||
shell_info("The ConfigDB settings have been removed from the database.")
|
||||
else
|
||||
shell_error("No changes made.")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def run(["delete", "--force", group, key]) do
|
||||
start_pleroma()
|
||||
|
||||
{opts, _} =
|
||||
OptionParser.parse!(options,
|
||||
strict: [env: :string, delete: :boolean],
|
||||
aliases: [d: :delete]
|
||||
)
|
||||
group = maybe_atomize(group)
|
||||
key = maybe_atomize(key)
|
||||
|
||||
migrate_from_db(opts)
|
||||
with true <- key_exists?(group, key) do
|
||||
shell_info("The following settings will be removed from ConfigDB:\n")
|
||||
|
||||
group
|
||||
|> ConfigDB.get_by_group_and_key(key)
|
||||
|> dump()
|
||||
|
||||
delete_key(group, key)
|
||||
else
|
||||
_ ->
|
||||
shell_error("No settings in ConfigDB for #{inspect(group)}, #{inspect(key)}. Aborting.")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["delete", "--force", group]) do
|
||||
start_pleroma()
|
||||
|
||||
group = maybe_atomize(group)
|
||||
|
||||
with true <- group_exists?(group) do
|
||||
shell_info("The following settings will be removed from ConfigDB:\n")
|
||||
dump_group(group)
|
||||
delete_group(group)
|
||||
else
|
||||
_ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["delete", group, key]) do
|
||||
start_pleroma()
|
||||
|
||||
group = maybe_atomize(group)
|
||||
key = maybe_atomize(key)
|
||||
|
||||
with true <- key_exists?(group, key) do
|
||||
shell_info("The following settings will be removed from ConfigDB:\n")
|
||||
|
||||
group
|
||||
|> ConfigDB.get_by_group_and_key(key)
|
||||
|> dump()
|
||||
|
||||
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
|
||||
delete_key(group, key)
|
||||
else
|
||||
shell_error("No changes made.")
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
shell_error("No settings in ConfigDB for #{inspect(group)}, #{inspect(key)}. Aborting.")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["delete", group]) do
|
||||
start_pleroma()
|
||||
|
||||
group = maybe_atomize(group)
|
||||
|
||||
with true <- group_exists?(group) do
|
||||
shell_info("The following settings will be removed from ConfigDB:\n")
|
||||
dump_group(group)
|
||||
|
||||
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
|
||||
delete_group(group)
|
||||
else
|
||||
shell_error("No changes made.")
|
||||
end
|
||||
else
|
||||
_ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.")
|
||||
end
|
||||
end
|
||||
|
||||
@spec migrate_to_db(Path.t() | nil) :: any()
|
||||
def migrate_to_db(file_path \\ nil) do
|
||||
with true <- Pleroma.Config.get([:configurable_from_database]),
|
||||
:ok <- Pleroma.Config.DeprecationWarnings.warn() do
|
||||
with :ok <- Pleroma.Config.DeprecationWarnings.warn() do
|
||||
config_file =
|
||||
if file_path do
|
||||
file_path
|
||||
|
|
@ -47,16 +221,15 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
|
||||
do_migrate_to_db(config_file)
|
||||
else
|
||||
:error -> deprecation_error()
|
||||
_ -> migration_error()
|
||||
_ ->
|
||||
shell_error("Migration is not allowed until all deprecation warnings have been resolved.")
|
||||
end
|
||||
end
|
||||
|
||||
defp do_migrate_to_db(config_file) do
|
||||
if File.exists?(config_file) do
|
||||
shell_info("Migrating settings from file: #{Path.expand(config_file)}")
|
||||
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
|
||||
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
|
||||
truncatedb()
|
||||
|
||||
custom_config =
|
||||
config_file
|
||||
|
|
@ -80,52 +253,38 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
shell_info("Settings for key #{key} migrated.")
|
||||
end)
|
||||
|
||||
shell_info("Settings for group :#{group} migrated.")
|
||||
shell_info("Settings for group #{inspect(group)} migrated.")
|
||||
end
|
||||
|
||||
defp migrate_from_db(opts) do
|
||||
if Pleroma.Config.get([:configurable_from_database]) do
|
||||
env = opts[:env] || Pleroma.Config.get(:env)
|
||||
env = opts[:env] || Pleroma.Config.get(:env)
|
||||
|
||||
config_path =
|
||||
if Pleroma.Config.get(:release) do
|
||||
:config_path
|
||||
|> Pleroma.Config.get()
|
||||
|> Path.dirname()
|
||||
else
|
||||
"config"
|
||||
end
|
||||
|> Path.join("#{env}.exported_from_db.secret.exs")
|
||||
config_path =
|
||||
if Pleroma.Config.get(:release) do
|
||||
:config_path
|
||||
|> Pleroma.Config.get()
|
||||
|> Path.dirname()
|
||||
else
|
||||
"config"
|
||||
end
|
||||
|> Path.join("#{env}.exported_from_db.secret.exs")
|
||||
|
||||
file = File.open!(config_path, [:write, :utf8])
|
||||
file = File.open!(config_path, [:write, :utf8])
|
||||
|
||||
IO.write(file, config_header())
|
||||
IO.write(file, config_header())
|
||||
|
||||
ConfigDB
|
||||
|> Repo.all()
|
||||
|> Enum.each(&write_and_delete(&1, file, opts[:delete]))
|
||||
ConfigDB
|
||||
|> Repo.all()
|
||||
|> Enum.each(&write_and_delete(&1, file, opts[:delete]))
|
||||
|
||||
:ok = File.close(file)
|
||||
System.cmd("mix", ["format", config_path])
|
||||
:ok = File.close(file)
|
||||
System.cmd("mix", ["format", config_path])
|
||||
|
||||
shell_info(
|
||||
"Database configuration settings have been exported to config/#{env}.exported_from_db.secret.exs"
|
||||
)
|
||||
else
|
||||
migration_error()
|
||||
end
|
||||
end
|
||||
|
||||
defp migration_error do
|
||||
shell_error(
|
||||
"Migration is not allowed in config. You can change this behavior by setting `config :pleroma, configurable_from_database: true`"
|
||||
shell_info(
|
||||
"Database configuration settings have been exported to config/#{env}.exported_from_db.secret.exs"
|
||||
)
|
||||
end
|
||||
|
||||
defp deprecation_error do
|
||||
shell_error("Migration is not allowed until all deprecation warnings have been resolved.")
|
||||
end
|
||||
|
||||
if Code.ensure_loaded?(Config.Reader) do
|
||||
defp config_header, do: "import Config\r\n\r\n"
|
||||
defp read_file(config_file), do: Config.Reader.read_imports!(config_file)
|
||||
|
|
@ -150,8 +309,80 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
|
||||
defp delete(config, true) do
|
||||
{:ok, _} = Repo.delete(config)
|
||||
shell_info("#{config.key} deleted from DB.")
|
||||
|
||||
shell_info(
|
||||
"config #{inspect(config.group)}, #{inspect(config.key)} was deleted from the ConfigDB."
|
||||
)
|
||||
end
|
||||
|
||||
defp delete(_config, _), do: :ok
|
||||
|
||||
defp dump(%ConfigDB{} = config) do
|
||||
value = inspect(config.value, limit: :infinity)
|
||||
|
||||
shell_info("config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n")
|
||||
end
|
||||
|
||||
defp dump(_), do: :noop
|
||||
|
||||
defp dump_group(group) when is_atom(group) do
|
||||
group
|
||||
|> ConfigDB.get_all_by_group()
|
||||
|> Enum.each(&dump/1)
|
||||
end
|
||||
|
||||
defp group_exists?(group) do
|
||||
group
|
||||
|> ConfigDB.get_all_by_group()
|
||||
|> Enum.any?()
|
||||
end
|
||||
|
||||
defp key_exists?(group, key) do
|
||||
group
|
||||
|> ConfigDB.get_by_group_and_key(key)
|
||||
|> is_nil
|
||||
|> Kernel.!()
|
||||
end
|
||||
|
||||
defp maybe_atomize(arg) when is_atom(arg), do: arg
|
||||
|
||||
defp maybe_atomize(":" <> arg), do: maybe_atomize(arg)
|
||||
|
||||
defp maybe_atomize(arg) when is_binary(arg) do
|
||||
if ConfigDB.module_name?(arg) do
|
||||
String.to_existing_atom("Elixir." <> arg)
|
||||
else
|
||||
String.to_atom(arg)
|
||||
end
|
||||
end
|
||||
|
||||
defp check_configdb(callback) do
|
||||
with true <- Pleroma.Config.get([:configurable_from_database]) do
|
||||
callback.()
|
||||
else
|
||||
_ ->
|
||||
shell_error(
|
||||
"ConfigDB not enabled. Please check the value of :configurable_from_database in your configuration."
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp delete_key(group, key) do
|
||||
check_configdb(fn ->
|
||||
ConfigDB.delete(%{group: group, key: key})
|
||||
end)
|
||||
end
|
||||
|
||||
defp delete_group(group) do
|
||||
check_configdb(fn ->
|
||||
group
|
||||
|> ConfigDB.get_all_by_group()
|
||||
|> Enum.each(&ConfigDB.delete/1)
|
||||
end)
|
||||
end
|
||||
|
||||
defp truncatedb do
|
||||
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
|
||||
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -48,9 +48,15 @@ defmodule Mix.Tasks.Pleroma.Database do
|
|||
def run(["update_users_following_followers_counts"]) do
|
||||
start_pleroma()
|
||||
|
||||
User
|
||||
|> Repo.all()
|
||||
|> Enum.each(&User.update_follower_count/1)
|
||||
Repo.transaction(
|
||||
fn ->
|
||||
from(u in User, select: u)
|
||||
|> Repo.stream()
|
||||
|> Stream.each(&User.update_follower_count/1)
|
||||
|> Stream.run()
|
||||
end,
|
||||
timeout: :infinity
|
||||
)
|
||||
end
|
||||
|
||||
def run(["prune_objects" | args]) do
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ defmodule Mix.Tasks.Pleroma.Frontend do
|
|||
end
|
||||
|
||||
def run(["install", frontend | args]) do
|
||||
log_level = Logger.level()
|
||||
Logger.configure(level: :warn)
|
||||
start_pleroma()
|
||||
|
||||
{options, [], []} =
|
||||
|
|
@ -33,109 +31,6 @@ defmodule Mix.Tasks.Pleroma.Frontend do
|
|||
]
|
||||
)
|
||||
|
||||
instance_static_dir =
|
||||
with nil <- options[:static_dir] do
|
||||
Pleroma.Config.get!([:instance, :static_dir])
|
||||
end
|
||||
|
||||
cmd_frontend_info = %{
|
||||
"name" => frontend,
|
||||
"ref" => options[:ref],
|
||||
"build_url" => options[:build_url],
|
||||
"build_dir" => options[:build_dir]
|
||||
}
|
||||
|
||||
config_frontend_info = Pleroma.Config.get([:frontends, :available, frontend], %{})
|
||||
|
||||
frontend_info =
|
||||
Map.merge(config_frontend_info, cmd_frontend_info, fn _key, config, cmd ->
|
||||
# This only overrides things that are actually set
|
||||
cmd || config
|
||||
end)
|
||||
|
||||
ref = frontend_info["ref"]
|
||||
|
||||
unless ref do
|
||||
raise "No ref given or configured"
|
||||
end
|
||||
|
||||
dest =
|
||||
Path.join([
|
||||
instance_static_dir,
|
||||
"frontends",
|
||||
frontend,
|
||||
ref
|
||||
])
|
||||
|
||||
fe_label = "#{frontend} (#{ref})"
|
||||
|
||||
tmp_dir = Path.join([instance_static_dir, "frontends", "tmp"])
|
||||
|
||||
with {_, :ok} <-
|
||||
{:download_or_unzip, download_or_unzip(frontend_info, tmp_dir, options[:file])},
|
||||
shell_info("Installing #{fe_label} to #{dest}"),
|
||||
:ok <- install_frontend(frontend_info, tmp_dir, dest) do
|
||||
File.rm_rf!(tmp_dir)
|
||||
shell_info("Frontend #{fe_label} installed to #{dest}")
|
||||
|
||||
Logger.configure(level: log_level)
|
||||
else
|
||||
{:download_or_unzip, _} ->
|
||||
shell_info("Could not download or unzip the frontend")
|
||||
|
||||
_e ->
|
||||
shell_info("Could not install the frontend")
|
||||
end
|
||||
end
|
||||
|
||||
defp download_or_unzip(frontend_info, temp_dir, file) do
|
||||
if file do
|
||||
with {:ok, zip} <- File.read(Path.expand(file)) do
|
||||
unzip(zip, temp_dir)
|
||||
end
|
||||
else
|
||||
download_build(frontend_info, temp_dir)
|
||||
end
|
||||
end
|
||||
|
||||
def unzip(zip, dest) do
|
||||
with {:ok, unzipped} <- :zip.unzip(zip, [:memory]) do
|
||||
File.rm_rf!(dest)
|
||||
File.mkdir_p!(dest)
|
||||
|
||||
Enum.each(unzipped, fn {filename, data} ->
|
||||
path = filename
|
||||
|
||||
new_file_path = Path.join(dest, path)
|
||||
|
||||
new_file_path
|
||||
|> Path.dirname()
|
||||
|> File.mkdir_p!()
|
||||
|
||||
File.write!(new_file_path, data)
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp download_build(frontend_info, dest) do
|
||||
shell_info("Downloading pre-built bundle for #{frontend_info["name"]}")
|
||||
url = String.replace(frontend_info["build_url"], "${ref}", frontend_info["ref"])
|
||||
|
||||
with {:ok, %{status: 200, body: zip_body}} <-
|
||||
Pleroma.HTTP.get(url, [], pool: :media, recv_timeout: 120_000) do
|
||||
unzip(zip_body, dest)
|
||||
else
|
||||
e -> {:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
defp install_frontend(frontend_info, source, dest) do
|
||||
from = frontend_info["build_dir"] || "dist"
|
||||
File.rm_rf!(dest)
|
||||
File.mkdir_p!(dest)
|
||||
File.cp_r!(Path.join([source, from]), dest)
|
||||
:ok
|
||||
Pleroma.Frontend.install(frontend, options)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -36,9 +36,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
listen_port: :string,
|
||||
strip_uploads: :string,
|
||||
anonymize_uploads: :string,
|
||||
dedupe_uploads: :string,
|
||||
skip_release_env: :boolean,
|
||||
release_env_file: :string
|
||||
dedupe_uploads: :string
|
||||
],
|
||||
aliases: [
|
||||
o: :output,
|
||||
|
|
@ -163,12 +161,21 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
)
|
||||
|> Path.expand()
|
||||
|
||||
{strip_uploads_message, strip_uploads_default} =
|
||||
if Pleroma.Utils.command_available?("exiftool") do
|
||||
{"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as installed. (y/n)",
|
||||
"y"}
|
||||
else
|
||||
{"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
|
||||
"n"}
|
||||
end
|
||||
|
||||
strip_uploads =
|
||||
get_option(
|
||||
options,
|
||||
:strip_uploads,
|
||||
"Do you want to strip location (GPS) data from uploaded images? (y/n)",
|
||||
"y"
|
||||
strip_uploads_message,
|
||||
strip_uploads_default
|
||||
) === "y"
|
||||
|
||||
anonymize_uploads =
|
||||
|
|
@ -243,24 +250,6 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
|
||||
write_robots_txt(static_dir, indexable, template_dir)
|
||||
|
||||
if Keyword.get(options, :skip_release_env, false) do
|
||||
shell_info("""
|
||||
Release environment file is skip. Please generate the release env file before start.
|
||||
`MIX_ENV=#{Mix.env()} mix pleroma.release_env gen`
|
||||
""")
|
||||
else
|
||||
shell_info("Generation the environment file:")
|
||||
|
||||
release_env_args =
|
||||
with path when not is_nil(path) <- Keyword.get(options, :release_env_file) do
|
||||
["gen", "--path", path]
|
||||
else
|
||||
_ -> ["gen"]
|
||||
end
|
||||
|
||||
Mix.Tasks.Pleroma.ReleaseEnv.run(release_env_args)
|
||||
end
|
||||
|
||||
shell_info(
|
||||
"\n All files successfully written! Refer to the installation instructions for your platform for next steps."
|
||||
)
|
||||
|
|
@ -273,7 +262,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
else
|
||||
shell_error(
|
||||
"The task would have overwritten the following files:\n" <>
|
||||
(Enum.map(paths, &"- #{&1}\n") |> Enum.join("")) <>
|
||||
(Enum.map(will_overwrite, &"- #{&1}\n") |> Enum.join("")) <>
|
||||
"Rerun with `--force` to overwrite them."
|
||||
)
|
||||
end
|
||||
|
|
@ -304,7 +293,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
defp upload_filters(filters) when is_map(filters) do
|
||||
enabled_filters =
|
||||
if filters.strip do
|
||||
[Pleroma.Upload.Filter.ExifTool]
|
||||
[Pleroma.Upload.Filter.Exiftool]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,76 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.ReleaseEnv do
|
||||
use Mix.Task
|
||||
import Mix.Pleroma
|
||||
|
||||
@shortdoc "Generate Pleroma environment file."
|
||||
@moduledoc File.read!("docs/administration/CLI_tasks/release_environments.md")
|
||||
|
||||
def run(["gen" | rest]) do
|
||||
{options, [], []} =
|
||||
OptionParser.parse(
|
||||
rest,
|
||||
strict: [
|
||||
force: :boolean,
|
||||
path: :string
|
||||
],
|
||||
aliases: [
|
||||
p: :path,
|
||||
f: :force
|
||||
]
|
||||
)
|
||||
|
||||
file_path =
|
||||
get_option(
|
||||
options,
|
||||
:path,
|
||||
"Environment file path",
|
||||
"./config/pleroma.env"
|
||||
)
|
||||
|
||||
env_path = Path.expand(file_path)
|
||||
|
||||
proceed? =
|
||||
if File.exists?(env_path) do
|
||||
get_option(
|
||||
options,
|
||||
:force,
|
||||
"Environment file already exists. Do you want to overwrite the #{env_path} file? (y/n)",
|
||||
"n"
|
||||
) === "y"
|
||||
else
|
||||
true
|
||||
end
|
||||
|
||||
if proceed? do
|
||||
case do_generate(env_path) do
|
||||
{:error, reason} ->
|
||||
shell_error(
|
||||
File.Error.message(%{action: "write to file", reason: reason, path: env_path})
|
||||
)
|
||||
|
||||
_ ->
|
||||
shell_info("\nThe file generated: #{env_path}.\n")
|
||||
|
||||
shell_info("""
|
||||
WARNING: before start pleroma app please make sure to make the file read-only and non-modifiable.
|
||||
Example:
|
||||
chmod 0444 #{file_path}
|
||||
chattr +i #{file_path}
|
||||
""")
|
||||
end
|
||||
else
|
||||
shell_info("\nThe file is exist. #{env_path}.\n")
|
||||
end
|
||||
end
|
||||
|
||||
def do_generate(path) do
|
||||
content = "RELEASE_COOKIE=#{Base.encode32(:crypto.strong_rand_bytes(32))}"
|
||||
|
||||
File.mkdir_p!(Path.dirname(path))
|
||||
File.write(path, content)
|
||||
end
|
||||
end
|
||||
|
|
@ -60,7 +60,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
- admin: #{if(admin?, do: "true", else: "false")}
|
||||
""")
|
||||
|
||||
proceed? = assume_yes? or shell_yes?("Continue?")
|
||||
proceed? = assume_yes? or shell_prompt("Continue?", "n") in ~w(Yn Y y)
|
||||
|
||||
if proceed? do
|
||||
start_pleroma()
|
||||
|
|
@ -345,11 +345,11 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def run(["toggle_confirmed", nickname]) do
|
||||
def run(["confirm", nickname]) do
|
||||
start_pleroma()
|
||||
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||
{:ok, user} = User.toggle_confirmation(user)
|
||||
{:ok, user} = User.confirm(user)
|
||||
|
||||
message = if user.confirmation_pending, do: "needs", else: "doesn't need"
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ defmodule Pleroma.Activity do
|
|||
|
||||
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
|
||||
|
||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||
|
||||
schema "activities" do
|
||||
field(:data, :map)
|
||||
field(:local, :boolean, default: true)
|
||||
|
|
@ -194,6 +196,19 @@ defmodule Pleroma.Activity do
|
|||
end
|
||||
end
|
||||
|
||||
def get_by_id_with_user_actor(id) do
|
||||
case FlakeId.flake_id?(id) do
|
||||
true ->
|
||||
Activity
|
||||
|> where([a], a.id == ^id)
|
||||
|> with_preloaded_user_actor()
|
||||
|> Repo.one()
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_by_id_with_object(id) do
|
||||
Activity
|
||||
|> where(id: ^id)
|
||||
|
|
@ -285,7 +300,7 @@ defmodule Pleroma.Activity do
|
|||
|
||||
defp purge_web_resp_cache(%Activity{} = activity) do
|
||||
%{path: path} = URI.parse(activity.data["id"])
|
||||
Cachex.del(:web_resp_cache, path)
|
||||
@cachex.del(:web_resp_cache, path)
|
||||
activity
|
||||
end
|
||||
|
||||
|
|
@ -356,4 +371,15 @@ defmodule Pleroma.Activity do
|
|||
actor = user_actor(activity)
|
||||
activity.id in actor.pinned_activities
|
||||
end
|
||||
|
||||
@spec get_by_object_ap_id_with_object(String.t()) :: t() | nil
|
||||
def get_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
|
||||
ap_id
|
||||
|> Queries.by_object_id()
|
||||
|> with_preloaded_object()
|
||||
|> first()
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
def get_by_object_ap_id_with_object(_), do: nil
|
||||
end
|
||||
|
|
|
|||
|
|
@ -19,15 +19,25 @@ defmodule Pleroma.Activity.Search do
|
|||
offset = Keyword.get(options, :offset, 0)
|
||||
author = Keyword.get(options, :author)
|
||||
|
||||
search_function =
|
||||
if :persistent_term.get({Pleroma.Repo, :postgres_version}) >= 11 do
|
||||
:websearch
|
||||
else
|
||||
:plain
|
||||
end
|
||||
|
||||
Activity
|
||||
|> Activity.with_preloaded_object()
|
||||
|> Activity.restrict_deactivated_users()
|
||||
|> restrict_public()
|
||||
|> query_with(index_type, search_query)
|
||||
|> query_with(index_type, search_query, search_function)
|
||||
|> maybe_restrict_local(user)
|
||||
|> maybe_restrict_author(author)
|
||||
|> maybe_restrict_blocked(user)
|
||||
|> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset)
|
||||
|> Pagination.fetch_paginated(
|
||||
%{"offset" => offset, "limit" => limit, "skip_order" => index_type == :rum},
|
||||
:offset
|
||||
)
|
||||
|> maybe_fetch(user, search_query)
|
||||
end
|
||||
|
||||
|
|
@ -50,7 +60,7 @@ defmodule Pleroma.Activity.Search do
|
|||
)
|
||||
end
|
||||
|
||||
defp query_with(q, :gin, search_query) do
|
||||
defp query_with(q, :gin, search_query, :plain) do
|
||||
from([a, o] in q,
|
||||
where:
|
||||
fragment(
|
||||
|
|
@ -61,7 +71,18 @@ defmodule Pleroma.Activity.Search do
|
|||
)
|
||||
end
|
||||
|
||||
defp query_with(q, :rum, search_query) do
|
||||
defp query_with(q, :gin, search_query, :websearch) do
|
||||
from([a, o] in q,
|
||||
where:
|
||||
fragment(
|
||||
"to_tsvector('english', ?->>'content') @@ websearch_to_tsquery('english', ?)",
|
||||
o.data,
|
||||
^search_query
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
defp query_with(q, :rum, search_query, :plain) do
|
||||
from([a, o] in q,
|
||||
where:
|
||||
fragment(
|
||||
|
|
@ -73,6 +94,18 @@ defmodule Pleroma.Activity.Search do
|
|||
)
|
||||
end
|
||||
|
||||
defp query_with(q, :rum, search_query, :websearch) do
|
||||
from([a, o] in q,
|
||||
where:
|
||||
fragment(
|
||||
"? @@ websearch_to_tsquery('english', ?)",
|
||||
o.fts_content,
|
||||
^search_query
|
||||
),
|
||||
order_by: [fragment("? <=> now()::date", o.inserted_at)]
|
||||
)
|
||||
end
|
||||
|
||||
defp maybe_restrict_local(q, user) do
|
||||
limit = Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated)
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ defmodule Pleroma.Application do
|
|||
setup_instrumenters()
|
||||
load_custom_modules()
|
||||
Pleroma.Docs.JSON.compile()
|
||||
limiters_setup()
|
||||
|
||||
adapter = Application.get_env(:tesla, :adapter)
|
||||
|
||||
|
|
@ -109,7 +110,28 @@ defmodule Pleroma.Application do
|
|||
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: Pleroma.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
result = Supervisor.start_link(children, opts)
|
||||
|
||||
set_postgres_server_version()
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
defp set_postgres_server_version do
|
||||
version =
|
||||
with %{rows: [[version]]} <- Ecto.Adapters.SQL.query!(Pleroma.Repo, "show server_version"),
|
||||
{num, _} <- Float.parse(version) do
|
||||
num
|
||||
else
|
||||
e ->
|
||||
Logger.warn(
|
||||
"Could not get the postgres version: #{inspect(e)}.\nSetting the default value of 9.6"
|
||||
)
|
||||
|
||||
9.6
|
||||
end
|
||||
|
||||
:persistent_term.put({Pleroma.Repo, :postgres_version}, version)
|
||||
end
|
||||
|
||||
def load_custom_modules do
|
||||
|
|
@ -207,8 +229,7 @@ defmodule Pleroma.Application do
|
|||
name: Pleroma.Web.Streamer.registry(),
|
||||
keys: :duplicate,
|
||||
partitions: System.schedulers_online()
|
||||
]},
|
||||
Pleroma.Web.FedSockets.Supervisor
|
||||
]}
|
||||
]
|
||||
end
|
||||
|
||||
|
|
@ -273,4 +294,10 @@ defmodule Pleroma.Application do
|
|||
end
|
||||
|
||||
defp http_children(_, _), do: []
|
||||
|
||||
@spec limiters_setup() :: :ok
|
||||
def limiters_setup do
|
||||
[Pleroma.Web.RichMedia.Helpers, Pleroma.Web.MediaProxy]
|
||||
|> Enum.each(&ConcurrentLimiter.new(&1, 1, 0))
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ defmodule Pleroma.ApplicationRequirements do
|
|||
|> check_migrations_applied!()
|
||||
|> check_welcome_message_config!()
|
||||
|> check_rum!()
|
||||
|> check_repo_pool_size!()
|
||||
|> handle_result()
|
||||
end
|
||||
|
||||
|
|
@ -188,6 +189,30 @@ defmodule Pleroma.ApplicationRequirements do
|
|||
|
||||
defp check_system_commands!(result), do: result
|
||||
|
||||
defp check_repo_pool_size!(:ok) do
|
||||
if Pleroma.Config.get([Pleroma.Repo, :pool_size], 10) != 10 and
|
||||
not Pleroma.Config.get([:dangerzone, :override_repo_pool_size], false) do
|
||||
Logger.error("""
|
||||
!!!CONFIG WARNING!!!
|
||||
|
||||
The database pool size has been altered from the recommended value of 10.
|
||||
|
||||
Please revert or ensure your database is tuned appropriately and then set
|
||||
`config :pleroma, :dangerzone, override_repo_pool_size: true`.
|
||||
|
||||
If you are experiencing database timeouts, please check the "Optimizing
|
||||
your PostgreSQL performance" section in the documentation. If you still
|
||||
encounter issues after that, please open an issue on the tracker.
|
||||
""")
|
||||
|
||||
{:error, "Repo.pool_size different than recommended value."}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp check_repo_pool_size!(result), do: result
|
||||
|
||||
defp check_filter(filter, command_required) do
|
||||
filters = Config.get([Pleroma.Upload, :filters])
|
||||
|
||||
|
|
|
|||
19
lib/pleroma/caching.ex
Normal file
19
lib/pleroma/caching.ex
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Caching do
|
||||
@callback get!(Cachex.cache(), any()) :: any()
|
||||
@callback get(Cachex.cache(), any()) :: {atom(), any()}
|
||||
@callback put(Cachex.cache(), any(), any(), Keyword.t()) :: {Cachex.status(), boolean()}
|
||||
@callback put(Cachex.cache(), any(), any()) :: {Cachex.status(), boolean()}
|
||||
@callback fetch!(Cachex.cache(), any(), function() | nil) :: any()
|
||||
# @callback del(Cachex.cache(), any(), Keyword.t()) :: {Cachex.status(), boolean()}
|
||||
@callback del(Cachex.cache(), any()) :: {Cachex.status(), boolean()}
|
||||
@callback stream!(Cachex.cache(), any()) :: Enumerable.t()
|
||||
@callback expire_at(Cachex.cache(), binary(), number()) :: {Cachex.status(), boolean()}
|
||||
@callback exists?(Cachex.cache(), any()) :: {Cachex.status(), boolean()}
|
||||
@callback execute!(Cachex.cache(), function()) :: any()
|
||||
@callback get_and_update(Cachex.cache(), any(), function()) ::
|
||||
{:commit | :ignore, any()}
|
||||
end
|
||||
|
|
@ -7,6 +7,8 @@ defmodule Pleroma.Captcha do
|
|||
alias Plug.Crypto.KeyGenerator
|
||||
alias Plug.Crypto.MessageEncryptor
|
||||
|
||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||
|
||||
@doc """
|
||||
Ask the configured captcha service for a new captcha
|
||||
"""
|
||||
|
|
@ -86,7 +88,7 @@ defmodule Pleroma.Captcha do
|
|||
end
|
||||
|
||||
defp validate_usage(token) do
|
||||
if is_nil(Cachex.get!(:used_captcha_cache, token)) do
|
||||
if is_nil(@cachex.get!(:used_captcha_cache, token)) do
|
||||
:ok
|
||||
else
|
||||
{:error, :already_used}
|
||||
|
|
@ -95,7 +97,7 @@ defmodule Pleroma.Captcha do
|
|||
|
||||
defp mark_captcha_as_used(token) do
|
||||
ttl = seconds_valid() |> :timer.seconds()
|
||||
Cachex.put(:used_captcha_cache, token, true, ttl: ttl)
|
||||
@cachex.put(:used_captcha_cache, token, true, ttl: ttl)
|
||||
end
|
||||
|
||||
defp method, do: Pleroma.Config.get!([__MODULE__, :method])
|
||||
|
|
|
|||
|
|
@ -3,14 +3,18 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config do
|
||||
@behaviour Pleroma.Config.Getting
|
||||
defmodule Error do
|
||||
defexception [:message]
|
||||
end
|
||||
|
||||
@impl true
|
||||
def get(key), do: get(key, nil)
|
||||
|
||||
@impl true
|
||||
def get([key], default), do: get(key, default)
|
||||
|
||||
@impl true
|
||||
def get([_ | _] = path, default) do
|
||||
case fetch(path) do
|
||||
{:ok, value} -> value
|
||||
|
|
@ -18,6 +22,7 @@ defmodule Pleroma.Config do
|
|||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def get(key, default) do
|
||||
Application.get_env(:pleroma, key, default)
|
||||
end
|
||||
|
|
|
|||
8
lib/pleroma/config/getting.ex
Normal file
8
lib/pleroma/config/getting.ex
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.Getting do
|
||||
@callback get(any()) :: any()
|
||||
@callback get(any(), any()) :: any()
|
||||
end
|
||||
|
|
@ -9,12 +9,7 @@ defmodule Pleroma.Config.Holder do
|
|||
def save_default do
|
||||
default_config =
|
||||
if System.get_env("RELEASE_NAME") do
|
||||
release_config =
|
||||
[:code.root_dir(), "releases", System.get_env("RELEASE_VSN"), "releases.exs"]
|
||||
|> Path.join()
|
||||
|> Pleroma.Config.Loader.read()
|
||||
|
||||
Pleroma.Config.Loader.merge(@config, release_config)
|
||||
Pleroma.Config.Loader.merge(@config, release_defaults())
|
||||
else
|
||||
@config
|
||||
end
|
||||
|
|
@ -32,4 +27,16 @@ defmodule Pleroma.Config.Holder do
|
|||
def default_config(group, key), do: get_in(get_default(), [group, key])
|
||||
|
||||
defp get_default, do: Pleroma.Config.get(:default_config)
|
||||
|
||||
@spec release_defaults() :: keyword()
|
||||
def release_defaults do
|
||||
[
|
||||
pleroma: [
|
||||
{:instance, [static_dir: "/var/lib/pleroma/static"]},
|
||||
{Pleroma.Uploaders.Local, [uploads: "/var/lib/pleroma/uploads"]},
|
||||
{:modules, [runtime_dir: "/var/lib/pleroma/modules"]},
|
||||
{:release, true}
|
||||
]
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
50
lib/pleroma/config/release_runtime_provider.ex
Normal file
50
lib/pleroma/config/release_runtime_provider.ex
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
defmodule Pleroma.Config.ReleaseRuntimeProvider do
|
||||
@moduledoc """
|
||||
Imports `runtime.exs` and `{env}.exported_from_db.secret.exs` for elixir releases.
|
||||
"""
|
||||
@behaviour Config.Provider
|
||||
|
||||
@impl true
|
||||
def init(opts), do: opts
|
||||
|
||||
@impl true
|
||||
def load(config, _opts) do
|
||||
with_defaults = Config.Reader.merge(config, Pleroma.Config.Holder.release_defaults())
|
||||
|
||||
config_path = System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs"
|
||||
|
||||
with_runtime_config =
|
||||
if File.exists?(config_path) do
|
||||
runtime_config = Config.Reader.read!(config_path)
|
||||
|
||||
with_defaults
|
||||
|> Config.Reader.merge(pleroma: [config_path: config_path])
|
||||
|> Config.Reader.merge(runtime_config)
|
||||
else
|
||||
warning = [
|
||||
IO.ANSI.red(),
|
||||
IO.ANSI.bright(),
|
||||
"!!! #{config_path} not found! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file",
|
||||
IO.ANSI.reset()
|
||||
]
|
||||
|
||||
IO.puts(warning)
|
||||
with_defaults
|
||||
end
|
||||
|
||||
exported_config_path =
|
||||
config_path
|
||||
|> Path.dirname()
|
||||
|> Path.join("prod.exported_from_db.secret.exs")
|
||||
|
||||
with_exported =
|
||||
if File.exists?(exported_config_path) do
|
||||
exported_config = Config.Reader.read!(with_runtime_config)
|
||||
Config.Reader.merge(with_runtime_config, exported_config)
|
||||
else
|
||||
with_runtime_config
|
||||
end
|
||||
|
||||
with_exported
|
||||
end
|
||||
end
|
||||
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.ConfigDB do
|
|||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query, only: [select: 3]
|
||||
import Ecto.Query, only: [select: 3, from: 2]
|
||||
import Pleroma.Web.Gettext
|
||||
|
||||
alias __MODULE__
|
||||
|
|
@ -41,8 +41,18 @@ defmodule Pleroma.ConfigDB do
|
|||
end)
|
||||
end
|
||||
|
||||
@spec get_all_by_group(atom() | String.t()) :: [t()]
|
||||
def get_all_by_group(group) do
|
||||
from(c in ConfigDB, where: c.group == ^group) |> Repo.all()
|
||||
end
|
||||
|
||||
@spec get_by_group_and_key(atom() | String.t(), atom() | String.t()) :: t() | nil
|
||||
def get_by_group_and_key(group, key) do
|
||||
get_by_params(%{group: group, key: key})
|
||||
end
|
||||
|
||||
@spec get_by_params(map()) :: ConfigDB.t() | nil
|
||||
def get_by_params(params), do: Repo.get_by(ConfigDB, params)
|
||||
def get_by_params(%{group: _, key: _} = params), do: Repo.get_by(ConfigDB, params)
|
||||
|
||||
@spec changeset(ConfigDB.t(), map()) :: Changeset.t()
|
||||
def changeset(config, params \\ %{}) do
|
||||
|
|
|
|||
|
|
@ -26,4 +26,6 @@ defmodule Pleroma.Constants do
|
|||
do:
|
||||
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css)
|
||||
)
|
||||
|
||||
def as_local_public, do: Pleroma.Web.base_url() <> "/#Public"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@ defmodule Pleroma.Docs.JSON do
|
|||
|
||||
@spec compile :: :ok
|
||||
def compile do
|
||||
:persistent_term.put(@term, Pleroma.Docs.Generator.convert_to_strings(@raw_descriptions))
|
||||
descriptions =
|
||||
Pleroma.Web.ActivityPub.MRF.config_descriptions()
|
||||
|> Enum.reduce(@raw_descriptions, fn description, acc -> [description | acc] end)
|
||||
|
||||
:persistent_term.put(@term, Pleroma.Docs.Generator.convert_to_strings(descriptions))
|
||||
end
|
||||
|
||||
@spec compiled_descriptions :: Map.t()
|
||||
|
|
|
|||
|
|
@ -18,10 +18,6 @@ defmodule Pleroma.Emails.AdminEmail do
|
|||
Keyword.get(instance_config(), :notify_email, instance_config()[:email])
|
||||
end
|
||||
|
||||
defp user_url(user) do
|
||||
Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, user.id)
|
||||
end
|
||||
|
||||
def test_email(mail_to \\ nil) do
|
||||
html_body = """
|
||||
<h3>Instance Test Email</h3>
|
||||
|
|
@ -52,6 +48,9 @@ defmodule Pleroma.Emails.AdminEmail do
|
|||
status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, id)
|
||||
"<li><a href=\"#{status_url}\">#{status_url}</li>"
|
||||
|
||||
%{"id" => id} when is_binary(id) ->
|
||||
"<li><a href=\"#{id}\">#{id}</li>"
|
||||
|
||||
id when is_binary(id) ->
|
||||
"<li><a href=\"#{id}\">#{id}</li>"
|
||||
end)
|
||||
|
|
@ -69,8 +68,8 @@ defmodule Pleroma.Emails.AdminEmail do
|
|||
end
|
||||
|
||||
html_body = """
|
||||
<p>Reported by: <a href="#{user_url(reporter)}">#{reporter.nickname}</a></p>
|
||||
<p>Reported Account: <a href="#{user_url(account)}">#{account.nickname}</a></p>
|
||||
<p>Reported by: <a href="#{reporter.ap_id}">#{reporter.nickname}</a></p>
|
||||
<p>Reported Account: <a href="#{account.ap_id}">#{account.nickname}</a></p>
|
||||
#{comment_html}
|
||||
#{statuses_html}
|
||||
<p>
|
||||
|
|
@ -86,7 +85,7 @@ defmodule Pleroma.Emails.AdminEmail do
|
|||
|
||||
def new_unapproved_registration(to, account) do
|
||||
html_body = """
|
||||
<p>New account for review: <a href="#{user_url(account)}">@#{account.nickname}</a></p>
|
||||
<p>New account for review: <a href="#{account.ap_id}">@#{account.nickname}</a></p>
|
||||
<blockquote>#{HTML.strip_tags(account.registration_reason)}</blockquote>
|
||||
<a href="#{Pleroma.Web.base_url()}/pleroma/admin/#/users/#{account.id}/">Visit AdminFE</a>
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -93,6 +93,19 @@ defmodule Pleroma.Emails.UserEmail do
|
|||
|> html_body(html_body)
|
||||
end
|
||||
|
||||
def approval_pending_email(user) do
|
||||
html_body = """
|
||||
<h3>Awaiting Approval</h3>
|
||||
<p>Your account at #{instance_name()} is being reviewed by staff. You will receive another email once your account is approved.</p>
|
||||
"""
|
||||
|
||||
new()
|
||||
|> to(recipient(user))
|
||||
|> from(sender())
|
||||
|> subject("Your account is awaiting approval")
|
||||
|> html_body(html_body)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Email used in digest email notifications
|
||||
Includes Mentions and New Followers data
|
||||
|
|
@ -151,7 +164,7 @@ defmodule Pleroma.Emails.UserEmail do
|
|||
|
||||
logo_path =
|
||||
if is_nil(logo) do
|
||||
Path.join(:code.priv_dir(:pleroma), "static/static/logo.png")
|
||||
Path.join(:code.priv_dir(:pleroma), "static/static/logo.svg")
|
||||
else
|
||||
Path.join(Config.get([:instance, :static_dir]), logo)
|
||||
end
|
||||
|
|
@ -162,7 +175,7 @@ defmodule Pleroma.Emails.UserEmail do
|
|||
|> subject("Your digest from #{instance_name()}")
|
||||
|> put_layout(false)
|
||||
|> render_body("digest.html", html_data)
|
||||
|> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.png", type: :inline))
|
||||
|> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.svg", type: :inline))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,769 +0,0 @@
|
|||
# emoji-data.txt
|
||||
# Date: 2019-01-15, 12:10:05 GMT
|
||||
# © 2019 Unicode®, Inc.
|
||||
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
|
||||
# For terms of use, see http://www.unicode.org/terms_of_use.html
|
||||
#
|
||||
# Emoji Data for UTS #51
|
||||
# Version: 12.0
|
||||
#
|
||||
# For documentation and usage, see http://www.unicode.org/reports/tr51
|
||||
#
|
||||
# Format:
|
||||
# <codepoint(s)> ; <property> # <comments>
|
||||
# Note: there is no guarantee as to the structure of whitespace or comments
|
||||
#
|
||||
# Characters and sequences are listed in code point order. Users should be shown a more natural order.
|
||||
# See the CLDR collation order for Emoji.
|
||||
|
||||
|
||||
# ================================================
|
||||
|
||||
# All omitted code points have Emoji=No
|
||||
# @missing: 0000..10FFFF ; Emoji ; No
|
||||
|
||||
0023 ; Emoji # 1.1 [1] (#️) number sign
|
||||
002A ; Emoji # 1.1 [1] (*️) asterisk
|
||||
0030..0039 ; Emoji # 1.1 [10] (0️..9️) digit zero..digit nine
|
||||
00A9 ; Emoji # 1.1 [1] (©️) copyright
|
||||
00AE ; Emoji # 1.1 [1] (®️) registered
|
||||
203C ; Emoji # 1.1 [1] (‼️) double exclamation mark
|
||||
2049 ; Emoji # 3.0 [1] (⁉️) exclamation question mark
|
||||
2122 ; Emoji # 1.1 [1] (™️) trade mark
|
||||
2139 ; Emoji # 3.0 [1] (ℹ️) information
|
||||
2194..2199 ; Emoji # 1.1 [6] (↔️..↙️) left-right arrow..down-left arrow
|
||||
21A9..21AA ; Emoji # 1.1 [2] (↩️..↪️) right arrow curving left..left arrow curving right
|
||||
231A..231B ; Emoji # 1.1 [2] (⌚..⌛) watch..hourglass done
|
||||
2328 ; Emoji # 1.1 [1] (⌨️) keyboard
|
||||
23CF ; Emoji # 4.0 [1] (⏏️) eject button
|
||||
23E9..23F3 ; Emoji # 6.0 [11] (⏩..⏳) fast-forward button..hourglass not done
|
||||
23F8..23FA ; Emoji # 7.0 [3] (⏸️..⏺️) pause button..record button
|
||||
24C2 ; Emoji # 1.1 [1] (Ⓜ️) circled M
|
||||
25AA..25AB ; Emoji # 1.1 [2] (▪️..▫️) black small square..white small square
|
||||
25B6 ; Emoji # 1.1 [1] (▶️) play button
|
||||
25C0 ; Emoji # 1.1 [1] (◀️) reverse button
|
||||
25FB..25FE ; Emoji # 3.2 [4] (◻️..◾) white medium square..black medium-small square
|
||||
2600..2604 ; Emoji # 1.1 [5] (☀️..☄️) sun..comet
|
||||
260E ; Emoji # 1.1 [1] (☎️) telephone
|
||||
2611 ; Emoji # 1.1 [1] (☑️) check box with check
|
||||
2614..2615 ; Emoji # 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage
|
||||
2618 ; Emoji # 4.1 [1] (☘️) shamrock
|
||||
261D ; Emoji # 1.1 [1] (☝️) index pointing up
|
||||
2620 ; Emoji # 1.1 [1] (☠️) skull and crossbones
|
||||
2622..2623 ; Emoji # 1.1 [2] (☢️..☣️) radioactive..biohazard
|
||||
2626 ; Emoji # 1.1 [1] (☦️) orthodox cross
|
||||
262A ; Emoji # 1.1 [1] (☪️) star and crescent
|
||||
262E..262F ; Emoji # 1.1 [2] (☮️..☯️) peace symbol..yin yang
|
||||
2638..263A ; Emoji # 1.1 [3] (☸️..☺️) wheel of dharma..smiling face
|
||||
2640 ; Emoji # 1.1 [1] (♀️) female sign
|
||||
2642 ; Emoji # 1.1 [1] (♂️) male sign
|
||||
2648..2653 ; Emoji # 1.1 [12] (♈..♓) Aries..Pisces
|
||||
265F..2660 ; Emoji # 1.1 [2] (♟️..♠️) chess pawn..spade suit
|
||||
2663 ; Emoji # 1.1 [1] (♣️) club suit
|
||||
2665..2666 ; Emoji # 1.1 [2] (♥️..♦️) heart suit..diamond suit
|
||||
2668 ; Emoji # 1.1 [1] (♨️) hot springs
|
||||
267B ; Emoji # 3.2 [1] (♻️) recycling symbol
|
||||
267E..267F ; Emoji # 4.1 [2] (♾️..♿) infinity..wheelchair symbol
|
||||
2692..2697 ; Emoji # 4.1 [6] (⚒️..⚗️) hammer and pick..alembic
|
||||
2699 ; Emoji # 4.1 [1] (⚙️) gear
|
||||
269B..269C ; Emoji # 4.1 [2] (⚛️..⚜️) atom symbol..fleur-de-lis
|
||||
26A0..26A1 ; Emoji # 4.0 [2] (⚠️..⚡) warning..high voltage
|
||||
26AA..26AB ; Emoji # 4.1 [2] (⚪..⚫) white circle..black circle
|
||||
26B0..26B1 ; Emoji # 4.1 [2] (⚰️..⚱️) coffin..funeral urn
|
||||
26BD..26BE ; Emoji # 5.2 [2] (⚽..⚾) soccer ball..baseball
|
||||
26C4..26C5 ; Emoji # 5.2 [2] (⛄..⛅) snowman without snow..sun behind cloud
|
||||
26C8 ; Emoji # 5.2 [1] (⛈️) cloud with lightning and rain
|
||||
26CE ; Emoji # 6.0 [1] (⛎) Ophiuchus
|
||||
26CF ; Emoji # 5.2 [1] (⛏️) pick
|
||||
26D1 ; Emoji # 5.2 [1] (⛑️) rescue worker’s helmet
|
||||
26D3..26D4 ; Emoji # 5.2 [2] (⛓️..⛔) chains..no entry
|
||||
26E9..26EA ; Emoji # 5.2 [2] (⛩️..⛪) shinto shrine..church
|
||||
26F0..26F5 ; Emoji # 5.2 [6] (⛰️..⛵) mountain..sailboat
|
||||
26F7..26FA ; Emoji # 5.2 [4] (⛷️..⛺) skier..tent
|
||||
26FD ; Emoji # 5.2 [1] (⛽) fuel pump
|
||||
2702 ; Emoji # 1.1 [1] (✂️) scissors
|
||||
2705 ; Emoji # 6.0 [1] (✅) check mark button
|
||||
2708..2709 ; Emoji # 1.1 [2] (✈️..✉️) airplane..envelope
|
||||
270A..270B ; Emoji # 6.0 [2] (✊..✋) raised fist..raised hand
|
||||
270C..270D ; Emoji # 1.1 [2] (✌️..✍️) victory hand..writing hand
|
||||
270F ; Emoji # 1.1 [1] (✏️) pencil
|
||||
2712 ; Emoji # 1.1 [1] (✒️) black nib
|
||||
2714 ; Emoji # 1.1 [1] (✔️) check mark
|
||||
2716 ; Emoji # 1.1 [1] (✖️) multiplication sign
|
||||
271D ; Emoji # 1.1 [1] (✝️) latin cross
|
||||
2721 ; Emoji # 1.1 [1] (✡️) star of David
|
||||
2728 ; Emoji # 6.0 [1] (✨) sparkles
|
||||
2733..2734 ; Emoji # 1.1 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star
|
||||
2744 ; Emoji # 1.1 [1] (❄️) snowflake
|
||||
2747 ; Emoji # 1.1 [1] (❇️) sparkle
|
||||
274C ; Emoji # 6.0 [1] (❌) cross mark
|
||||
274E ; Emoji # 6.0 [1] (❎) cross mark button
|
||||
2753..2755 ; Emoji # 6.0 [3] (❓..❕) question mark..white exclamation mark
|
||||
2757 ; Emoji # 5.2 [1] (❗) exclamation mark
|
||||
2763..2764 ; Emoji # 1.1 [2] (❣️..❤️) heart exclamation..red heart
|
||||
2795..2797 ; Emoji # 6.0 [3] (➕..➗) plus sign..division sign
|
||||
27A1 ; Emoji # 1.1 [1] (➡️) right arrow
|
||||
27B0 ; Emoji # 6.0 [1] (➰) curly loop
|
||||
27BF ; Emoji # 6.0 [1] (➿) double curly loop
|
||||
2934..2935 ; Emoji # 3.2 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down
|
||||
2B05..2B07 ; Emoji # 4.0 [3] (⬅️..⬇️) left arrow..down arrow
|
||||
2B1B..2B1C ; Emoji # 5.1 [2] (⬛..⬜) black large square..white large square
|
||||
2B50 ; Emoji # 5.1 [1] (⭐) star
|
||||
2B55 ; Emoji # 5.2 [1] (⭕) hollow red circle
|
||||
3030 ; Emoji # 1.1 [1] (〰️) wavy dash
|
||||
303D ; Emoji # 3.2 [1] (〽️) part alternation mark
|
||||
3297 ; Emoji # 1.1 [1] (㊗️) Japanese “congratulations” button
|
||||
3299 ; Emoji # 1.1 [1] (㊙️) Japanese “secret” button
|
||||
1F004 ; Emoji # 5.1 [1] (🀄) mahjong red dragon
|
||||
1F0CF ; Emoji # 6.0 [1] (🃏) joker
|
||||
1F170..1F171 ; Emoji # 6.0 [2] (🅰️..🅱️) A button (blood type)..B button (blood type)
|
||||
1F17E ; Emoji # 6.0 [1] (🅾️) O button (blood type)
|
||||
1F17F ; Emoji # 5.2 [1] (🅿️) P button
|
||||
1F18E ; Emoji # 6.0 [1] (🆎) AB button (blood type)
|
||||
1F191..1F19A ; Emoji # 6.0 [10] (🆑..🆚) CL button..VS button
|
||||
1F1E6..1F1FF ; Emoji # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
|
||||
1F201..1F202 ; Emoji # 6.0 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button
|
||||
1F21A ; Emoji # 5.2 [1] (🈚) Japanese “free of charge” button
|
||||
1F22F ; Emoji # 5.2 [1] (🈯) Japanese “reserved” button
|
||||
1F232..1F23A ; Emoji # 6.0 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button
|
||||
1F250..1F251 ; Emoji # 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
|
||||
1F300..1F320 ; Emoji # 6.0 [33] (🌀..🌠) cyclone..shooting star
|
||||
1F321 ; Emoji # 7.0 [1] (🌡️) thermometer
|
||||
1F324..1F32C ; Emoji # 7.0 [9] (🌤️..🌬️) sun behind small cloud..wind face
|
||||
1F32D..1F32F ; Emoji # 8.0 [3] (🌭..🌯) hot dog..burrito
|
||||
1F330..1F335 ; Emoji # 6.0 [6] (🌰..🌵) chestnut..cactus
|
||||
1F336 ; Emoji # 7.0 [1] (🌶️) hot pepper
|
||||
1F337..1F37C ; Emoji # 6.0 [70] (🌷..🍼) tulip..baby bottle
|
||||
1F37D ; Emoji # 7.0 [1] (🍽️) fork and knife with plate
|
||||
1F37E..1F37F ; Emoji # 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn
|
||||
1F380..1F393 ; Emoji # 6.0 [20] (🎀..🎓) ribbon..graduation cap
|
||||
1F396..1F397 ; Emoji # 7.0 [2] (🎖️..🎗️) military medal..reminder ribbon
|
||||
1F399..1F39B ; Emoji # 7.0 [3] (🎙️..🎛️) studio microphone..control knobs
|
||||
1F39E..1F39F ; Emoji # 7.0 [2] (🎞️..🎟️) film frames..admission tickets
|
||||
1F3A0..1F3C4 ; Emoji # 6.0 [37] (🎠..🏄) carousel horse..person surfing
|
||||
1F3C5 ; Emoji # 7.0 [1] (🏅) sports medal
|
||||
1F3C6..1F3CA ; Emoji # 6.0 [5] (🏆..🏊) trophy..person swimming
|
||||
1F3CB..1F3CE ; Emoji # 7.0 [4] (🏋️..🏎️) person lifting weights..racing car
|
||||
1F3CF..1F3D3 ; Emoji # 8.0 [5] (🏏..🏓) cricket game..ping pong
|
||||
1F3D4..1F3DF ; Emoji # 7.0 [12] (🏔️..🏟️) snow-capped mountain..stadium
|
||||
1F3E0..1F3F0 ; Emoji # 6.0 [17] (🏠..🏰) house..castle
|
||||
1F3F3..1F3F5 ; Emoji # 7.0 [3] (🏳️..🏵️) white flag..rosette
|
||||
1F3F7 ; Emoji # 7.0 [1] (🏷️) label
|
||||
1F3F8..1F3FF ; Emoji # 8.0 [8] (🏸..🏿) badminton..dark skin tone
|
||||
1F400..1F43E ; Emoji # 6.0 [63] (🐀..🐾) rat..paw prints
|
||||
1F43F ; Emoji # 7.0 [1] (🐿️) chipmunk
|
||||
1F440 ; Emoji # 6.0 [1] (👀) eyes
|
||||
1F441 ; Emoji # 7.0 [1] (👁️) eye
|
||||
1F442..1F4F7 ; Emoji # 6.0[182] (👂..📷) ear..camera
|
||||
1F4F8 ; Emoji # 7.0 [1] (📸) camera with flash
|
||||
1F4F9..1F4FC ; Emoji # 6.0 [4] (📹..📼) video camera..videocassette
|
||||
1F4FD ; Emoji # 7.0 [1] (📽️) film projector
|
||||
1F4FF ; Emoji # 8.0 [1] (📿) prayer beads
|
||||
1F500..1F53D ; Emoji # 6.0 [62] (🔀..🔽) shuffle tracks button..downwards button
|
||||
1F549..1F54A ; Emoji # 7.0 [2] (🕉️..🕊️) om..dove
|
||||
1F54B..1F54E ; Emoji # 8.0 [4] (🕋..🕎) kaaba..menorah
|
||||
1F550..1F567 ; Emoji # 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty
|
||||
1F56F..1F570 ; Emoji # 7.0 [2] (🕯️..🕰️) candle..mantelpiece clock
|
||||
1F573..1F579 ; Emoji # 7.0 [7] (🕳️..🕹️) hole..joystick
|
||||
1F57A ; Emoji # 9.0 [1] (🕺) man dancing
|
||||
1F587 ; Emoji # 7.0 [1] (🖇️) linked paperclips
|
||||
1F58A..1F58D ; Emoji # 7.0 [4] (🖊️..🖍️) pen..crayon
|
||||
1F590 ; Emoji # 7.0 [1] (🖐️) hand with fingers splayed
|
||||
1F595..1F596 ; Emoji # 7.0 [2] (🖕..🖖) middle finger..vulcan salute
|
||||
1F5A4 ; Emoji # 9.0 [1] (🖤) black heart
|
||||
1F5A5 ; Emoji # 7.0 [1] (🖥️) desktop computer
|
||||
1F5A8 ; Emoji # 7.0 [1] (🖨️) printer
|
||||
1F5B1..1F5B2 ; Emoji # 7.0 [2] (🖱️..🖲️) computer mouse..trackball
|
||||
1F5BC ; Emoji # 7.0 [1] (🖼️) framed picture
|
||||
1F5C2..1F5C4 ; Emoji # 7.0 [3] (🗂️..🗄️) card index dividers..file cabinet
|
||||
1F5D1..1F5D3 ; Emoji # 7.0 [3] (🗑️..🗓️) wastebasket..spiral calendar
|
||||
1F5DC..1F5DE ; Emoji # 7.0 [3] (🗜️..🗞️) clamp..rolled-up newspaper
|
||||
1F5E1 ; Emoji # 7.0 [1] (🗡️) dagger
|
||||
1F5E3 ; Emoji # 7.0 [1] (🗣️) speaking head
|
||||
1F5E8 ; Emoji # 7.0 [1] (🗨️) left speech bubble
|
||||
1F5EF ; Emoji # 7.0 [1] (🗯️) right anger bubble
|
||||
1F5F3 ; Emoji # 7.0 [1] (🗳️) ballot box with ballot
|
||||
1F5FA ; Emoji # 7.0 [1] (🗺️) world map
|
||||
1F5FB..1F5FF ; Emoji # 6.0 [5] (🗻..🗿) mount fuji..moai
|
||||
1F600 ; Emoji # 6.1 [1] (😀) grinning face
|
||||
1F601..1F610 ; Emoji # 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face
|
||||
1F611 ; Emoji # 6.1 [1] (😑) expressionless face
|
||||
1F612..1F614 ; Emoji # 6.0 [3] (😒..😔) unamused face..pensive face
|
||||
1F615 ; Emoji # 6.1 [1] (😕) confused face
|
||||
1F616 ; Emoji # 6.0 [1] (😖) confounded face
|
||||
1F617 ; Emoji # 6.1 [1] (😗) kissing face
|
||||
1F618 ; Emoji # 6.0 [1] (😘) face blowing a kiss
|
||||
1F619 ; Emoji # 6.1 [1] (😙) kissing face with smiling eyes
|
||||
1F61A ; Emoji # 6.0 [1] (😚) kissing face with closed eyes
|
||||
1F61B ; Emoji # 6.1 [1] (😛) face with tongue
|
||||
1F61C..1F61E ; Emoji # 6.0 [3] (😜..😞) winking face with tongue..disappointed face
|
||||
1F61F ; Emoji # 6.1 [1] (😟) worried face
|
||||
1F620..1F625 ; Emoji # 6.0 [6] (😠..😥) angry face..sad but relieved face
|
||||
1F626..1F627 ; Emoji # 6.1 [2] (😦..😧) frowning face with open mouth..anguished face
|
||||
1F628..1F62B ; Emoji # 6.0 [4] (😨..😫) fearful face..tired face
|
||||
1F62C ; Emoji # 6.1 [1] (😬) grimacing face
|
||||
1F62D ; Emoji # 6.0 [1] (😭) loudly crying face
|
||||
1F62E..1F62F ; Emoji # 6.1 [2] (😮..😯) face with open mouth..hushed face
|
||||
1F630..1F633 ; Emoji # 6.0 [4] (😰..😳) anxious face with sweat..flushed face
|
||||
1F634 ; Emoji # 6.1 [1] (😴) sleeping face
|
||||
1F635..1F640 ; Emoji # 6.0 [12] (😵..🙀) dizzy face..weary cat
|
||||
1F641..1F642 ; Emoji # 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
|
||||
1F643..1F644 ; Emoji # 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
|
||||
1F645..1F64F ; Emoji # 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
|
||||
1F680..1F6C5 ; Emoji # 6.0 [70] (🚀..🛅) rocket..left luggage
|
||||
1F6CB..1F6CF ; Emoji # 7.0 [5] (🛋️..🛏️) couch and lamp..bed
|
||||
1F6D0 ; Emoji # 8.0 [1] (🛐) place of worship
|
||||
1F6D1..1F6D2 ; Emoji # 9.0 [2] (🛑..🛒) stop sign..shopping cart
|
||||
1F6D5 ; Emoji # 12.0 [1] (🛕) hindu temple
|
||||
1F6E0..1F6E5 ; Emoji # 7.0 [6] (🛠️..🛥️) hammer and wrench..motor boat
|
||||
1F6E9 ; Emoji # 7.0 [1] (🛩️) small airplane
|
||||
1F6EB..1F6EC ; Emoji # 7.0 [2] (🛫..🛬) airplane departure..airplane arrival
|
||||
1F6F0 ; Emoji # 7.0 [1] (🛰️) satellite
|
||||
1F6F3 ; Emoji # 7.0 [1] (🛳️) passenger ship
|
||||
1F6F4..1F6F6 ; Emoji # 9.0 [3] (🛴..🛶) kick scooter..canoe
|
||||
1F6F7..1F6F8 ; Emoji # 10.0 [2] (🛷..🛸) sled..flying saucer
|
||||
1F6F9 ; Emoji # 11.0 [1] (🛹) skateboard
|
||||
1F6FA ; Emoji # 12.0 [1] (🛺) auto rickshaw
|
||||
1F7E0..1F7EB ; Emoji # 12.0 [12] (🟠..🟫) orange circle..brown square
|
||||
1F90D..1F90F ; Emoji # 12.0 [3] (🤍..🤏) white heart..pinching hand
|
||||
1F910..1F918 ; Emoji # 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
|
||||
1F919..1F91E ; Emoji # 9.0 [6] (🤙..🤞) call me hand..crossed fingers
|
||||
1F91F ; Emoji # 10.0 [1] (🤟) love-you gesture
|
||||
1F920..1F927 ; Emoji # 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face
|
||||
1F928..1F92F ; Emoji # 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
|
||||
1F930 ; Emoji # 9.0 [1] (🤰) pregnant woman
|
||||
1F931..1F932 ; Emoji # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
|
||||
1F933..1F93A ; Emoji # 9.0 [8] (🤳..🤺) selfie..person fencing
|
||||
1F93C..1F93E ; Emoji # 9.0 [3] (🤼..🤾) people wrestling..person playing handball
|
||||
1F93F ; Emoji # 12.0 [1] (🤿) diving mask
|
||||
1F940..1F945 ; Emoji # 9.0 [6] (🥀..🥅) wilted flower..goal net
|
||||
1F947..1F94B ; Emoji # 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
|
||||
1F94C ; Emoji # 10.0 [1] (🥌) curling stone
|
||||
1F94D..1F94F ; Emoji # 11.0 [3] (🥍..🥏) lacrosse..flying disc
|
||||
1F950..1F95E ; Emoji # 9.0 [15] (🥐..🥞) croissant..pancakes
|
||||
1F95F..1F96B ; Emoji # 10.0 [13] (🥟..🥫) dumpling..canned food
|
||||
1F96C..1F970 ; Emoji # 11.0 [5] (🥬..🥰) leafy green..smiling face with hearts
|
||||
1F971 ; Emoji # 12.0 [1] (🥱) yawning face
|
||||
1F973..1F976 ; Emoji # 11.0 [4] (🥳..🥶) partying face..cold face
|
||||
1F97A ; Emoji # 11.0 [1] (🥺) pleading face
|
||||
1F97B ; Emoji # 12.0 [1] (🥻) sari
|
||||
1F97C..1F97F ; Emoji # 11.0 [4] (🥼..🥿) lab coat..flat shoe
|
||||
1F980..1F984 ; Emoji # 8.0 [5] (🦀..🦄) crab..unicorn
|
||||
1F985..1F991 ; Emoji # 9.0 [13] (🦅..🦑) eagle..squid
|
||||
1F992..1F997 ; Emoji # 10.0 [6] (🦒..🦗) giraffe..cricket
|
||||
1F998..1F9A2 ; Emoji # 11.0 [11] (🦘..🦢) kangaroo..swan
|
||||
1F9A5..1F9AA ; Emoji # 12.0 [6] (🦥..🦪) sloth..oyster
|
||||
1F9AE..1F9AF ; Emoji # 12.0 [2] (🦮..🦯) guide dog..probing cane
|
||||
1F9B0..1F9B9 ; Emoji # 11.0 [10] (🦰..🦹) red hair..supervillain
|
||||
1F9BA..1F9BF ; Emoji # 12.0 [6] (🦺..🦿) safety vest..mechanical leg
|
||||
1F9C0 ; Emoji # 8.0 [1] (🧀) cheese wedge
|
||||
1F9C1..1F9C2 ; Emoji # 11.0 [2] (🧁..🧂) cupcake..salt
|
||||
1F9C3..1F9CA ; Emoji # 12.0 [8] (🧃..🧊) beverage box..ice cube
|
||||
1F9CD..1F9CF ; Emoji # 12.0 [3] (🧍..🧏) person standing..deaf person
|
||||
1F9D0..1F9E6 ; Emoji # 10.0 [23] (🧐..🧦) face with monocle..socks
|
||||
1F9E7..1F9FF ; Emoji # 11.0 [25] (🧧..🧿) red envelope..nazar amulet
|
||||
1FA70..1FA73 ; Emoji # 12.0 [4] (🩰..🩳) ballet shoes..shorts
|
||||
1FA78..1FA7A ; Emoji # 12.0 [3] (🩸..🩺) drop of blood..stethoscope
|
||||
1FA80..1FA82 ; Emoji # 12.0 [3] (🪀..🪂) yo-yo..parachute
|
||||
1FA90..1FA95 ; Emoji # 12.0 [6] (🪐..🪕) ringed planet..banjo
|
||||
|
||||
# Total elements: 1311
|
||||
|
||||
# ================================================
|
||||
|
||||
# All omitted code points have Emoji_Presentation=No
|
||||
# @missing: 0000..10FFFF ; Emoji_Presentation ; No
|
||||
|
||||
231A..231B ; Emoji_Presentation # 1.1 [2] (⌚..⌛) watch..hourglass done
|
||||
23E9..23EC ; Emoji_Presentation # 6.0 [4] (⏩..⏬) fast-forward button..fast down button
|
||||
23F0 ; Emoji_Presentation # 6.0 [1] (⏰) alarm clock
|
||||
23F3 ; Emoji_Presentation # 6.0 [1] (⏳) hourglass not done
|
||||
25FD..25FE ; Emoji_Presentation # 3.2 [2] (◽..◾) white medium-small square..black medium-small square
|
||||
2614..2615 ; Emoji_Presentation # 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage
|
||||
2648..2653 ; Emoji_Presentation # 1.1 [12] (♈..♓) Aries..Pisces
|
||||
267F ; Emoji_Presentation # 4.1 [1] (♿) wheelchair symbol
|
||||
2693 ; Emoji_Presentation # 4.1 [1] (⚓) anchor
|
||||
26A1 ; Emoji_Presentation # 4.0 [1] (⚡) high voltage
|
||||
26AA..26AB ; Emoji_Presentation # 4.1 [2] (⚪..⚫) white circle..black circle
|
||||
26BD..26BE ; Emoji_Presentation # 5.2 [2] (⚽..⚾) soccer ball..baseball
|
||||
26C4..26C5 ; Emoji_Presentation # 5.2 [2] (⛄..⛅) snowman without snow..sun behind cloud
|
||||
26CE ; Emoji_Presentation # 6.0 [1] (⛎) Ophiuchus
|
||||
26D4 ; Emoji_Presentation # 5.2 [1] (⛔) no entry
|
||||
26EA ; Emoji_Presentation # 5.2 [1] (⛪) church
|
||||
26F2..26F3 ; Emoji_Presentation # 5.2 [2] (⛲..⛳) fountain..flag in hole
|
||||
26F5 ; Emoji_Presentation # 5.2 [1] (⛵) sailboat
|
||||
26FA ; Emoji_Presentation # 5.2 [1] (⛺) tent
|
||||
26FD ; Emoji_Presentation # 5.2 [1] (⛽) fuel pump
|
||||
2705 ; Emoji_Presentation # 6.0 [1] (✅) check mark button
|
||||
270A..270B ; Emoji_Presentation # 6.0 [2] (✊..✋) raised fist..raised hand
|
||||
2728 ; Emoji_Presentation # 6.0 [1] (✨) sparkles
|
||||
274C ; Emoji_Presentation # 6.0 [1] (❌) cross mark
|
||||
274E ; Emoji_Presentation # 6.0 [1] (❎) cross mark button
|
||||
2753..2755 ; Emoji_Presentation # 6.0 [3] (❓..❕) question mark..white exclamation mark
|
||||
2757 ; Emoji_Presentation # 5.2 [1] (❗) exclamation mark
|
||||
2795..2797 ; Emoji_Presentation # 6.0 [3] (➕..➗) plus sign..division sign
|
||||
27B0 ; Emoji_Presentation # 6.0 [1] (➰) curly loop
|
||||
27BF ; Emoji_Presentation # 6.0 [1] (➿) double curly loop
|
||||
2B1B..2B1C ; Emoji_Presentation # 5.1 [2] (⬛..⬜) black large square..white large square
|
||||
2B50 ; Emoji_Presentation # 5.1 [1] (⭐) star
|
||||
2B55 ; Emoji_Presentation # 5.2 [1] (⭕) hollow red circle
|
||||
1F004 ; Emoji_Presentation # 5.1 [1] (🀄) mahjong red dragon
|
||||
1F0CF ; Emoji_Presentation # 6.0 [1] (🃏) joker
|
||||
1F18E ; Emoji_Presentation # 6.0 [1] (🆎) AB button (blood type)
|
||||
1F191..1F19A ; Emoji_Presentation # 6.0 [10] (🆑..🆚) CL button..VS button
|
||||
1F1E6..1F1FF ; Emoji_Presentation # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
|
||||
1F201 ; Emoji_Presentation # 6.0 [1] (🈁) Japanese “here” button
|
||||
1F21A ; Emoji_Presentation # 5.2 [1] (🈚) Japanese “free of charge” button
|
||||
1F22F ; Emoji_Presentation # 5.2 [1] (🈯) Japanese “reserved” button
|
||||
1F232..1F236 ; Emoji_Presentation # 6.0 [5] (🈲..🈶) Japanese “prohibited” button..Japanese “not free of charge” button
|
||||
1F238..1F23A ; Emoji_Presentation # 6.0 [3] (🈸..🈺) Japanese “application” button..Japanese “open for business” button
|
||||
1F250..1F251 ; Emoji_Presentation # 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
|
||||
1F300..1F320 ; Emoji_Presentation # 6.0 [33] (🌀..🌠) cyclone..shooting star
|
||||
1F32D..1F32F ; Emoji_Presentation # 8.0 [3] (🌭..🌯) hot dog..burrito
|
||||
1F330..1F335 ; Emoji_Presentation # 6.0 [6] (🌰..🌵) chestnut..cactus
|
||||
1F337..1F37C ; Emoji_Presentation # 6.0 [70] (🌷..🍼) tulip..baby bottle
|
||||
1F37E..1F37F ; Emoji_Presentation # 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn
|
||||
1F380..1F393 ; Emoji_Presentation # 6.0 [20] (🎀..🎓) ribbon..graduation cap
|
||||
1F3A0..1F3C4 ; Emoji_Presentation # 6.0 [37] (🎠..🏄) carousel horse..person surfing
|
||||
1F3C5 ; Emoji_Presentation # 7.0 [1] (🏅) sports medal
|
||||
1F3C6..1F3CA ; Emoji_Presentation # 6.0 [5] (🏆..🏊) trophy..person swimming
|
||||
1F3CF..1F3D3 ; Emoji_Presentation # 8.0 [5] (🏏..🏓) cricket game..ping pong
|
||||
1F3E0..1F3F0 ; Emoji_Presentation # 6.0 [17] (🏠..🏰) house..castle
|
||||
1F3F4 ; Emoji_Presentation # 7.0 [1] (🏴) black flag
|
||||
1F3F8..1F3FF ; Emoji_Presentation # 8.0 [8] (🏸..🏿) badminton..dark skin tone
|
||||
1F400..1F43E ; Emoji_Presentation # 6.0 [63] (🐀..🐾) rat..paw prints
|
||||
1F440 ; Emoji_Presentation # 6.0 [1] (👀) eyes
|
||||
1F442..1F4F7 ; Emoji_Presentation # 6.0[182] (👂..📷) ear..camera
|
||||
1F4F8 ; Emoji_Presentation # 7.0 [1] (📸) camera with flash
|
||||
1F4F9..1F4FC ; Emoji_Presentation # 6.0 [4] (📹..📼) video camera..videocassette
|
||||
1F4FF ; Emoji_Presentation # 8.0 [1] (📿) prayer beads
|
||||
1F500..1F53D ; Emoji_Presentation # 6.0 [62] (🔀..🔽) shuffle tracks button..downwards button
|
||||
1F54B..1F54E ; Emoji_Presentation # 8.0 [4] (🕋..🕎) kaaba..menorah
|
||||
1F550..1F567 ; Emoji_Presentation # 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty
|
||||
1F57A ; Emoji_Presentation # 9.0 [1] (🕺) man dancing
|
||||
1F595..1F596 ; Emoji_Presentation # 7.0 [2] (🖕..🖖) middle finger..vulcan salute
|
||||
1F5A4 ; Emoji_Presentation # 9.0 [1] (🖤) black heart
|
||||
1F5FB..1F5FF ; Emoji_Presentation # 6.0 [5] (🗻..🗿) mount fuji..moai
|
||||
1F600 ; Emoji_Presentation # 6.1 [1] (😀) grinning face
|
||||
1F601..1F610 ; Emoji_Presentation # 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face
|
||||
1F611 ; Emoji_Presentation # 6.1 [1] (😑) expressionless face
|
||||
1F612..1F614 ; Emoji_Presentation # 6.0 [3] (😒..😔) unamused face..pensive face
|
||||
1F615 ; Emoji_Presentation # 6.1 [1] (😕) confused face
|
||||
1F616 ; Emoji_Presentation # 6.0 [1] (😖) confounded face
|
||||
1F617 ; Emoji_Presentation # 6.1 [1] (😗) kissing face
|
||||
1F618 ; Emoji_Presentation # 6.0 [1] (😘) face blowing a kiss
|
||||
1F619 ; Emoji_Presentation # 6.1 [1] (😙) kissing face with smiling eyes
|
||||
1F61A ; Emoji_Presentation # 6.0 [1] (😚) kissing face with closed eyes
|
||||
1F61B ; Emoji_Presentation # 6.1 [1] (😛) face with tongue
|
||||
1F61C..1F61E ; Emoji_Presentation # 6.0 [3] (😜..😞) winking face with tongue..disappointed face
|
||||
1F61F ; Emoji_Presentation # 6.1 [1] (😟) worried face
|
||||
1F620..1F625 ; Emoji_Presentation # 6.0 [6] (😠..😥) angry face..sad but relieved face
|
||||
1F626..1F627 ; Emoji_Presentation # 6.1 [2] (😦..😧) frowning face with open mouth..anguished face
|
||||
1F628..1F62B ; Emoji_Presentation # 6.0 [4] (😨..😫) fearful face..tired face
|
||||
1F62C ; Emoji_Presentation # 6.1 [1] (😬) grimacing face
|
||||
1F62D ; Emoji_Presentation # 6.0 [1] (😭) loudly crying face
|
||||
1F62E..1F62F ; Emoji_Presentation # 6.1 [2] (😮..😯) face with open mouth..hushed face
|
||||
1F630..1F633 ; Emoji_Presentation # 6.0 [4] (😰..😳) anxious face with sweat..flushed face
|
||||
1F634 ; Emoji_Presentation # 6.1 [1] (😴) sleeping face
|
||||
1F635..1F640 ; Emoji_Presentation # 6.0 [12] (😵..🙀) dizzy face..weary cat
|
||||
1F641..1F642 ; Emoji_Presentation # 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
|
||||
1F643..1F644 ; Emoji_Presentation # 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
|
||||
1F645..1F64F ; Emoji_Presentation # 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
|
||||
1F680..1F6C5 ; Emoji_Presentation # 6.0 [70] (🚀..🛅) rocket..left luggage
|
||||
1F6CC ; Emoji_Presentation # 7.0 [1] (🛌) person in bed
|
||||
1F6D0 ; Emoji_Presentation # 8.0 [1] (🛐) place of worship
|
||||
1F6D1..1F6D2 ; Emoji_Presentation # 9.0 [2] (🛑..🛒) stop sign..shopping cart
|
||||
1F6D5 ; Emoji_Presentation # 12.0 [1] (🛕) hindu temple
|
||||
1F6EB..1F6EC ; Emoji_Presentation # 7.0 [2] (🛫..🛬) airplane departure..airplane arrival
|
||||
1F6F4..1F6F6 ; Emoji_Presentation # 9.0 [3] (🛴..🛶) kick scooter..canoe
|
||||
1F6F7..1F6F8 ; Emoji_Presentation # 10.0 [2] (🛷..🛸) sled..flying saucer
|
||||
1F6F9 ; Emoji_Presentation # 11.0 [1] (🛹) skateboard
|
||||
1F6FA ; Emoji_Presentation # 12.0 [1] (🛺) auto rickshaw
|
||||
1F7E0..1F7EB ; Emoji_Presentation # 12.0 [12] (🟠..🟫) orange circle..brown square
|
||||
1F90D..1F90F ; Emoji_Presentation # 12.0 [3] (🤍..🤏) white heart..pinching hand
|
||||
1F910..1F918 ; Emoji_Presentation # 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
|
||||
1F919..1F91E ; Emoji_Presentation # 9.0 [6] (🤙..🤞) call me hand..crossed fingers
|
||||
1F91F ; Emoji_Presentation # 10.0 [1] (🤟) love-you gesture
|
||||
1F920..1F927 ; Emoji_Presentation # 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face
|
||||
1F928..1F92F ; Emoji_Presentation # 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
|
||||
1F930 ; Emoji_Presentation # 9.0 [1] (🤰) pregnant woman
|
||||
1F931..1F932 ; Emoji_Presentation # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
|
||||
1F933..1F93A ; Emoji_Presentation # 9.0 [8] (🤳..🤺) selfie..person fencing
|
||||
1F93C..1F93E ; Emoji_Presentation # 9.0 [3] (🤼..🤾) people wrestling..person playing handball
|
||||
1F93F ; Emoji_Presentation # 12.0 [1] (🤿) diving mask
|
||||
1F940..1F945 ; Emoji_Presentation # 9.0 [6] (🥀..🥅) wilted flower..goal net
|
||||
1F947..1F94B ; Emoji_Presentation # 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
|
||||
1F94C ; Emoji_Presentation # 10.0 [1] (🥌) curling stone
|
||||
1F94D..1F94F ; Emoji_Presentation # 11.0 [3] (🥍..🥏) lacrosse..flying disc
|
||||
1F950..1F95E ; Emoji_Presentation # 9.0 [15] (🥐..🥞) croissant..pancakes
|
||||
1F95F..1F96B ; Emoji_Presentation # 10.0 [13] (🥟..🥫) dumpling..canned food
|
||||
1F96C..1F970 ; Emoji_Presentation # 11.0 [5] (🥬..🥰) leafy green..smiling face with hearts
|
||||
1F971 ; Emoji_Presentation # 12.0 [1] (🥱) yawning face
|
||||
1F973..1F976 ; Emoji_Presentation # 11.0 [4] (🥳..🥶) partying face..cold face
|
||||
1F97A ; Emoji_Presentation # 11.0 [1] (🥺) pleading face
|
||||
1F97B ; Emoji_Presentation # 12.0 [1] (🥻) sari
|
||||
1F97C..1F97F ; Emoji_Presentation # 11.0 [4] (🥼..🥿) lab coat..flat shoe
|
||||
1F980..1F984 ; Emoji_Presentation # 8.0 [5] (🦀..🦄) crab..unicorn
|
||||
1F985..1F991 ; Emoji_Presentation # 9.0 [13] (🦅..🦑) eagle..squid
|
||||
1F992..1F997 ; Emoji_Presentation # 10.0 [6] (🦒..🦗) giraffe..cricket
|
||||
1F998..1F9A2 ; Emoji_Presentation # 11.0 [11] (🦘..🦢) kangaroo..swan
|
||||
1F9A5..1F9AA ; Emoji_Presentation # 12.0 [6] (🦥..🦪) sloth..oyster
|
||||
1F9AE..1F9AF ; Emoji_Presentation # 12.0 [2] (🦮..🦯) guide dog..probing cane
|
||||
1F9B0..1F9B9 ; Emoji_Presentation # 11.0 [10] (🦰..🦹) red hair..supervillain
|
||||
1F9BA..1F9BF ; Emoji_Presentation # 12.0 [6] (🦺..🦿) safety vest..mechanical leg
|
||||
1F9C0 ; Emoji_Presentation # 8.0 [1] (🧀) cheese wedge
|
||||
1F9C1..1F9C2 ; Emoji_Presentation # 11.0 [2] (🧁..🧂) cupcake..salt
|
||||
1F9C3..1F9CA ; Emoji_Presentation # 12.0 [8] (🧃..🧊) beverage box..ice cube
|
||||
1F9CD..1F9CF ; Emoji_Presentation # 12.0 [3] (🧍..🧏) person standing..deaf person
|
||||
1F9D0..1F9E6 ; Emoji_Presentation # 10.0 [23] (🧐..🧦) face with monocle..socks
|
||||
1F9E7..1F9FF ; Emoji_Presentation # 11.0 [25] (🧧..🧿) red envelope..nazar amulet
|
||||
1FA70..1FA73 ; Emoji_Presentation # 12.0 [4] (🩰..🩳) ballet shoes..shorts
|
||||
1FA78..1FA7A ; Emoji_Presentation # 12.0 [3] (🩸..🩺) drop of blood..stethoscope
|
||||
1FA80..1FA82 ; Emoji_Presentation # 12.0 [3] (🪀..🪂) yo-yo..parachute
|
||||
1FA90..1FA95 ; Emoji_Presentation # 12.0 [6] (🪐..🪕) ringed planet..banjo
|
||||
|
||||
# Total elements: 1093
|
||||
|
||||
# ================================================
|
||||
|
||||
# All omitted code points have Emoji_Modifier=No
|
||||
# @missing: 0000..10FFFF ; Emoji_Modifier ; No
|
||||
|
||||
1F3FB..1F3FF ; Emoji_Modifier # 8.0 [5] (🏻..🏿) light skin tone..dark skin tone
|
||||
|
||||
# Total elements: 5
|
||||
|
||||
# ================================================
|
||||
|
||||
# All omitted code points have Emoji_Modifier_Base=No
|
||||
# @missing: 0000..10FFFF ; Emoji_Modifier_Base ; No
|
||||
|
||||
261D ; Emoji_Modifier_Base # 1.1 [1] (☝️) index pointing up
|
||||
26F9 ; Emoji_Modifier_Base # 5.2 [1] (⛹️) person bouncing ball
|
||||
270A..270B ; Emoji_Modifier_Base # 6.0 [2] (✊..✋) raised fist..raised hand
|
||||
270C..270D ; Emoji_Modifier_Base # 1.1 [2] (✌️..✍️) victory hand..writing hand
|
||||
1F385 ; Emoji_Modifier_Base # 6.0 [1] (🎅) Santa Claus
|
||||
1F3C2..1F3C4 ; Emoji_Modifier_Base # 6.0 [3] (🏂..🏄) snowboarder..person surfing
|
||||
1F3C7 ; Emoji_Modifier_Base # 6.0 [1] (🏇) horse racing
|
||||
1F3CA ; Emoji_Modifier_Base # 6.0 [1] (🏊) person swimming
|
||||
1F3CB..1F3CC ; Emoji_Modifier_Base # 7.0 [2] (🏋️..🏌️) person lifting weights..person golfing
|
||||
1F442..1F443 ; Emoji_Modifier_Base # 6.0 [2] (👂..👃) ear..nose
|
||||
1F446..1F450 ; Emoji_Modifier_Base # 6.0 [11] (👆..👐) backhand index pointing up..open hands
|
||||
1F466..1F478 ; Emoji_Modifier_Base # 6.0 [19] (👦..👸) boy..princess
|
||||
1F47C ; Emoji_Modifier_Base # 6.0 [1] (👼) baby angel
|
||||
1F481..1F483 ; Emoji_Modifier_Base # 6.0 [3] (💁..💃) person tipping hand..woman dancing
|
||||
1F485..1F487 ; Emoji_Modifier_Base # 6.0 [3] (💅..💇) nail polish..person getting haircut
|
||||
1F48F ; Emoji_Modifier_Base # 6.0 [1] (💏) kiss
|
||||
1F491 ; Emoji_Modifier_Base # 6.0 [1] (💑) couple with heart
|
||||
1F4AA ; Emoji_Modifier_Base # 6.0 [1] (💪) flexed biceps
|
||||
1F574..1F575 ; Emoji_Modifier_Base # 7.0 [2] (🕴️..🕵️) man in suit levitating..detective
|
||||
1F57A ; Emoji_Modifier_Base # 9.0 [1] (🕺) man dancing
|
||||
1F590 ; Emoji_Modifier_Base # 7.0 [1] (🖐️) hand with fingers splayed
|
||||
1F595..1F596 ; Emoji_Modifier_Base # 7.0 [2] (🖕..🖖) middle finger..vulcan salute
|
||||
1F645..1F647 ; Emoji_Modifier_Base # 6.0 [3] (🙅..🙇) person gesturing NO..person bowing
|
||||
1F64B..1F64F ; Emoji_Modifier_Base # 6.0 [5] (🙋..🙏) person raising hand..folded hands
|
||||
1F6A3 ; Emoji_Modifier_Base # 6.0 [1] (🚣) person rowing boat
|
||||
1F6B4..1F6B6 ; Emoji_Modifier_Base # 6.0 [3] (🚴..🚶) person biking..person walking
|
||||
1F6C0 ; Emoji_Modifier_Base # 6.0 [1] (🛀) person taking bath
|
||||
1F6CC ; Emoji_Modifier_Base # 7.0 [1] (🛌) person in bed
|
||||
1F90F ; Emoji_Modifier_Base # 12.0 [1] (🤏) pinching hand
|
||||
1F918 ; Emoji_Modifier_Base # 8.0 [1] (🤘) sign of the horns
|
||||
1F919..1F91E ; Emoji_Modifier_Base # 9.0 [6] (🤙..🤞) call me hand..crossed fingers
|
||||
1F91F ; Emoji_Modifier_Base # 10.0 [1] (🤟) love-you gesture
|
||||
1F926 ; Emoji_Modifier_Base # 9.0 [1] (🤦) person facepalming
|
||||
1F930 ; Emoji_Modifier_Base # 9.0 [1] (🤰) pregnant woman
|
||||
1F931..1F932 ; Emoji_Modifier_Base # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
|
||||
1F933..1F939 ; Emoji_Modifier_Base # 9.0 [7] (🤳..🤹) selfie..person juggling
|
||||
1F93C..1F93E ; Emoji_Modifier_Base # 9.0 [3] (🤼..🤾) people wrestling..person playing handball
|
||||
1F9B5..1F9B6 ; Emoji_Modifier_Base # 11.0 [2] (🦵..🦶) leg..foot
|
||||
1F9B8..1F9B9 ; Emoji_Modifier_Base # 11.0 [2] (🦸..🦹) superhero..supervillain
|
||||
1F9BB ; Emoji_Modifier_Base # 12.0 [1] (🦻) ear with hearing aid
|
||||
1F9CD..1F9CF ; Emoji_Modifier_Base # 12.0 [3] (🧍..🧏) person standing..deaf person
|
||||
1F9D1..1F9DD ; Emoji_Modifier_Base # 10.0 [13] (🧑..🧝) person..elf
|
||||
|
||||
# Total elements: 120
|
||||
|
||||
# ================================================
|
||||
|
||||
# All omitted code points have Emoji_Component=No
|
||||
# @missing: 0000..10FFFF ; Emoji_Component ; No
|
||||
|
||||
0023 ; Emoji_Component # 1.1 [1] (#️) number sign
|
||||
002A ; Emoji_Component # 1.1 [1] (*️) asterisk
|
||||
0030..0039 ; Emoji_Component # 1.1 [10] (0️..9️) digit zero..digit nine
|
||||
200D ; Emoji_Component # 1.1 [1] () zero width joiner
|
||||
20E3 ; Emoji_Component # 3.0 [1] (⃣) combining enclosing keycap
|
||||
FE0F ; Emoji_Component # 3.2 [1] () VARIATION SELECTOR-16
|
||||
1F1E6..1F1FF ; Emoji_Component # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
|
||||
1F3FB..1F3FF ; Emoji_Component # 8.0 [5] (🏻..🏿) light skin tone..dark skin tone
|
||||
1F9B0..1F9B3 ; Emoji_Component # 11.0 [4] (🦰..🦳) red hair..white hair
|
||||
E0020..E007F ; Emoji_Component # 3.1 [96] (..) tag space..cancel tag
|
||||
|
||||
# Total elements: 146
|
||||
|
||||
# ================================================
|
||||
|
||||
# All omitted code points have Extended_Pictographic=No
|
||||
# @missing: 0000..10FFFF ; Extended_Pictographic ; No
|
||||
|
||||
00A9 ; Extended_Pictographic# 1.1 [1] (©️) copyright
|
||||
00AE ; Extended_Pictographic# 1.1 [1] (®️) registered
|
||||
203C ; Extended_Pictographic# 1.1 [1] (‼️) double exclamation mark
|
||||
2049 ; Extended_Pictographic# 3.0 [1] (⁉️) exclamation question mark
|
||||
2122 ; Extended_Pictographic# 1.1 [1] (™️) trade mark
|
||||
2139 ; Extended_Pictographic# 3.0 [1] (ℹ️) information
|
||||
2194..2199 ; Extended_Pictographic# 1.1 [6] (↔️..↙️) left-right arrow..down-left arrow
|
||||
21A9..21AA ; Extended_Pictographic# 1.1 [2] (↩️..↪️) right arrow curving left..left arrow curving right
|
||||
231A..231B ; Extended_Pictographic# 1.1 [2] (⌚..⌛) watch..hourglass done
|
||||
2328 ; Extended_Pictographic# 1.1 [1] (⌨️) keyboard
|
||||
2388 ; Extended_Pictographic# 3.0 [1] (⎈) HELM SYMBOL
|
||||
23CF ; Extended_Pictographic# 4.0 [1] (⏏️) eject button
|
||||
23E9..23F3 ; Extended_Pictographic# 6.0 [11] (⏩..⏳) fast-forward button..hourglass not done
|
||||
23F8..23FA ; Extended_Pictographic# 7.0 [3] (⏸️..⏺️) pause button..record button
|
||||
24C2 ; Extended_Pictographic# 1.1 [1] (Ⓜ️) circled M
|
||||
25AA..25AB ; Extended_Pictographic# 1.1 [2] (▪️..▫️) black small square..white small square
|
||||
25B6 ; Extended_Pictographic# 1.1 [1] (▶️) play button
|
||||
25C0 ; Extended_Pictographic# 1.1 [1] (◀️) reverse button
|
||||
25FB..25FE ; Extended_Pictographic# 3.2 [4] (◻️..◾) white medium square..black medium-small square
|
||||
2600..2605 ; Extended_Pictographic# 1.1 [6] (☀️..★) sun..BLACK STAR
|
||||
2607..2612 ; Extended_Pictographic# 1.1 [12] (☇..☒) LIGHTNING..BALLOT BOX WITH X
|
||||
2614..2615 ; Extended_Pictographic# 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage
|
||||
2616..2617 ; Extended_Pictographic# 3.2 [2] (☖..☗) WHITE SHOGI PIECE..BLACK SHOGI PIECE
|
||||
2618 ; Extended_Pictographic# 4.1 [1] (☘️) shamrock
|
||||
2619 ; Extended_Pictographic# 3.0 [1] (☙) REVERSED ROTATED FLORAL HEART BULLET
|
||||
261A..266F ; Extended_Pictographic# 1.1 [86] (☚..♯) BLACK LEFT POINTING INDEX..MUSIC SHARP SIGN
|
||||
2670..2671 ; Extended_Pictographic# 3.0 [2] (♰..♱) WEST SYRIAC CROSS..EAST SYRIAC CROSS
|
||||
2672..267D ; Extended_Pictographic# 3.2 [12] (♲..♽) UNIVERSAL RECYCLING SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL
|
||||
267E..267F ; Extended_Pictographic# 4.1 [2] (♾️..♿) infinity..wheelchair symbol
|
||||
2680..2685 ; Extended_Pictographic# 3.2 [6] (⚀..⚅) DIE FACE-1..DIE FACE-6
|
||||
2690..2691 ; Extended_Pictographic# 4.0 [2] (⚐..⚑) WHITE FLAG..BLACK FLAG
|
||||
2692..269C ; Extended_Pictographic# 4.1 [11] (⚒️..⚜️) hammer and pick..fleur-de-lis
|
||||
269D ; Extended_Pictographic# 5.1 [1] (⚝) OUTLINED WHITE STAR
|
||||
269E..269F ; Extended_Pictographic# 5.2 [2] (⚞..⚟) THREE LINES CONVERGING RIGHT..THREE LINES CONVERGING LEFT
|
||||
26A0..26A1 ; Extended_Pictographic# 4.0 [2] (⚠️..⚡) warning..high voltage
|
||||
26A2..26B1 ; Extended_Pictographic# 4.1 [16] (⚢..⚱️) DOUBLED FEMALE SIGN..funeral urn
|
||||
26B2 ; Extended_Pictographic# 5.0 [1] (⚲) NEUTER
|
||||
26B3..26BC ; Extended_Pictographic# 5.1 [10] (⚳..⚼) CERES..SESQUIQUADRATE
|
||||
26BD..26BF ; Extended_Pictographic# 5.2 [3] (⚽..⚿) soccer ball..SQUARED KEY
|
||||
26C0..26C3 ; Extended_Pictographic# 5.1 [4] (⛀..⛃) WHITE DRAUGHTS MAN..BLACK DRAUGHTS KING
|
||||
26C4..26CD ; Extended_Pictographic# 5.2 [10] (⛄..⛍) snowman without snow..DISABLED CAR
|
||||
26CE ; Extended_Pictographic# 6.0 [1] (⛎) Ophiuchus
|
||||
26CF..26E1 ; Extended_Pictographic# 5.2 [19] (⛏️..⛡) pick..RESTRICTED LEFT ENTRY-2
|
||||
26E2 ; Extended_Pictographic# 6.0 [1] (⛢) ASTRONOMICAL SYMBOL FOR URANUS
|
||||
26E3 ; Extended_Pictographic# 5.2 [1] (⛣) HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE
|
||||
26E4..26E7 ; Extended_Pictographic# 6.0 [4] (⛤..⛧) PENTAGRAM..INVERTED PENTAGRAM
|
||||
26E8..26FF ; Extended_Pictographic# 5.2 [24] (⛨..⛿) BLACK CROSS ON SHIELD..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE
|
||||
2700 ; Extended_Pictographic# 7.0 [1] (✀) BLACK SAFETY SCISSORS
|
||||
2701..2704 ; Extended_Pictographic# 1.1 [4] (✁..✄) UPPER BLADE SCISSORS..WHITE SCISSORS
|
||||
2705 ; Extended_Pictographic# 6.0 [1] (✅) check mark button
|
||||
2708..2709 ; Extended_Pictographic# 1.1 [2] (✈️..✉️) airplane..envelope
|
||||
270A..270B ; Extended_Pictographic# 6.0 [2] (✊..✋) raised fist..raised hand
|
||||
270C..2712 ; Extended_Pictographic# 1.1 [7] (✌️..✒️) victory hand..black nib
|
||||
2714 ; Extended_Pictographic# 1.1 [1] (✔️) check mark
|
||||
2716 ; Extended_Pictographic# 1.1 [1] (✖️) multiplication sign
|
||||
271D ; Extended_Pictographic# 1.1 [1] (✝️) latin cross
|
||||
2721 ; Extended_Pictographic# 1.1 [1] (✡️) star of David
|
||||
2728 ; Extended_Pictographic# 6.0 [1] (✨) sparkles
|
||||
2733..2734 ; Extended_Pictographic# 1.1 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star
|
||||
2744 ; Extended_Pictographic# 1.1 [1] (❄️) snowflake
|
||||
2747 ; Extended_Pictographic# 1.1 [1] (❇️) sparkle
|
||||
274C ; Extended_Pictographic# 6.0 [1] (❌) cross mark
|
||||
274E ; Extended_Pictographic# 6.0 [1] (❎) cross mark button
|
||||
2753..2755 ; Extended_Pictographic# 6.0 [3] (❓..❕) question mark..white exclamation mark
|
||||
2757 ; Extended_Pictographic# 5.2 [1] (❗) exclamation mark
|
||||
2763..2767 ; Extended_Pictographic# 1.1 [5] (❣️..❧) heart exclamation..ROTATED FLORAL HEART BULLET
|
||||
2795..2797 ; Extended_Pictographic# 6.0 [3] (➕..➗) plus sign..division sign
|
||||
27A1 ; Extended_Pictographic# 1.1 [1] (➡️) right arrow
|
||||
27B0 ; Extended_Pictographic# 6.0 [1] (➰) curly loop
|
||||
27BF ; Extended_Pictographic# 6.0 [1] (➿) double curly loop
|
||||
2934..2935 ; Extended_Pictographic# 3.2 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down
|
||||
2B05..2B07 ; Extended_Pictographic# 4.0 [3] (⬅️..⬇️) left arrow..down arrow
|
||||
2B1B..2B1C ; Extended_Pictographic# 5.1 [2] (⬛..⬜) black large square..white large square
|
||||
2B50 ; Extended_Pictographic# 5.1 [1] (⭐) star
|
||||
2B55 ; Extended_Pictographic# 5.2 [1] (⭕) hollow red circle
|
||||
3030 ; Extended_Pictographic# 1.1 [1] (〰️) wavy dash
|
||||
303D ; Extended_Pictographic# 3.2 [1] (〽️) part alternation mark
|
||||
3297 ; Extended_Pictographic# 1.1 [1] (㊗️) Japanese “congratulations” button
|
||||
3299 ; Extended_Pictographic# 1.1 [1] (㊙️) Japanese “secret” button
|
||||
1F000..1F02B ; Extended_Pictographic# 5.1 [44] (🀀..🀫) MAHJONG TILE EAST WIND..MAHJONG TILE BACK
|
||||
1F02C..1F02F ; Extended_Pictographic# NA [4] (..) <reserved-1F02C>..<reserved-1F02F>
|
||||
1F030..1F093 ; Extended_Pictographic# 5.1[100] (🀰..🂓) DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06
|
||||
1F094..1F09F ; Extended_Pictographic# NA [12] (..) <reserved-1F094>..<reserved-1F09F>
|
||||
1F0A0..1F0AE ; Extended_Pictographic# 6.0 [15] (🂠..🂮) PLAYING CARD BACK..PLAYING CARD KING OF SPADES
|
||||
1F0AF..1F0B0 ; Extended_Pictographic# NA [2] (..) <reserved-1F0AF>..<reserved-1F0B0>
|
||||
1F0B1..1F0BE ; Extended_Pictographic# 6.0 [14] (🂱..🂾) PLAYING CARD ACE OF HEARTS..PLAYING CARD KING OF HEARTS
|
||||
1F0BF ; Extended_Pictographic# 7.0 [1] (🂿) PLAYING CARD RED JOKER
|
||||
1F0C0 ; Extended_Pictographic# NA [1] () <reserved-1F0C0>
|
||||
1F0C1..1F0CF ; Extended_Pictographic# 6.0 [15] (🃁..🃏) PLAYING CARD ACE OF DIAMONDS..joker
|
||||
1F0D0 ; Extended_Pictographic# NA [1] () <reserved-1F0D0>
|
||||
1F0D1..1F0DF ; Extended_Pictographic# 6.0 [15] (🃑..🃟) PLAYING CARD ACE OF CLUBS..PLAYING CARD WHITE JOKER
|
||||
1F0E0..1F0F5 ; Extended_Pictographic# 7.0 [22] (🃠..🃵) PLAYING CARD FOOL..PLAYING CARD TRUMP-21
|
||||
1F0F6..1F0FF ; Extended_Pictographic# NA [10] (..) <reserved-1F0F6>..<reserved-1F0FF>
|
||||
1F10D..1F10F ; Extended_Pictographic# NA [3] (🄍..🄏) <reserved-1F10D>..<reserved-1F10F>
|
||||
1F12F ; Extended_Pictographic# 11.0 [1] (🄯) COPYLEFT SYMBOL
|
||||
1F16C ; Extended_Pictographic# 12.0 [1] (🅬) RAISED MR SIGN
|
||||
1F16D..1F16F ; Extended_Pictographic# NA [3] (🅭..🅯) <reserved-1F16D>..<reserved-1F16F>
|
||||
1F170..1F171 ; Extended_Pictographic# 6.0 [2] (🅰️..🅱️) A button (blood type)..B button (blood type)
|
||||
1F17E ; Extended_Pictographic# 6.0 [1] (🅾️) O button (blood type)
|
||||
1F17F ; Extended_Pictographic# 5.2 [1] (🅿️) P button
|
||||
1F18E ; Extended_Pictographic# 6.0 [1] (🆎) AB button (blood type)
|
||||
1F191..1F19A ; Extended_Pictographic# 6.0 [10] (🆑..🆚) CL button..VS button
|
||||
1F1AD..1F1E5 ; Extended_Pictographic# NA [57] (🆭..) <reserved-1F1AD>..<reserved-1F1E5>
|
||||
1F201..1F202 ; Extended_Pictographic# 6.0 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button
|
||||
1F203..1F20F ; Extended_Pictographic# NA [13] (..) <reserved-1F203>..<reserved-1F20F>
|
||||
1F21A ; Extended_Pictographic# 5.2 [1] (🈚) Japanese “free of charge” button
|
||||
1F22F ; Extended_Pictographic# 5.2 [1] (🈯) Japanese “reserved” button
|
||||
1F232..1F23A ; Extended_Pictographic# 6.0 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button
|
||||
1F23C..1F23F ; Extended_Pictographic# NA [4] (..) <reserved-1F23C>..<reserved-1F23F>
|
||||
1F249..1F24F ; Extended_Pictographic# NA [7] (..) <reserved-1F249>..<reserved-1F24F>
|
||||
1F250..1F251 ; Extended_Pictographic# 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
|
||||
1F252..1F25F ; Extended_Pictographic# NA [14] (..) <reserved-1F252>..<reserved-1F25F>
|
||||
1F260..1F265 ; Extended_Pictographic# 10.0 [6] (🉠..🉥) ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI
|
||||
1F266..1F2FF ; Extended_Pictographic# NA[154] (..) <reserved-1F266>..<reserved-1F2FF>
|
||||
1F300..1F320 ; Extended_Pictographic# 6.0 [33] (🌀..🌠) cyclone..shooting star
|
||||
1F321..1F32C ; Extended_Pictographic# 7.0 [12] (🌡️..🌬️) thermometer..wind face
|
||||
1F32D..1F32F ; Extended_Pictographic# 8.0 [3] (🌭..🌯) hot dog..burrito
|
||||
1F330..1F335 ; Extended_Pictographic# 6.0 [6] (🌰..🌵) chestnut..cactus
|
||||
1F336 ; Extended_Pictographic# 7.0 [1] (🌶️) hot pepper
|
||||
1F337..1F37C ; Extended_Pictographic# 6.0 [70] (🌷..🍼) tulip..baby bottle
|
||||
1F37D ; Extended_Pictographic# 7.0 [1] (🍽️) fork and knife with plate
|
||||
1F37E..1F37F ; Extended_Pictographic# 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn
|
||||
1F380..1F393 ; Extended_Pictographic# 6.0 [20] (🎀..🎓) ribbon..graduation cap
|
||||
1F394..1F39F ; Extended_Pictographic# 7.0 [12] (🎔..🎟️) HEART WITH TIP ON THE LEFT..admission tickets
|
||||
1F3A0..1F3C4 ; Extended_Pictographic# 6.0 [37] (🎠..🏄) carousel horse..person surfing
|
||||
1F3C5 ; Extended_Pictographic# 7.0 [1] (🏅) sports medal
|
||||
1F3C6..1F3CA ; Extended_Pictographic# 6.0 [5] (🏆..🏊) trophy..person swimming
|
||||
1F3CB..1F3CE ; Extended_Pictographic# 7.0 [4] (🏋️..🏎️) person lifting weights..racing car
|
||||
1F3CF..1F3D3 ; Extended_Pictographic# 8.0 [5] (🏏..🏓) cricket game..ping pong
|
||||
1F3D4..1F3DF ; Extended_Pictographic# 7.0 [12] (🏔️..🏟️) snow-capped mountain..stadium
|
||||
1F3E0..1F3F0 ; Extended_Pictographic# 6.0 [17] (🏠..🏰) house..castle
|
||||
1F3F1..1F3F7 ; Extended_Pictographic# 7.0 [7] (🏱..🏷️) WHITE PENNANT..label
|
||||
1F3F8..1F3FA ; Extended_Pictographic# 8.0 [3] (🏸..🏺) badminton..amphora
|
||||
1F400..1F43E ; Extended_Pictographic# 6.0 [63] (🐀..🐾) rat..paw prints
|
||||
1F43F ; Extended_Pictographic# 7.0 [1] (🐿️) chipmunk
|
||||
1F440 ; Extended_Pictographic# 6.0 [1] (👀) eyes
|
||||
1F441 ; Extended_Pictographic# 7.0 [1] (👁️) eye
|
||||
1F442..1F4F7 ; Extended_Pictographic# 6.0[182] (👂..📷) ear..camera
|
||||
1F4F8 ; Extended_Pictographic# 7.0 [1] (📸) camera with flash
|
||||
1F4F9..1F4FC ; Extended_Pictographic# 6.0 [4] (📹..📼) video camera..videocassette
|
||||
1F4FD..1F4FE ; Extended_Pictographic# 7.0 [2] (📽️..📾) film projector..PORTABLE STEREO
|
||||
1F4FF ; Extended_Pictographic# 8.0 [1] (📿) prayer beads
|
||||
1F500..1F53D ; Extended_Pictographic# 6.0 [62] (🔀..🔽) shuffle tracks button..downwards button
|
||||
1F546..1F54A ; Extended_Pictographic# 7.0 [5] (🕆..🕊️) WHITE LATIN CROSS..dove
|
||||
1F54B..1F54F ; Extended_Pictographic# 8.0 [5] (🕋..🕏) kaaba..BOWL OF HYGIEIA
|
||||
1F550..1F567 ; Extended_Pictographic# 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty
|
||||
1F568..1F579 ; Extended_Pictographic# 7.0 [18] (🕨..🕹️) RIGHT SPEAKER..joystick
|
||||
1F57A ; Extended_Pictographic# 9.0 [1] (🕺) man dancing
|
||||
1F57B..1F5A3 ; Extended_Pictographic# 7.0 [41] (🕻..🖣) LEFT HAND TELEPHONE RECEIVER..BLACK DOWN POINTING BACKHAND INDEX
|
||||
1F5A4 ; Extended_Pictographic# 9.0 [1] (🖤) black heart
|
||||
1F5A5..1F5FA ; Extended_Pictographic# 7.0 [86] (🖥️..🗺️) desktop computer..world map
|
||||
1F5FB..1F5FF ; Extended_Pictographic# 6.0 [5] (🗻..🗿) mount fuji..moai
|
||||
1F600 ; Extended_Pictographic# 6.1 [1] (😀) grinning face
|
||||
1F601..1F610 ; Extended_Pictographic# 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face
|
||||
1F611 ; Extended_Pictographic# 6.1 [1] (😑) expressionless face
|
||||
1F612..1F614 ; Extended_Pictographic# 6.0 [3] (😒..😔) unamused face..pensive face
|
||||
1F615 ; Extended_Pictographic# 6.1 [1] (😕) confused face
|
||||
1F616 ; Extended_Pictographic# 6.0 [1] (😖) confounded face
|
||||
1F617 ; Extended_Pictographic# 6.1 [1] (😗) kissing face
|
||||
1F618 ; Extended_Pictographic# 6.0 [1] (😘) face blowing a kiss
|
||||
1F619 ; Extended_Pictographic# 6.1 [1] (😙) kissing face with smiling eyes
|
||||
1F61A ; Extended_Pictographic# 6.0 [1] (😚) kissing face with closed eyes
|
||||
1F61B ; Extended_Pictographic# 6.1 [1] (😛) face with tongue
|
||||
1F61C..1F61E ; Extended_Pictographic# 6.0 [3] (😜..😞) winking face with tongue..disappointed face
|
||||
1F61F ; Extended_Pictographic# 6.1 [1] (😟) worried face
|
||||
1F620..1F625 ; Extended_Pictographic# 6.0 [6] (😠..😥) angry face..sad but relieved face
|
||||
1F626..1F627 ; Extended_Pictographic# 6.1 [2] (😦..😧) frowning face with open mouth..anguished face
|
||||
1F628..1F62B ; Extended_Pictographic# 6.0 [4] (😨..😫) fearful face..tired face
|
||||
1F62C ; Extended_Pictographic# 6.1 [1] (😬) grimacing face
|
||||
1F62D ; Extended_Pictographic# 6.0 [1] (😭) loudly crying face
|
||||
1F62E..1F62F ; Extended_Pictographic# 6.1 [2] (😮..😯) face with open mouth..hushed face
|
||||
1F630..1F633 ; Extended_Pictographic# 6.0 [4] (😰..😳) anxious face with sweat..flushed face
|
||||
1F634 ; Extended_Pictographic# 6.1 [1] (😴) sleeping face
|
||||
1F635..1F640 ; Extended_Pictographic# 6.0 [12] (😵..🙀) dizzy face..weary cat
|
||||
1F641..1F642 ; Extended_Pictographic# 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
|
||||
1F643..1F644 ; Extended_Pictographic# 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
|
||||
1F645..1F64F ; Extended_Pictographic# 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
|
||||
1F680..1F6C5 ; Extended_Pictographic# 6.0 [70] (🚀..🛅) rocket..left luggage
|
||||
1F6C6..1F6CF ; Extended_Pictographic# 7.0 [10] (🛆..🛏️) TRIANGLE WITH ROUNDED CORNERS..bed
|
||||
1F6D0 ; Extended_Pictographic# 8.0 [1] (🛐) place of worship
|
||||
1F6D1..1F6D2 ; Extended_Pictographic# 9.0 [2] (🛑..🛒) stop sign..shopping cart
|
||||
1F6D3..1F6D4 ; Extended_Pictographic# 10.0 [2] (🛓..🛔) STUPA..PAGODA
|
||||
1F6D5 ; Extended_Pictographic# 12.0 [1] (🛕) hindu temple
|
||||
1F6D6..1F6DF ; Extended_Pictographic# NA [10] (🛖..🛟) <reserved-1F6D6>..<reserved-1F6DF>
|
||||
1F6E0..1F6EC ; Extended_Pictographic# 7.0 [13] (🛠️..🛬) hammer and wrench..airplane arrival
|
||||
1F6ED..1F6EF ; Extended_Pictographic# NA [3] (..) <reserved-1F6ED>..<reserved-1F6EF>
|
||||
1F6F0..1F6F3 ; Extended_Pictographic# 7.0 [4] (🛰️..🛳️) satellite..passenger ship
|
||||
1F6F4..1F6F6 ; Extended_Pictographic# 9.0 [3] (🛴..🛶) kick scooter..canoe
|
||||
1F6F7..1F6F8 ; Extended_Pictographic# 10.0 [2] (🛷..🛸) sled..flying saucer
|
||||
1F6F9 ; Extended_Pictographic# 11.0 [1] (🛹) skateboard
|
||||
1F6FA ; Extended_Pictographic# 12.0 [1] (🛺) auto rickshaw
|
||||
1F6FB..1F6FF ; Extended_Pictographic# NA [5] (🛻..) <reserved-1F6FB>..<reserved-1F6FF>
|
||||
1F774..1F77F ; Extended_Pictographic# NA [12] (🝴..🝿) <reserved-1F774>..<reserved-1F77F>
|
||||
1F7D5..1F7D8 ; Extended_Pictographic# 11.0 [4] (🟕..🟘) CIRCLED TRIANGLE..NEGATIVE CIRCLED SQUARE
|
||||
1F7D9..1F7DF ; Extended_Pictographic# NA [7] (🟙..) <reserved-1F7D9>..<reserved-1F7DF>
|
||||
1F7E0..1F7EB ; Extended_Pictographic# 12.0 [12] (🟠..🟫) orange circle..brown square
|
||||
1F7EC..1F7FF ; Extended_Pictographic# NA [20] (..) <reserved-1F7EC>..<reserved-1F7FF>
|
||||
1F80C..1F80F ; Extended_Pictographic# NA [4] (..) <reserved-1F80C>..<reserved-1F80F>
|
||||
1F848..1F84F ; Extended_Pictographic# NA [8] (..) <reserved-1F848>..<reserved-1F84F>
|
||||
1F85A..1F85F ; Extended_Pictographic# NA [6] (..) <reserved-1F85A>..<reserved-1F85F>
|
||||
1F888..1F88F ; Extended_Pictographic# NA [8] (..) <reserved-1F888>..<reserved-1F88F>
|
||||
1F8AE..1F8FF ; Extended_Pictographic# NA [82] (..) <reserved-1F8AE>..<reserved-1F8FF>
|
||||
1F90C ; Extended_Pictographic# NA [1] (🤌) <reserved-1F90C>
|
||||
1F90D..1F90F ; Extended_Pictographic# 12.0 [3] (🤍..🤏) white heart..pinching hand
|
||||
1F910..1F918 ; Extended_Pictographic# 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
|
||||
1F919..1F91E ; Extended_Pictographic# 9.0 [6] (🤙..🤞) call me hand..crossed fingers
|
||||
1F91F ; Extended_Pictographic# 10.0 [1] (🤟) love-you gesture
|
||||
1F920..1F927 ; Extended_Pictographic# 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face
|
||||
1F928..1F92F ; Extended_Pictographic# 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
|
||||
1F930 ; Extended_Pictographic# 9.0 [1] (🤰) pregnant woman
|
||||
1F931..1F932 ; Extended_Pictographic# 10.0 [2] (🤱..🤲) breast-feeding..palms up together
|
||||
1F933..1F93A ; Extended_Pictographic# 9.0 [8] (🤳..🤺) selfie..person fencing
|
||||
1F93C..1F93E ; Extended_Pictographic# 9.0 [3] (🤼..🤾) people wrestling..person playing handball
|
||||
1F93F ; Extended_Pictographic# 12.0 [1] (🤿) diving mask
|
||||
1F940..1F945 ; Extended_Pictographic# 9.0 [6] (🥀..🥅) wilted flower..goal net
|
||||
1F947..1F94B ; Extended_Pictographic# 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
|
||||
1F94C ; Extended_Pictographic# 10.0 [1] (🥌) curling stone
|
||||
1F94D..1F94F ; Extended_Pictographic# 11.0 [3] (🥍..🥏) lacrosse..flying disc
|
||||
1F950..1F95E ; Extended_Pictographic# 9.0 [15] (🥐..🥞) croissant..pancakes
|
||||
1F95F..1F96B ; Extended_Pictographic# 10.0 [13] (🥟..🥫) dumpling..canned food
|
||||
1F96C..1F970 ; Extended_Pictographic# 11.0 [5] (🥬..🥰) leafy green..smiling face with hearts
|
||||
1F971 ; Extended_Pictographic# 12.0 [1] (🥱) yawning face
|
||||
1F972 ; Extended_Pictographic# NA [1] (🥲) <reserved-1F972>
|
||||
1F973..1F976 ; Extended_Pictographic# 11.0 [4] (🥳..🥶) partying face..cold face
|
||||
1F977..1F979 ; Extended_Pictographic# NA [3] (🥷..🥹) <reserved-1F977>..<reserved-1F979>
|
||||
1F97A ; Extended_Pictographic# 11.0 [1] (🥺) pleading face
|
||||
1F97B ; Extended_Pictographic# 12.0 [1] (🥻) sari
|
||||
1F97C..1F97F ; Extended_Pictographic# 11.0 [4] (🥼..🥿) lab coat..flat shoe
|
||||
1F980..1F984 ; Extended_Pictographic# 8.0 [5] (🦀..🦄) crab..unicorn
|
||||
1F985..1F991 ; Extended_Pictographic# 9.0 [13] (🦅..🦑) eagle..squid
|
||||
1F992..1F997 ; Extended_Pictographic# 10.0 [6] (🦒..🦗) giraffe..cricket
|
||||
1F998..1F9A2 ; Extended_Pictographic# 11.0 [11] (🦘..🦢) kangaroo..swan
|
||||
1F9A3..1F9A4 ; Extended_Pictographic# NA [2] (🦣..🦤) <reserved-1F9A3>..<reserved-1F9A4>
|
||||
1F9A5..1F9AA ; Extended_Pictographic# 12.0 [6] (🦥..🦪) sloth..oyster
|
||||
1F9AB..1F9AD ; Extended_Pictographic# NA [3] (🦫..🦭) <reserved-1F9AB>..<reserved-1F9AD>
|
||||
1F9AE..1F9AF ; Extended_Pictographic# 12.0 [2] (🦮..🦯) guide dog..probing cane
|
||||
1F9B0..1F9B9 ; Extended_Pictographic# 11.0 [10] (🦰..🦹) red hair..supervillain
|
||||
1F9BA..1F9BF ; Extended_Pictographic# 12.0 [6] (🦺..🦿) safety vest..mechanical leg
|
||||
1F9C0 ; Extended_Pictographic# 8.0 [1] (🧀) cheese wedge
|
||||
1F9C1..1F9C2 ; Extended_Pictographic# 11.0 [2] (🧁..🧂) cupcake..salt
|
||||
1F9C3..1F9CA ; Extended_Pictographic# 12.0 [8] (🧃..🧊) beverage box..ice cube
|
||||
1F9CB..1F9CC ; Extended_Pictographic# NA [2] (🧋..🧌) <reserved-1F9CB>..<reserved-1F9CC>
|
||||
1F9CD..1F9CF ; Extended_Pictographic# 12.0 [3] (🧍..🧏) person standing..deaf person
|
||||
1F9D0..1F9E6 ; Extended_Pictographic# 10.0 [23] (🧐..🧦) face with monocle..socks
|
||||
1F9E7..1F9FF ; Extended_Pictographic# 11.0 [25] (🧧..🧿) red envelope..nazar amulet
|
||||
1FA00..1FA53 ; Extended_Pictographic# 12.0 [84] (🨀..🩓) NEUTRAL CHESS KING..BLACK CHESS KNIGHT-BISHOP
|
||||
1FA54..1FA5F ; Extended_Pictographic# NA [12] (..) <reserved-1FA54>..<reserved-1FA5F>
|
||||
1FA60..1FA6D ; Extended_Pictographic# 11.0 [14] (🩠..🩭) XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER
|
||||
1FA6E..1FA6F ; Extended_Pictographic# NA [2] (..) <reserved-1FA6E>..<reserved-1FA6F>
|
||||
1FA70..1FA73 ; Extended_Pictographic# 12.0 [4] (🩰..🩳) ballet shoes..shorts
|
||||
1FA74..1FA77 ; Extended_Pictographic# NA [4] (🩴..🩷) <reserved-1FA74>..<reserved-1FA77>
|
||||
1FA78..1FA7A ; Extended_Pictographic# 12.0 [3] (🩸..🩺) drop of blood..stethoscope
|
||||
1FA7B..1FA7F ; Extended_Pictographic# NA [5] (🩻..) <reserved-1FA7B>..<reserved-1FA7F>
|
||||
1FA80..1FA82 ; Extended_Pictographic# 12.0 [3] (🪀..🪂) yo-yo..parachute
|
||||
1FA83..1FA8F ; Extended_Pictographic# NA [13] (🪃..) <reserved-1FA83>..<reserved-1FA8F>
|
||||
1FA90..1FA95 ; Extended_Pictographic# 12.0 [6] (🪐..🪕) ringed planet..banjo
|
||||
1FA96..1FFFD ; Extended_Pictographic# NA[1384] (🪖..) <reserved-1FA96>..<reserved-1FFFD>
|
||||
|
||||
# Total elements: 3793
|
||||
|
||||
#EOF
|
||||
4879
lib/pleroma/emoji-test.txt
Normal file
4879
lib/pleroma/emoji-test.txt
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -102,31 +102,36 @@ defmodule Pleroma.Emoji do
|
|||
:ets.insert(@ets, emojis)
|
||||
end
|
||||
|
||||
@external_resource "lib/pleroma/emoji-data.txt"
|
||||
@external_resource "lib/pleroma/emoji-test.txt"
|
||||
|
||||
regional_indicators =
|
||||
Enum.map(127_462..127_487, fn codepoint ->
|
||||
<<codepoint::utf8>>
|
||||
end)
|
||||
|
||||
emojis =
|
||||
@external_resource
|
||||
|> File.read!()
|
||||
|> String.split("\n")
|
||||
|> Enum.filter(fn line -> line != "" and not String.starts_with?(line, "#") end)
|
||||
|> Enum.filter(fn line ->
|
||||
line != "" and not String.starts_with?(line, "#") and
|
||||
String.contains?(line, "fully-qualified")
|
||||
end)
|
||||
|> Enum.map(fn line ->
|
||||
line
|
||||
|> String.split(";", parts: 2)
|
||||
|> hd()
|
||||
|> String.trim()
|
||||
|> String.split("..")
|
||||
|> case do
|
||||
[number] ->
|
||||
<<String.to_integer(number, 16)::utf8>>
|
||||
|
||||
[first, last] ->
|
||||
String.to_integer(first, 16)..String.to_integer(last, 16)
|
||||
|> Enum.map(&<<&1::utf8>>)
|
||||
end
|
||||
|> String.split()
|
||||
|> Enum.map(fn codepoint ->
|
||||
<<String.to_integer(codepoint, 16)::utf8>>
|
||||
end)
|
||||
|> Enum.join()
|
||||
end)
|
||||
|> List.flatten()
|
||||
|> Enum.uniq()
|
||||
|
||||
emojis = emojis ++ regional_indicators
|
||||
|
||||
for emoji <- emojis do
|
||||
def is_unicode_emoji?(unquote(emoji)), do: true
|
||||
end
|
||||
|
|
|
|||
|
|
@ -20,16 +20,18 @@ defmodule Pleroma.Emoji.Pack do
|
|||
name: String.t()
|
||||
}
|
||||
|
||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||
|
||||
alias Pleroma.Emoji
|
||||
alias Pleroma.Emoji.Pack
|
||||
alias Pleroma.Utils
|
||||
|
||||
@spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values}
|
||||
def create(name) do
|
||||
with :ok <- validate_not_empty([name]),
|
||||
dir <- Path.join(emoji_path(), name),
|
||||
:ok <- File.mkdir(dir) do
|
||||
%__MODULE__{pack_file: Path.join(dir, "pack.json")}
|
||||
|> save_pack()
|
||||
save_pack(%__MODULE__{pack_file: Path.join(dir, "pack.json")})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -62,10 +64,9 @@ defmodule Pleroma.Emoji.Pack do
|
|||
@spec delete(String.t()) ::
|
||||
{:ok, [binary()]} | {:error, File.posix(), binary()} | {:error, :empty_values}
|
||||
def delete(name) do
|
||||
with :ok <- validate_not_empty([name]) do
|
||||
emoji_path()
|
||||
|> Path.join(name)
|
||||
|> File.rm_rf()
|
||||
with :ok <- validate_not_empty([name]),
|
||||
pack_path <- Path.join(emoji_path(), name) do
|
||||
File.rm_rf(pack_path)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -94,7 +95,7 @@ defmodule Pleroma.Emoji.Pack do
|
|||
def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} = file) do
|
||||
with {:ok, zip_files} <- :zip.table(to_charlist(file.path)),
|
||||
[_ | _] = emojies <- unpack_zip_emojies(zip_files),
|
||||
{:ok, tmp_dir} <- Pleroma.Utils.tmp_dir("emoji") do
|
||||
{:ok, tmp_dir} <- Utils.tmp_dir("emoji") do
|
||||
try do
|
||||
{:ok, _emoji_files} =
|
||||
:zip.unzip(
|
||||
|
|
@ -282,18 +283,21 @@ defmodule Pleroma.Emoji.Pack do
|
|||
end
|
||||
end
|
||||
|
||||
@spec load_pack(String.t()) :: {:ok, t()} | {:error, :not_found}
|
||||
@spec load_pack(String.t()) :: {:ok, t()} | {:error, :file.posix()}
|
||||
def load_pack(name) do
|
||||
pack_file = Path.join([emoji_path(), name, "pack.json"])
|
||||
|
||||
if File.exists?(pack_file) do
|
||||
with {:ok, _} <- File.stat(pack_file),
|
||||
{:ok, pack_data} <- File.read(pack_file) do
|
||||
pack =
|
||||
pack_file
|
||||
|> File.read!()
|
||||
|> from_json()
|
||||
|> Map.put(:pack_file, pack_file)
|
||||
|> Map.put(:path, Path.dirname(pack_file))
|
||||
|> Map.put(:name, name)
|
||||
from_json(
|
||||
pack_data,
|
||||
%{
|
||||
pack_file: pack_file,
|
||||
path: Path.dirname(pack_file),
|
||||
name: name
|
||||
}
|
||||
)
|
||||
|
||||
files_count =
|
||||
pack.files
|
||||
|
|
@ -301,8 +305,6 @@ defmodule Pleroma.Emoji.Pack do
|
|||
|> length()
|
||||
|
||||
{:ok, Map.put(pack, :files_count, files_count)}
|
||||
else
|
||||
{:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -415,7 +417,7 @@ defmodule Pleroma.Emoji.Pack do
|
|||
ttl_per_file = Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file])
|
||||
overall_ttl = :timer.seconds(ttl_per_file * Enum.count(files))
|
||||
|
||||
Cachex.put!(
|
||||
@cachex.put(
|
||||
:emoji_packs_cache,
|
||||
pack.name,
|
||||
# if pack.json MD5 changes, the cache is not valid anymore
|
||||
|
|
@ -434,10 +436,17 @@ defmodule Pleroma.Emoji.Pack do
|
|||
end
|
||||
end
|
||||
|
||||
defp from_json(json) do
|
||||
defp from_json(json, attrs) do
|
||||
map = Jason.decode!(json)
|
||||
|
||||
struct(__MODULE__, %{files: map["files"], pack: map["pack"]})
|
||||
pack_attrs =
|
||||
attrs
|
||||
|> Map.merge(%{
|
||||
files: map["files"],
|
||||
pack: map["pack"]
|
||||
})
|
||||
|
||||
struct(__MODULE__, pack_attrs)
|
||||
end
|
||||
|
||||
defp validate_shareable_packs_available(uri) do
|
||||
|
|
@ -491,10 +500,10 @@ defmodule Pleroma.Emoji.Pack do
|
|||
end
|
||||
|
||||
defp create_subdirs(file_path) do
|
||||
if String.contains?(file_path, "/") do
|
||||
file_path
|
||||
|> Path.dirname()
|
||||
|> File.mkdir_p!()
|
||||
with true <- String.contains?(file_path, "/"),
|
||||
path <- Path.dirname(file_path),
|
||||
false <- File.exists?(path) do
|
||||
File.mkdir_p!(path)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -518,10 +527,15 @@ defmodule Pleroma.Emoji.Pack do
|
|||
|
||||
defp get_filename(pack, shortcode) do
|
||||
with %{^shortcode => filename} when is_binary(filename) <- pack.files,
|
||||
true <- pack.path |> Path.join(filename) |> File.exists?() do
|
||||
file_path <- Path.join(pack.path, filename),
|
||||
{:ok, _} <- File.stat(file_path) do
|
||||
{:ok, filename}
|
||||
else
|
||||
_ -> {:error, :doesnt_exist}
|
||||
{:error, _} = error ->
|
||||
error
|
||||
|
||||
_ ->
|
||||
{:error, :doesnt_exist}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -606,7 +620,7 @@ defmodule Pleroma.Emoji.Pack do
|
|||
defp fetch_archive(pack) do
|
||||
hash = :crypto.hash(:md5, File.read!(pack.pack_file))
|
||||
|
||||
case Cachex.get!(:emoji_packs_cache, pack.name) do
|
||||
case @cachex.get!(:emoji_packs_cache, pack.name) do
|
||||
%{hash: ^hash, pack_data: archive} -> archive
|
||||
_ -> create_archive_and_cache(pack, hash)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -62,23 +62,47 @@ defmodule Pleroma.FollowingRelationship do
|
|||
follow(follower, following, state)
|
||||
|
||||
following_relationship ->
|
||||
following_relationship
|
||||
|> cast(%{state: state}, [:state])
|
||||
|> validate_required([:state])
|
||||
|> Repo.update()
|
||||
with {:ok, _following_relationship} <-
|
||||
following_relationship
|
||||
|> cast(%{state: state}, [:state])
|
||||
|> validate_required([:state])
|
||||
|> Repo.update() do
|
||||
after_update(state, follower, following)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def follow(%User{} = follower, %User{} = following, state \\ :follow_accept) do
|
||||
%__MODULE__{}
|
||||
|> changeset(%{follower: follower, following: following, state: state})
|
||||
|> Repo.insert(on_conflict: :nothing)
|
||||
with {:ok, _following_relationship} <-
|
||||
%__MODULE__{}
|
||||
|> changeset(%{follower: follower, following: following, state: state})
|
||||
|> Repo.insert(on_conflict: :nothing) do
|
||||
after_update(state, follower, following)
|
||||
end
|
||||
end
|
||||
|
||||
def unfollow(%User{} = follower, %User{} = following) do
|
||||
case get(follower, following) do
|
||||
%__MODULE__{} = following_relationship -> Repo.delete(following_relationship)
|
||||
_ -> {:ok, nil}
|
||||
%__MODULE__{} = following_relationship ->
|
||||
with {:ok, _following_relationship} <- Repo.delete(following_relationship) do
|
||||
after_update(:unfollow, follower, following)
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:ok, nil}
|
||||
end
|
||||
end
|
||||
|
||||
defp after_update(state, %User{} = follower, %User{} = following) do
|
||||
with {:ok, following} <- User.update_follower_count(following),
|
||||
{:ok, follower} <- User.update_following_count(follower) do
|
||||
Pleroma.Web.Streamer.stream("follow_relationship", %{
|
||||
state: state,
|
||||
following: following,
|
||||
follower: follower
|
||||
})
|
||||
|
||||
{:ok, follower, following}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
110
lib/pleroma/frontend.ex
Normal file
110
lib/pleroma/frontend.ex
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Frontend do
|
||||
alias Pleroma.Config
|
||||
|
||||
require Logger
|
||||
|
||||
def install(name, opts \\ []) do
|
||||
frontend_info = %{
|
||||
"ref" => opts[:ref],
|
||||
"build_url" => opts[:build_url],
|
||||
"build_dir" => opts[:build_dir]
|
||||
}
|
||||
|
||||
frontend_info =
|
||||
[:frontends, :available, name]
|
||||
|> Config.get(%{})
|
||||
|> Map.merge(frontend_info, fn _key, config, cmd ->
|
||||
# This only overrides things that are actually set
|
||||
cmd || config
|
||||
end)
|
||||
|
||||
ref = frontend_info["ref"]
|
||||
|
||||
unless ref do
|
||||
raise "No ref given or configured"
|
||||
end
|
||||
|
||||
dest = Path.join([dir(), name, ref])
|
||||
|
||||
label = "#{name} (#{ref})"
|
||||
tmp_dir = Path.join(dir(), "tmp")
|
||||
|
||||
with {_, :ok} <-
|
||||
{:download_or_unzip, download_or_unzip(frontend_info, tmp_dir, opts[:file])},
|
||||
Logger.info("Installing #{label} to #{dest}"),
|
||||
:ok <- install_frontend(frontend_info, tmp_dir, dest) do
|
||||
File.rm_rf!(tmp_dir)
|
||||
Logger.info("Frontend #{label} installed to #{dest}")
|
||||
else
|
||||
{:download_or_unzip, _} ->
|
||||
Logger.info("Could not download or unzip the frontend")
|
||||
{:error, "Could not download or unzip the frontend"}
|
||||
|
||||
_e ->
|
||||
Logger.info("Could not install the frontend")
|
||||
{:error, "Could not install the frontend"}
|
||||
end
|
||||
end
|
||||
|
||||
def dir(opts \\ []) do
|
||||
if is_nil(opts[:static_dir]) do
|
||||
Pleroma.Config.get!([:instance, :static_dir])
|
||||
else
|
||||
opts[:static_dir]
|
||||
end
|
||||
|> Path.join("frontends")
|
||||
end
|
||||
|
||||
defp download_or_unzip(frontend_info, temp_dir, nil),
|
||||
do: download_build(frontend_info, temp_dir)
|
||||
|
||||
defp download_or_unzip(_frontend_info, temp_dir, file) do
|
||||
with {:ok, zip} <- File.read(Path.expand(file)) do
|
||||
unzip(zip, temp_dir)
|
||||
end
|
||||
end
|
||||
|
||||
def unzip(zip, dest) do
|
||||
with {:ok, unzipped} <- :zip.unzip(zip, [:memory]) do
|
||||
File.rm_rf!(dest)
|
||||
File.mkdir_p!(dest)
|
||||
|
||||
Enum.each(unzipped, fn {filename, data} ->
|
||||
path = filename
|
||||
|
||||
new_file_path = Path.join(dest, path)
|
||||
|
||||
new_file_path
|
||||
|> Path.dirname()
|
||||
|> File.mkdir_p!()
|
||||
|
||||
File.write!(new_file_path, data)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defp download_build(frontend_info, dest) do
|
||||
Logger.info("Downloading pre-built bundle for #{frontend_info["name"]}")
|
||||
url = String.replace(frontend_info["build_url"], "${ref}", frontend_info["ref"])
|
||||
|
||||
with {:ok, %{status: 200, body: zip_body}} <-
|
||||
Pleroma.HTTP.get(url, [], pool: :media, recv_timeout: 120_000) do
|
||||
unzip(zip_body, dest)
|
||||
else
|
||||
{:error, e} -> {:error, e}
|
||||
e -> {:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
defp install_frontend(frontend_info, source, dest) do
|
||||
from = frontend_info["build_dir"] || "dist"
|
||||
File.rm_rf!(dest)
|
||||
File.mkdir_p!(dest)
|
||||
File.cp_r!(Path.join([source, from]), dest)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
46
lib/pleroma/helpers/auth_helper.ex
Normal file
46
lib/pleroma/helpers/auth_helper.ex
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Helpers.AuthHelper do
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
alias Plug.Conn
|
||||
|
||||
import Plug.Conn
|
||||
|
||||
@oauth_token_session_key :oauth_token
|
||||
|
||||
@doc """
|
||||
Skips OAuth permissions (scopes) checks, assigns nil `:token`.
|
||||
Intended to be used with explicit authentication and only when OAuth token cannot be determined.
|
||||
"""
|
||||
def skip_oauth(conn) do
|
||||
conn
|
||||
|> assign(:token, nil)
|
||||
|> OAuthScopesPlug.skip_plug()
|
||||
end
|
||||
|
||||
@doc "Drops authentication info from connection"
|
||||
def drop_auth_info(conn) do
|
||||
# To simplify debugging, setting a private variable on `conn` if auth info is dropped
|
||||
conn
|
||||
|> assign(:user, nil)
|
||||
|> assign(:token, nil)
|
||||
|> put_private(:authentication_ignored, true)
|
||||
end
|
||||
|
||||
@doc "Gets OAuth token string from session"
|
||||
def get_session_token(%Conn{} = conn) do
|
||||
get_session(conn, @oauth_token_session_key)
|
||||
end
|
||||
|
||||
@doc "Updates OAuth token string in session"
|
||||
def put_session_token(%Conn{} = conn, token) when is_binary(token) do
|
||||
put_session(conn, @oauth_token_session_key, token)
|
||||
end
|
||||
|
||||
@doc "Deletes OAuth token string from session"
|
||||
def delete_session_token(%Conn{} = conn) do
|
||||
delete_session(conn, @oauth_token_session_key)
|
||||
end
|
||||
end
|
||||
|
|
@ -6,6 +6,8 @@ defmodule Pleroma.HTML do
|
|||
# Scrubbers are compiled on boot so they can be configured in OTP releases
|
||||
# @on_load :compile_scrubbers
|
||||
|
||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||
|
||||
def compile_scrubbers do
|
||||
dir = Path.join(:code.priv_dir(:pleroma), "scrubbers")
|
||||
|
||||
|
|
@ -56,7 +58,7 @@ defmodule Pleroma.HTML do
|
|||
) do
|
||||
key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}"
|
||||
|
||||
Cachex.fetch!(:scrubber_cache, key, fn _key ->
|
||||
@cachex.fetch!(:scrubber_cache, key, fn _key ->
|
||||
object = Pleroma.Object.normalize(activity)
|
||||
ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false, callback)
|
||||
end)
|
||||
|
|
@ -105,7 +107,7 @@ defmodule Pleroma.HTML do
|
|||
unless object.data["fake"] do
|
||||
key = "URL|#{object.id}"
|
||||
|
||||
Cachex.fetch!(:scrubber_cache, key, fn _key ->
|
||||
@cachex.fetch!(:scrubber_cache, key, fn _key ->
|
||||
{:commit, {:ok, extract_first_external_url(content)}}
|
||||
end)
|
||||
else
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ defmodule Pleroma.Instances.Instance do
|
|||
)
|
||||
end
|
||||
|
||||
def reachable?(_), do: true
|
||||
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
|
||||
with host <- host(url_or_host),
|
||||
|
|
@ -166,7 +166,8 @@ defmodule Pleroma.Instances.Instance do
|
|||
|
||||
defp scrape_favicon(%URI{} = instance_uri) do
|
||||
try do
|
||||
with {:ok, %Tesla.Env{body: html}} <-
|
||||
with {_, true} <- {:reachable, reachable?(instance_uri.host)},
|
||||
{:ok, %Tesla.Env{body: html}} <-
|
||||
Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}], pool: :media),
|
||||
{_, [favicon_rel | _]} when is_binary(favicon_rel) <-
|
||||
{:parse,
|
||||
|
|
@ -175,7 +176,15 @@ defmodule Pleroma.Instances.Instance do
|
|||
{:merge, URI.merge(instance_uri, favicon_rel) |> to_string()} do
|
||||
favicon
|
||||
else
|
||||
_ -> nil
|
||||
{:reachable, false} ->
|
||||
Logger.debug(
|
||||
"Instance.scrape_favicon(\"#{to_string(instance_uri)}\") ignored unreachable host"
|
||||
)
|
||||
|
||||
nil
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
rescue
|
||||
e ->
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
Postgrex.Types.define(
|
||||
Pleroma.PostgresTypes,
|
||||
[] ++ Ecto.Adapters.Postgres.extensions(),
|
||||
json: Jason
|
||||
)
|
||||
defmodule Pleroma.Logging do
|
||||
@callback error(String.t()) :: any()
|
||||
end
|
||||
|
|
@ -12,6 +12,26 @@ defmodule Pleroma.ModerationLog do
|
|||
|
||||
import Ecto.Query
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
@type log_subject :: Activity.t() | User.t() | list(User.t())
|
||||
@type log_params :: %{
|
||||
required(:actor) => User.t(),
|
||||
required(:action) => String.t(),
|
||||
optional(:subject) => log_subject(),
|
||||
optional(:subject_actor) => User.t(),
|
||||
optional(:subject_id) => String.t(),
|
||||
optional(:subjects) => list(User.t()),
|
||||
optional(:permission) => String.t(),
|
||||
optional(:text) => String.t(),
|
||||
optional(:sensitive) => String.t(),
|
||||
optional(:visibility) => String.t(),
|
||||
optional(:followed) => User.t(),
|
||||
optional(:follower) => User.t(),
|
||||
optional(:nicknames) => list(String.t()),
|
||||
optional(:tags) => list(String.t()),
|
||||
optional(:target) => String.t()
|
||||
}
|
||||
|
||||
schema "moderation_log" do
|
||||
field(:data, :map)
|
||||
|
||||
|
|
@ -90,203 +110,105 @@ defmodule Pleroma.ModerationLog do
|
|||
parsed_datetime
|
||||
end
|
||||
|
||||
@spec insert_log(%{actor: User, subject: [User], action: String.t(), permission: String.t()}) ::
|
||||
{:ok, ModerationLog} | {:error, any}
|
||||
def insert_log(%{
|
||||
actor: %User{} = actor,
|
||||
subject: subjects,
|
||||
action: action,
|
||||
permission: permission
|
||||
}) do
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
"actor" => user_to_map(actor),
|
||||
"subject" => user_to_map(subjects),
|
||||
"action" => action,
|
||||
"permission" => permission,
|
||||
"message" => ""
|
||||
}
|
||||
defp prepare_log_data(%{actor: actor, action: action} = attrs) do
|
||||
%{
|
||||
"actor" => user_to_map(actor),
|
||||
"action" => action,
|
||||
"message" => ""
|
||||
}
|
||||
|> insert_log_entry_with_message()
|
||||
|> Pleroma.Maps.put_if_present("subject_actor", user_to_map(attrs[:subject_actor]))
|
||||
end
|
||||
|
||||
@spec insert_log(%{actor: User, subject: User, action: String.t()}) ::
|
||||
{:ok, ModerationLog} | {:error, any}
|
||||
def insert_log(%{
|
||||
actor: %User{} = actor,
|
||||
action: "report_update",
|
||||
subject: %Activity{data: %{"type" => "Flag"}} = subject
|
||||
}) do
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
"actor" => user_to_map(actor),
|
||||
"action" => "report_update",
|
||||
"subject" => report_to_map(subject),
|
||||
"message" => ""
|
||||
}
|
||||
}
|
||||
|> insert_log_entry_with_message()
|
||||
defp prepare_log_data(attrs), do: attrs
|
||||
|
||||
@spec insert_log(log_params()) :: {:ok, ModerationLog} | {:error, any}
|
||||
def insert_log(%{actor: %User{}, subject: subjects, permission: permission} = attrs) do
|
||||
data =
|
||||
attrs
|
||||
|> prepare_log_data
|
||||
|> Map.merge(%{"subject" => user_to_map(subjects), "permission" => permission})
|
||||
|
||||
insert_log_entry_with_message(%ModerationLog{data: data})
|
||||
end
|
||||
|
||||
@spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) ::
|
||||
{:ok, ModerationLog} | {:error, any}
|
||||
def insert_log(%{
|
||||
actor: %User{} = actor,
|
||||
action: "report_note",
|
||||
subject: %Activity{} = subject,
|
||||
text: text
|
||||
}) do
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
"actor" => user_to_map(actor),
|
||||
"action" => "report_note",
|
||||
"subject" => report_to_map(subject),
|
||||
"text" => text
|
||||
}
|
||||
}
|
||||
|> insert_log_entry_with_message()
|
||||
def insert_log(%{actor: %User{}, action: action, subject: %Activity{} = subject} = attrs)
|
||||
when action in ["report_note_delete", "report_update", "report_note"] do
|
||||
data =
|
||||
attrs
|
||||
|> prepare_log_data
|
||||
|> Pleroma.Maps.put_if_present("text", attrs[:text])
|
||||
|> Map.merge(%{"subject" => report_to_map(subject)})
|
||||
|
||||
insert_log_entry_with_message(%ModerationLog{data: data})
|
||||
end
|
||||
|
||||
@spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) ::
|
||||
{:ok, ModerationLog} | {:error, any}
|
||||
def insert_log(%{
|
||||
actor: %User{} = actor,
|
||||
action: "report_note_delete",
|
||||
subject: %Activity{} = subject,
|
||||
text: text
|
||||
}) do
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
"actor" => user_to_map(actor),
|
||||
"action" => "report_note_delete",
|
||||
"subject" => report_to_map(subject),
|
||||
"text" => text
|
||||
}
|
||||
}
|
||||
|> insert_log_entry_with_message()
|
||||
end
|
||||
|
||||
@spec insert_log(%{
|
||||
actor: User,
|
||||
subject: Activity,
|
||||
action: String.t(),
|
||||
sensitive: String.t(),
|
||||
visibility: String.t()
|
||||
}) :: {:ok, ModerationLog} | {:error, any}
|
||||
def insert_log(%{
|
||||
actor: %User{} = actor,
|
||||
action: "status_update",
|
||||
subject: %Activity{} = subject,
|
||||
sensitive: sensitive,
|
||||
visibility: visibility
|
||||
}) do
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
"actor" => user_to_map(actor),
|
||||
"action" => "status_update",
|
||||
def insert_log(
|
||||
%{
|
||||
actor: %User{},
|
||||
action: action,
|
||||
subject: %Activity{} = subject,
|
||||
sensitive: sensitive,
|
||||
visibility: visibility
|
||||
} = attrs
|
||||
)
|
||||
when action == "status_update" do
|
||||
data =
|
||||
attrs
|
||||
|> prepare_log_data
|
||||
|> Map.merge(%{
|
||||
"subject" => status_to_map(subject),
|
||||
"sensitive" => sensitive,
|
||||
"visibility" => visibility,
|
||||
"message" => ""
|
||||
}
|
||||
}
|
||||
|> insert_log_entry_with_message()
|
||||
"visibility" => visibility
|
||||
})
|
||||
|
||||
insert_log_entry_with_message(%ModerationLog{data: data})
|
||||
end
|
||||
|
||||
@spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) ::
|
||||
{:ok, ModerationLog} | {:error, any}
|
||||
def insert_log(%{
|
||||
actor: %User{} = actor,
|
||||
action: "status_delete",
|
||||
subject_id: subject_id
|
||||
}) do
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
"actor" => user_to_map(actor),
|
||||
"action" => "status_delete",
|
||||
"subject_id" => subject_id,
|
||||
"message" => ""
|
||||
}
|
||||
}
|
||||
|> insert_log_entry_with_message()
|
||||
def insert_log(%{actor: %User{}, action: action, subject_id: subject_id} = attrs)
|
||||
when action == "status_delete" do
|
||||
data =
|
||||
attrs
|
||||
|> prepare_log_data
|
||||
|> Map.merge(%{"subject_id" => subject_id})
|
||||
|
||||
insert_log_entry_with_message(%ModerationLog{data: data})
|
||||
end
|
||||
|
||||
@spec insert_log(%{actor: User, subject: User, action: String.t()}) ::
|
||||
{:ok, ModerationLog} | {:error, any}
|
||||
def insert_log(%{actor: %User{} = actor, subject: subject, action: action}) do
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
"actor" => user_to_map(actor),
|
||||
"action" => action,
|
||||
"subject" => user_to_map(subject),
|
||||
"message" => ""
|
||||
}
|
||||
}
|
||||
|> insert_log_entry_with_message()
|
||||
def insert_log(%{actor: %User{}, subject: subject, action: _action} = attrs) do
|
||||
data =
|
||||
attrs
|
||||
|> prepare_log_data
|
||||
|> Map.merge(%{"subject" => user_to_map(subject)})
|
||||
|
||||
insert_log_entry_with_message(%ModerationLog{data: data})
|
||||
end
|
||||
|
||||
@spec insert_log(%{actor: User, subjects: [User], action: String.t()}) ::
|
||||
{:ok, ModerationLog} | {:error, any}
|
||||
def insert_log(%{actor: %User{} = actor, subjects: subjects, action: action}) do
|
||||
subjects = Enum.map(subjects, &user_to_map/1)
|
||||
def insert_log(%{actor: %User{}, subjects: subjects, action: _action} = attrs) do
|
||||
data =
|
||||
attrs
|
||||
|> prepare_log_data
|
||||
|> Map.merge(%{"subjects" => user_to_map(subjects)})
|
||||
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
"actor" => user_to_map(actor),
|
||||
"action" => action,
|
||||
"subjects" => subjects,
|
||||
"message" => ""
|
||||
}
|
||||
}
|
||||
|> insert_log_entry_with_message()
|
||||
insert_log_entry_with_message(%ModerationLog{data: data})
|
||||
end
|
||||
|
||||
@spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) ::
|
||||
{:ok, ModerationLog} | {:error, any}
|
||||
def insert_log(%{
|
||||
actor: %User{} = actor,
|
||||
followed: %User{} = followed,
|
||||
follower: %User{} = follower,
|
||||
action: "follow"
|
||||
}) do
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
"actor" => user_to_map(actor),
|
||||
"action" => "follow",
|
||||
"followed" => user_to_map(followed),
|
||||
"follower" => user_to_map(follower),
|
||||
"message" => ""
|
||||
}
|
||||
}
|
||||
|> insert_log_entry_with_message()
|
||||
def insert_log(
|
||||
%{
|
||||
actor: %User{},
|
||||
followed: %User{} = followed,
|
||||
follower: %User{} = follower,
|
||||
action: action
|
||||
} = attrs
|
||||
)
|
||||
when action in ["unfollow", "follow"] do
|
||||
data =
|
||||
attrs
|
||||
|> prepare_log_data
|
||||
|> Map.merge(%{"followed" => user_to_map(followed), "follower" => user_to_map(follower)})
|
||||
|
||||
insert_log_entry_with_message(%ModerationLog{data: data})
|
||||
end
|
||||
|
||||
@spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) ::
|
||||
{:ok, ModerationLog} | {:error, any}
|
||||
def insert_log(%{
|
||||
actor: %User{} = actor,
|
||||
followed: %User{} = followed,
|
||||
follower: %User{} = follower,
|
||||
action: "unfollow"
|
||||
}) do
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
"actor" => user_to_map(actor),
|
||||
"action" => "unfollow",
|
||||
"followed" => user_to_map(followed),
|
||||
"follower" => user_to_map(follower),
|
||||
"message" => ""
|
||||
}
|
||||
}
|
||||
|> insert_log_entry_with_message()
|
||||
end
|
||||
|
||||
@spec insert_log(%{
|
||||
actor: User,
|
||||
action: String.t(),
|
||||
nicknames: [String.t()],
|
||||
tags: [String.t()]
|
||||
}) :: {:ok, ModerationLog} | {:error, any}
|
||||
def insert_log(%{
|
||||
actor: %User{} = actor,
|
||||
nicknames: nicknames,
|
||||
|
|
@ -305,27 +227,16 @@ defmodule Pleroma.ModerationLog do
|
|||
|> insert_log_entry_with_message()
|
||||
end
|
||||
|
||||
@spec insert_log(%{actor: User, action: String.t(), target: String.t()}) ::
|
||||
{:ok, ModerationLog} | {:error, any}
|
||||
def insert_log(%{
|
||||
actor: %User{} = actor,
|
||||
action: action,
|
||||
target: target
|
||||
})
|
||||
def insert_log(%{actor: %User{}, action: action, target: target} = attrs)
|
||||
when action in ["relay_follow", "relay_unfollow"] do
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
"actor" => user_to_map(actor),
|
||||
"action" => action,
|
||||
"target" => target,
|
||||
"message" => ""
|
||||
}
|
||||
}
|
||||
|> insert_log_entry_with_message()
|
||||
data =
|
||||
attrs
|
||||
|> prepare_log_data
|
||||
|> Map.merge(%{"target" => target})
|
||||
|
||||
insert_log_entry_with_message(%ModerationLog{data: data})
|
||||
end
|
||||
|
||||
@spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) ::
|
||||
{:ok, ModerationLog} | {:error, any}
|
||||
def insert_log(%{actor: %User{} = actor, action: "chat_message_delete", subject_id: subject_id}) do
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
|
|
@ -345,32 +256,27 @@ defmodule Pleroma.ModerationLog do
|
|||
end
|
||||
|
||||
defp user_to_map(users) when is_list(users) do
|
||||
users |> Enum.map(&user_to_map/1)
|
||||
Enum.map(users, &user_to_map/1)
|
||||
end
|
||||
|
||||
defp user_to_map(%User{} = user) do
|
||||
user
|
||||
|> Map.from_struct()
|
||||
|> Map.take([:id, :nickname])
|
||||
|> Map.new(fn {k, v} -> {Atom.to_string(k), v} end)
|
||||
|> Map.put("type", "user")
|
||||
end
|
||||
|
||||
defp user_to_map(_), do: nil
|
||||
|
||||
defp report_to_map(%Activity{} = report) do
|
||||
%{
|
||||
"type" => "report",
|
||||
"id" => report.id,
|
||||
"state" => report.data["state"]
|
||||
}
|
||||
%{"type" => "report", "id" => report.id, "state" => report.data["state"]}
|
||||
end
|
||||
|
||||
defp status_to_map(%Activity{} = status) do
|
||||
%{
|
||||
"type" => "status",
|
||||
"id" => status.id
|
||||
}
|
||||
%{"type" => "status", "id" => status.id}
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog.t()) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -382,7 +288,6 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} made @#{follower_nickname} #{action} @#{followed_nickname}"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -393,7 +298,6 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} deleted users: #{users_to_nicknames_string(subjects)}"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -404,7 +308,6 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} created users: #{users_to_nicknames_string(subjects)}"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -415,7 +318,6 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} activated users: #{users_to_nicknames_string(users)}"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -426,7 +328,6 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -437,7 +338,6 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -451,7 +351,6 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} added tags: #{tags_string} to users: #{nicknames_to_string(nicknames)}"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -465,7 +364,6 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} removed tags: #{tags_string} from users: #{nicknames_to_string(nicknames)}"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -477,7 +375,6 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} made #{users_to_nicknames_string(users)} #{permission}"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -489,7 +386,6 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} revoked #{permission} role from #{users_to_nicknames_string(users)}"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -500,7 +396,6 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} followed relay: #{target}"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -511,42 +406,48 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} unfollowed relay: #{target}"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
"action" => "report_update",
|
||||
"subject" => %{"id" => subject_id, "state" => state, "type" => "report"}
|
||||
}
|
||||
}) do
|
||||
"@#{actor_nickname} updated report ##{subject_id} with '#{state}' state"
|
||||
def get_log_entry_message(
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
"action" => "report_update",
|
||||
"subject" => %{"id" => subject_id, "state" => state, "type" => "report"}
|
||||
}
|
||||
} = log
|
||||
) do
|
||||
"@#{actor_nickname} updated report ##{subject_id}" <>
|
||||
subject_actor_nickname(log, " (on user ", ")") <>
|
||||
" with '#{state}' state"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
"action" => "report_note",
|
||||
"subject" => %{"id" => subject_id, "type" => "report"},
|
||||
"text" => text
|
||||
}
|
||||
}) do
|
||||
"@#{actor_nickname} added note '#{text}' to report ##{subject_id}"
|
||||
def get_log_entry_message(
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
"action" => "report_note",
|
||||
"subject" => %{"id" => subject_id, "type" => "report"},
|
||||
"text" => text
|
||||
}
|
||||
} = log
|
||||
) do
|
||||
"@#{actor_nickname} added note '#{text}' to report ##{subject_id}" <>
|
||||
subject_actor_nickname(log, " on user ")
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
"action" => "report_note_delete",
|
||||
"subject" => %{"id" => subject_id, "type" => "report"},
|
||||
"text" => text
|
||||
}
|
||||
}) do
|
||||
"@#{actor_nickname} deleted note '#{text}' from report ##{subject_id}"
|
||||
def get_log_entry_message(
|
||||
%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
"action" => "report_note_delete",
|
||||
"subject" => %{"id" => subject_id, "type" => "report"},
|
||||
"text" => text
|
||||
}
|
||||
} = log
|
||||
) do
|
||||
"@#{actor_nickname} deleted note '#{text}' from report ##{subject_id}" <>
|
||||
subject_actor_nickname(log, " on user ")
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -559,7 +460,6 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} updated status ##{subject_id}, set visibility: '#{visibility}'"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -572,7 +472,6 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}'"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -587,7 +486,6 @@ defmodule Pleroma.ModerationLog do
|
|||
}'"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -598,7 +496,6 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} deleted status ##{subject_id}"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -609,7 +506,6 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} forced password reset for users: #{users_to_nicknames_string(subjects)}"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -620,7 +516,6 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} confirmed email for users: #{users_to_nicknames_string(subjects)}"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -633,7 +528,6 @@ defmodule Pleroma.ModerationLog do
|
|||
}"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -644,7 +538,6 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}"
|
||||
end
|
||||
|
||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
|
@ -676,4 +569,16 @@ defmodule Pleroma.ModerationLog do
|
|||
|> Enum.map(&"@#{&1["nickname"]}")
|
||||
|> Enum.join(", ")
|
||||
end
|
||||
|
||||
defp subject_actor_nickname(%ModerationLog{data: data}, prefix_msg, postfix_msg \\ "") do
|
||||
case data do
|
||||
%{"subject_actor" => %{"nickname" => subject_actor}} ->
|
||||
[prefix_msg, "@#{subject_actor}", postfix_msg]
|
||||
|> Enum.reject(&(&1 == ""))
|
||||
|> Enum.join()
|
||||
|
||||
_ ->
|
||||
""
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ defmodule Pleroma.Notification do
|
|||
move
|
||||
pleroma:chat_mention
|
||||
pleroma:emoji_reaction
|
||||
pleroma:report
|
||||
reblog
|
||||
}
|
||||
|
||||
|
|
@ -367,7 +368,7 @@ defmodule Pleroma.Notification do
|
|||
end
|
||||
|
||||
def create_notifications(%Activity{data: %{"type" => type}} = activity, options)
|
||||
when type in ["Follow", "Like", "Announce", "Move", "EmojiReact"] do
|
||||
when type in ["Follow", "Like", "Announce", "Move", "EmojiReact", "Flag"] do
|
||||
do_create_notifications(activity, options)
|
||||
end
|
||||
|
||||
|
|
@ -410,6 +411,9 @@ defmodule Pleroma.Notification do
|
|||
"EmojiReact" ->
|
||||
"pleroma:emoji_reaction"
|
||||
|
||||
"Flag" ->
|
||||
"pleroma:report"
|
||||
|
||||
# Compatibility with old reactions
|
||||
"EmojiReaction" ->
|
||||
"pleroma:emoji_reaction"
|
||||
|
|
@ -467,7 +471,7 @@ defmodule Pleroma.Notification do
|
|||
def get_notified_from_activity(activity, local_only \\ true)
|
||||
|
||||
def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
|
||||
when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReact"] do
|
||||
when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReact", "Flag"] do
|
||||
potential_receiver_ap_ids = get_potential_receiver_ap_ids(activity)
|
||||
|
||||
potential_receivers =
|
||||
|
|
@ -503,6 +507,10 @@ defmodule Pleroma.Notification do
|
|||
[object_id]
|
||||
end
|
||||
|
||||
def get_potential_receiver_ap_ids(%{data: %{"type" => "Flag"}}) do
|
||||
User.all_superusers() |> Enum.map(fn user -> user.ap_id end)
|
||||
end
|
||||
|
||||
def get_potential_receiver_ap_ids(activity) do
|
||||
[]
|
||||
|> Utils.maybe_notify_to_recipients(activity)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ defmodule Pleroma.Object do
|
|||
|
||||
@derive {Jason.Encoder, only: [:data]}
|
||||
|
||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||
|
||||
schema "objects" do
|
||||
field(:data, :map)
|
||||
|
||||
|
|
@ -156,9 +158,9 @@ defmodule Pleroma.Object do
|
|||
def get_cached_by_ap_id(ap_id) do
|
||||
key = "object:#{ap_id}"
|
||||
|
||||
with {:ok, nil} <- Cachex.get(:object_cache, key),
|
||||
with {:ok, nil} <- @cachex.get(:object_cache, key),
|
||||
object when not is_nil(object) <- get_by_ap_id(ap_id),
|
||||
{:ok, true} <- Cachex.put(:object_cache, key, object) do
|
||||
{:ok, true} <- @cachex.put(:object_cache, key, object) do
|
||||
object
|
||||
else
|
||||
{:ok, object} -> object
|
||||
|
|
@ -216,13 +218,13 @@ defmodule Pleroma.Object do
|
|||
end
|
||||
|
||||
def invalid_object_cache(%Object{data: %{"id" => id}}) do
|
||||
with {:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do
|
||||
Cachex.del(:web_resp_cache, URI.parse(id).path)
|
||||
with {:ok, true} <- @cachex.del(:object_cache, "object:#{id}") do
|
||||
@cachex.del(:web_resp_cache, URI.parse(id).path)
|
||||
end
|
||||
end
|
||||
|
||||
def set_cache(%Object{data: %{"id" => ap_id}} = object) do
|
||||
Cachex.put(:object_cache, "object:#{ap_id}", object)
|
||||
@cachex.put(:object_cache, "object:#{ap_id}", object)
|
||||
{:ok, object}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ defmodule Pleroma.Object.Fetcher do
|
|||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.Federator
|
||||
alias Pleroma.Web.FedSockets
|
||||
|
||||
require Logger
|
||||
require Pleroma.Constants
|
||||
|
|
@ -183,16 +182,16 @@ defmodule Pleroma.Object.Fetcher do
|
|||
end
|
||||
end
|
||||
|
||||
def fetch_and_contain_remote_object_from_id(prm, opts \\ [])
|
||||
def fetch_and_contain_remote_object_from_id(id)
|
||||
|
||||
def fetch_and_contain_remote_object_from_id(%{"id" => id}, opts),
|
||||
do: fetch_and_contain_remote_object_from_id(id, opts)
|
||||
def fetch_and_contain_remote_object_from_id(%{"id" => id}),
|
||||
do: fetch_and_contain_remote_object_from_id(id)
|
||||
|
||||
def fetch_and_contain_remote_object_from_id(id, opts) when is_binary(id) do
|
||||
def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
|
||||
Logger.debug("Fetching object #{id} via AP")
|
||||
|
||||
with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")},
|
||||
{:ok, body} <- get_object(id, opts),
|
||||
{:ok, body} <- get_object(id),
|
||||
{:ok, data} <- safe_json_decode(body),
|
||||
:ok <- Containment.contain_origin_from_id(id, data) do
|
||||
{:ok, data}
|
||||
|
|
@ -208,22 +207,10 @@ defmodule Pleroma.Object.Fetcher do
|
|||
end
|
||||
end
|
||||
|
||||
def fetch_and_contain_remote_object_from_id(_id, _opts),
|
||||
def fetch_and_contain_remote_object_from_id(_id),
|
||||
do: {:error, "id must be a string"}
|
||||
|
||||
defp get_object(id, opts) do
|
||||
with false <- Keyword.get(opts, :force_http, false),
|
||||
{:ok, fedsocket} <- FedSockets.get_or_create_fed_socket(id) do
|
||||
Logger.debug("fetching via fedsocket - #{inspect(id)}")
|
||||
FedSockets.fetch(fedsocket, id)
|
||||
else
|
||||
_other ->
|
||||
Logger.debug("fetching via http - #{inspect(id)}")
|
||||
get_object_http(id)
|
||||
end
|
||||
end
|
||||
|
||||
defp get_object_http(id) do
|
||||
defp get_object(id) do
|
||||
date = Pleroma.Signature.signed_date()
|
||||
|
||||
headers =
|
||||
|
|
@ -232,8 +219,24 @@ defmodule Pleroma.Object.Fetcher do
|
|||
|> sign_fetch(id, date)
|
||||
|
||||
case HTTP.get(id, headers) do
|
||||
{:ok, %{body: body, status: code}} when code in 200..299 ->
|
||||
{:ok, body}
|
||||
{:ok, %{body: body, status: code, headers: headers}} when code in 200..299 ->
|
||||
case List.keyfind(headers, "content-type", 0) do
|
||||
{_, content_type} ->
|
||||
case Plug.Conn.Utils.media_type(content_type) do
|
||||
{:ok, "application", "activity+json", _} ->
|
||||
{:ok, body}
|
||||
|
||||
{:ok, "application", "ld+json",
|
||||
%{"profile" => "https://www.w3.org/ns/activitystreams"}} ->
|
||||
{:ok, body}
|
||||
|
||||
_ ->
|
||||
{:error, {:content_type, content_type}}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, {:content_type, nil}}
|
||||
end
|
||||
|
||||
{:ok, %{status: code}} when code in [404, 410] ->
|
||||
{:error, "Object has been deleted"}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ defmodule Pleroma.PasswordResetToken do
|
|||
@spec reset_password(binary(), map()) :: {:ok, User.t()} | {:error, binary()}
|
||||
def reset_password(token, data) do
|
||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||
false <- expired?(token),
|
||||
%User{} = user <- User.get_cached_by_id(token.user_id),
|
||||
{:ok, _user} <- User.reset_password(user, data),
|
||||
{:ok, token} <- Repo.update(used_changeset(token)) do
|
||||
|
|
@ -48,4 +49,14 @@ defmodule Pleroma.PasswordResetToken do
|
|||
_e -> {:error, token}
|
||||
end
|
||||
end
|
||||
|
||||
def expired?(%__MODULE__{inserted_at: inserted_at}) do
|
||||
validity = Pleroma.Config.get([:instance, :password_reset_token_validity], 0)
|
||||
|
||||
now = NaiveDateTime.utc_now()
|
||||
|
||||
difference = NaiveDateTime.diff(now, inserted_at)
|
||||
|
||||
difference > validity
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ defmodule Pleroma.ReverseProxy do
|
|||
@failed_request_ttl :timer.seconds(60)
|
||||
@methods ~w(GET HEAD)
|
||||
|
||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||
|
||||
def max_read_duration_default, do: @max_read_duration
|
||||
def default_cache_control_header, do: @default_cache_control_header
|
||||
|
||||
|
|
@ -107,7 +109,7 @@ defmodule Pleroma.ReverseProxy do
|
|||
opts
|
||||
end
|
||||
|
||||
with {:ok, nil} <- Cachex.get(:failed_proxy_url_cache, url),
|
||||
with {:ok, nil} <- @cachex.get(:failed_proxy_url_cache, url),
|
||||
{:ok, code, headers, client} <- request(method, url, req_headers, client_opts),
|
||||
:ok <-
|
||||
header_length_constraint(
|
||||
|
|
@ -427,6 +429,6 @@ defmodule Pleroma.ReverseProxy do
|
|||
nil
|
||||
end
|
||||
|
||||
Cachex.put(:failed_proxy_url_cache, url, true, ttl: ttl)
|
||||
@cachex.put(:failed_proxy_url_cache, url, true, ttl: ttl)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ defmodule Pleroma.Signature do
|
|||
def fetch_public_key(conn) do
|
||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
||||
{:ok, actor_id} <- key_id_to_actor_id(kid),
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id, force_http: true) do
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||
{:ok, public_key}
|
||||
else
|
||||
e ->
|
||||
|
|
@ -50,8 +50,8 @@ defmodule Pleroma.Signature do
|
|||
def refetch_public_key(conn) do
|
||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
||||
{:ok, actor_id} <- key_id_to_actor_id(kid),
|
||||
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id, force_http: true),
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id, force_http: true) do
|
||||
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||
{:ok, public_key}
|
||||
else
|
||||
e ->
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ defmodule Pleroma.Stats do
|
|||
|
||||
@impl true
|
||||
def init(_args) do
|
||||
if Pleroma.Config.get(:env) == :test, do: :ok = Ecto.Adapters.SQL.Sandbox.checkout(Repo)
|
||||
{:ok, nil, {:continue, :calculate_stats}}
|
||||
end
|
||||
|
||||
|
|
@ -32,11 +31,6 @@ defmodule Pleroma.Stats do
|
|||
GenServer.call(__MODULE__, :force_update)
|
||||
end
|
||||
|
||||
@doc "Performs collect stats"
|
||||
def do_collect do
|
||||
GenServer.cast(__MODULE__, :run_update)
|
||||
end
|
||||
|
||||
@doc "Returns stats data"
|
||||
@spec get_stats() :: %{
|
||||
domain_count: non_neg_integer(),
|
||||
|
|
@ -111,7 +105,11 @@ defmodule Pleroma.Stats do
|
|||
@impl true
|
||||
def handle_continue(:calculate_stats, _) do
|
||||
stats = calculate_stat_data()
|
||||
Process.send_after(self(), :run_update, @interval)
|
||||
|
||||
unless Pleroma.Config.get(:env) == :test do
|
||||
Process.send_after(self(), :run_update, @interval)
|
||||
end
|
||||
|
||||
{:noreply, stats}
|
||||
end
|
||||
|
||||
|
|
@ -126,13 +124,6 @@ defmodule Pleroma.Stats do
|
|||
{:reply, state, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast(:run_update, _state) do
|
||||
new_stats = calculate_stat_data()
|
||||
|
||||
{:noreply, new_stats}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(:run_update, _) do
|
||||
new_stats = calculate_stat_data()
|
||||
|
|
|
|||
|
|
@ -81,6 +81,8 @@ defmodule Pleroma.User do
|
|||
]
|
||||
]
|
||||
|
||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||
|
||||
schema "users" do
|
||||
field(:bio, :string, default: "")
|
||||
field(:raw_bio, :string)
|
||||
|
|
@ -140,7 +142,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, :string}, default: [])
|
||||
field(:also_known_as, {:array, ObjectValidators.ObjectID}, default: [])
|
||||
field(:inbox, :string)
|
||||
field(:shared_inbox, :string)
|
||||
field(:accepts_chat_messages, :boolean, default: nil)
|
||||
|
|
@ -245,6 +247,18 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def cached_blocked_users_ap_ids(user) do
|
||||
@cachex.fetch!(:user_cache, "blocked_users_ap_ids:#{user.ap_id}", fn _ ->
|
||||
blocked_users_ap_ids(user)
|
||||
end)
|
||||
end
|
||||
|
||||
def cached_muted_users_ap_ids(user) do
|
||||
@cachex.fetch!(:user_cache, "muted_users_ap_ids:#{user.ap_id}", fn _ ->
|
||||
muted_users_ap_ids(user)
|
||||
end)
|
||||
end
|
||||
|
||||
defdelegate following_count(user), to: FollowingRelationship
|
||||
defdelegate following(user), to: FollowingRelationship
|
||||
defdelegate following?(follower, followed), to: FollowingRelationship
|
||||
|
|
@ -461,6 +475,18 @@ defmodule Pleroma.User do
|
|||
|> validate_length(:bio, max: bio_limit)
|
||||
|> validate_length(:name, max: name_limit)
|
||||
|> validate_fields(true)
|
||||
|> validate_non_local()
|
||||
end
|
||||
|
||||
defp validate_non_local(cng) do
|
||||
local? = get_field(cng, :local)
|
||||
|
||||
if local? do
|
||||
cng
|
||||
|> add_error(:local, "User is local, can't update with this changeset.")
|
||||
else
|
||||
cng
|
||||
end
|
||||
end
|
||||
|
||||
def update_changeset(struct, params \\ %{}) do
|
||||
|
|
@ -489,6 +515,7 @@ defmodule Pleroma.User do
|
|||
:hide_follows_count,
|
||||
:hide_favorites,
|
||||
:allow_following_move,
|
||||
:also_known_as,
|
||||
:background,
|
||||
:show_role,
|
||||
:skip_thread_containment,
|
||||
|
|
@ -497,7 +524,6 @@ defmodule Pleroma.User do
|
|||
:pleroma_settings_store,
|
||||
:is_discoverable,
|
||||
:actor_type,
|
||||
:also_known_as,
|
||||
:accepts_chat_messages
|
||||
]
|
||||
)
|
||||
|
|
@ -782,18 +808,50 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def post_register_action(%User{} = user) do
|
||||
def post_register_action(%User{confirmation_pending: true} = user) do
|
||||
with {:ok, _} <- try_send_confirmation_email(user) do
|
||||
{:ok, user}
|
||||
end
|
||||
end
|
||||
|
||||
def post_register_action(%User{approval_pending: true} = user) do
|
||||
with {:ok, _} <- send_user_approval_email(user),
|
||||
{:ok, _} <- send_admin_approval_emails(user) do
|
||||
{:ok, user}
|
||||
end
|
||||
end
|
||||
|
||||
def post_register_action(%User{approval_pending: false, confirmation_pending: false} = user) do
|
||||
with {:ok, user} <- autofollow_users(user),
|
||||
{:ok, _} <- autofollowing_users(user),
|
||||
{:ok, user} <- set_cache(user),
|
||||
{:ok, _} <- send_welcome_email(user),
|
||||
{:ok, _} <- send_welcome_message(user),
|
||||
{:ok, _} <- send_welcome_chat_message(user),
|
||||
{:ok, _} <- try_send_confirmation_email(user) do
|
||||
{:ok, _} <- send_welcome_chat_message(user) do
|
||||
{:ok, user}
|
||||
end
|
||||
end
|
||||
|
||||
defp send_user_approval_email(user) do
|
||||
user
|
||||
|> Pleroma.Emails.UserEmail.approval_pending_email()
|
||||
|> Pleroma.Emails.Mailer.deliver_async()
|
||||
|
||||
{:ok, :enqueued}
|
||||
end
|
||||
|
||||
defp send_admin_approval_emails(user) do
|
||||
all_superusers()
|
||||
|> Enum.filter(fn user -> not is_nil(user.email) end)
|
||||
|> Enum.each(fn superuser ->
|
||||
superuser
|
||||
|> Pleroma.Emails.AdminEmail.new_unapproved_registration(user)
|
||||
|> Pleroma.Emails.Mailer.deliver_async()
|
||||
end)
|
||||
|
||||
{:ok, :enqueued}
|
||||
end
|
||||
|
||||
def send_welcome_message(user) do
|
||||
if User.WelcomeMessage.enabled?() do
|
||||
User.WelcomeMessage.post_message(user)
|
||||
|
|
@ -870,7 +928,7 @@ defmodule Pleroma.User do
|
|||
if not ap_enabled?(followed) do
|
||||
follow(follower, followed)
|
||||
else
|
||||
{:ok, follower}
|
||||
{:ok, follower, followed}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -896,11 +954,6 @@ defmodule Pleroma.User do
|
|||
|
||||
true ->
|
||||
FollowingRelationship.follow(follower, followed, state)
|
||||
|
||||
{:ok, _} = update_follower_count(followed)
|
||||
|
||||
follower
|
||||
|> update_following_count()
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -924,11 +977,6 @@ defmodule Pleroma.User do
|
|||
case get_follow_state(follower, followed) do
|
||||
state when state in [:follow_pending, :follow_accept] ->
|
||||
FollowingRelationship.unfollow(follower, followed)
|
||||
{:ok, followed} = update_follower_count(followed)
|
||||
|
||||
{:ok, follower} = update_following_count(follower)
|
||||
|
||||
{:ok, follower, followed}
|
||||
|
||||
nil ->
|
||||
{:error, "Not subscribed!"}
|
||||
|
|
@ -1002,9 +1050,9 @@ defmodule Pleroma.User do
|
|||
def set_cache({:error, err}), do: {:error, err}
|
||||
|
||||
def set_cache(%User{} = user) do
|
||||
Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
|
||||
Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
|
||||
Cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
|
||||
@cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
|
||||
@cachex.put(:user_cache, "nickname:#{user.nickname}", user)
|
||||
@cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
|
||||
{:ok, user}
|
||||
end
|
||||
|
||||
|
|
@ -1027,24 +1075,26 @@ defmodule Pleroma.User do
|
|||
|
||||
@spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
|
||||
def get_cached_user_friends_ap_ids(user) do
|
||||
Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
|
||||
@cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
|
||||
get_user_friends_ap_ids(user)
|
||||
end)
|
||||
end
|
||||
|
||||
def invalidate_cache(user) do
|
||||
Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
|
||||
Cachex.del(:user_cache, "nickname:#{user.nickname}")
|
||||
Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
|
||||
@cachex.del(:user_cache, "ap_id:#{user.ap_id}")
|
||||
@cachex.del(:user_cache, "nickname:#{user.nickname}")
|
||||
@cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
|
||||
@cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
|
||||
@cachex.del(:user_cache, "muted_users_ap_ids:#{user.ap_id}")
|
||||
end
|
||||
|
||||
@spec get_cached_by_ap_id(String.t()) :: User.t() | nil
|
||||
def get_cached_by_ap_id(ap_id) do
|
||||
key = "ap_id:#{ap_id}"
|
||||
|
||||
with {:ok, nil} <- Cachex.get(:user_cache, key),
|
||||
with {:ok, nil} <- @cachex.get(:user_cache, key),
|
||||
user when not is_nil(user) <- get_by_ap_id(ap_id),
|
||||
{:ok, true} <- Cachex.put(:user_cache, key, user) do
|
||||
{:ok, true} <- @cachex.put(:user_cache, key, user) do
|
||||
user
|
||||
else
|
||||
{:ok, user} -> user
|
||||
|
|
@ -1056,11 +1106,11 @@ defmodule Pleroma.User do
|
|||
key = "id:#{id}"
|
||||
|
||||
ap_id =
|
||||
Cachex.fetch!(:user_cache, key, fn _ ->
|
||||
@cachex.fetch!(:user_cache, key, fn _ ->
|
||||
user = get_by_id(id)
|
||||
|
||||
if user do
|
||||
Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
|
||||
@cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
|
||||
{:commit, user.ap_id}
|
||||
else
|
||||
{:ignore, ""}
|
||||
|
|
@ -1073,7 +1123,7 @@ defmodule Pleroma.User do
|
|||
def get_cached_by_nickname(nickname) do
|
||||
key = "nickname:#{nickname}"
|
||||
|
||||
Cachex.fetch!(:user_cache, key, fn ->
|
||||
@cachex.fetch!(:user_cache, key, fn _ ->
|
||||
case get_or_fetch_by_nickname(nickname) do
|
||||
{:ok, user} -> {:commit, user}
|
||||
{:error, _error} -> {:ignore, nil}
|
||||
|
|
@ -1324,14 +1374,51 @@ defmodule Pleroma.User do
|
|||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec mute(User.t(), User.t(), boolean()) ::
|
||||
@spec mute(User.t(), User.t(), map()) ::
|
||||
{:ok, list(UserRelationship.t())} | {:error, String.t()}
|
||||
def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
|
||||
add_to_mutes(muter, mutee, notifications?)
|
||||
def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do
|
||||
notifications? = Map.get(params, :notifications, true)
|
||||
expires_in = Map.get(params, :expires_in, 0)
|
||||
|
||||
with {:ok, user_mute} <- UserRelationship.create_mute(muter, mutee),
|
||||
{:ok, user_notification_mute} <-
|
||||
(notifications? && UserRelationship.create_notification_mute(muter, mutee)) ||
|
||||
{:ok, nil} do
|
||||
if expires_in > 0 do
|
||||
Pleroma.Workers.MuteExpireWorker.enqueue(
|
||||
"unmute_user",
|
||||
%{"muter_id" => muter.id, "mutee_id" => mutee.id},
|
||||
schedule_in: expires_in
|
||||
)
|
||||
end
|
||||
|
||||
@cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
|
||||
|
||||
{:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
|
||||
end
|
||||
end
|
||||
|
||||
def unmute(%User{} = muter, %User{} = mutee) do
|
||||
remove_from_mutes(muter, mutee)
|
||||
with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee),
|
||||
{:ok, user_notification_mute} <-
|
||||
UserRelationship.delete_notification_mute(muter, mutee) do
|
||||
@cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
|
||||
{:ok, [user_mute, user_notification_mute]}
|
||||
end
|
||||
end
|
||||
|
||||
def unmute(muter_id, mutee_id) do
|
||||
with {:muter, %User{} = muter} <- {:muter, User.get_by_id(muter_id)},
|
||||
{:mutee, %User{} = mutee} <- {:mutee, User.get_by_id(mutee_id)} do
|
||||
unmute(muter, mutee)
|
||||
else
|
||||
{who, result} = error ->
|
||||
Logger.warn(
|
||||
"User.unmute/2 failed. #{who}: #{result}, muter_id: #{muter_id}, mutee_id: #{mutee_id}"
|
||||
)
|
||||
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
def subscribe(%User{} = subscriber, %User{} = target) do
|
||||
|
|
@ -1537,11 +1624,34 @@ defmodule Pleroma.User do
|
|||
end)
|
||||
end
|
||||
|
||||
def approve(%User{} = user) do
|
||||
change(user, approval_pending: false)
|
||||
|> update_and_set_cache()
|
||||
def approve(%User{approval_pending: true} = user) do
|
||||
with chg <- change(user, approval_pending: false),
|
||||
{:ok, user} <- update_and_set_cache(chg) do
|
||||
post_register_action(user)
|
||||
{:ok, user}
|
||||
end
|
||||
end
|
||||
|
||||
def approve(%User{} = user), do: {:ok, user}
|
||||
|
||||
def confirm(users) when is_list(users) do
|
||||
Repo.transaction(fn ->
|
||||
Enum.map(users, fn user ->
|
||||
with {:ok, user} <- confirm(user), do: user
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
def confirm(%User{confirmation_pending: true} = user) do
|
||||
with chg <- confirmation_changeset(user, need_confirmation: false),
|
||||
{:ok, user} <- update_and_set_cache(chg) do
|
||||
post_register_action(user)
|
||||
{:ok, user}
|
||||
end
|
||||
end
|
||||
|
||||
def confirm(%User{} = user), do: {:ok, user}
|
||||
|
||||
def update_notification_settings(%User{} = user, settings) do
|
||||
user
|
||||
|> cast(%{notification_settings: settings}, [])
|
||||
|
|
@ -1738,12 +1848,12 @@ defmodule Pleroma.User do
|
|||
|
||||
def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
|
||||
|
||||
def fetch_by_ap_id(ap_id, opts \\ []), do: ActivityPub.make_user_from_ap_id(ap_id, opts)
|
||||
def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
|
||||
|
||||
def get_or_fetch_by_ap_id(ap_id, opts \\ []) do
|
||||
def get_or_fetch_by_ap_id(ap_id) do
|
||||
cached_user = get_cached_by_ap_id(ap_id)
|
||||
|
||||
maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id, opts)
|
||||
maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
|
||||
|
||||
case {cached_user, maybe_fetched_user} do
|
||||
{_, {:ok, %User{} = user}} ->
|
||||
|
|
@ -1816,8 +1926,8 @@ defmodule Pleroma.User do
|
|||
|
||||
def public_key(_), do: {:error, "key not found"}
|
||||
|
||||
def get_public_key_for_ap_id(ap_id, opts \\ []) do
|
||||
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id, opts),
|
||||
def get_public_key_for_ap_id(ap_id) do
|
||||
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
|
||||
{:ok, public_key} <- public_key(user) do
|
||||
{:ok, public_key}
|
||||
else
|
||||
|
|
@ -2028,18 +2138,6 @@ defmodule Pleroma.User do
|
|||
updated_user
|
||||
end
|
||||
|
||||
@spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
|
||||
def toggle_confirmation(%User{} = user) do
|
||||
user
|
||||
|> confirmation_changeset(need_confirmation: !user.confirmation_pending)
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
@spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
|
||||
def toggle_confirmation(users) do
|
||||
Enum.map(users, &toggle_confirmation/1)
|
||||
end
|
||||
|
||||
@spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
|
||||
def need_confirmation(%User{} = user, bool) do
|
||||
user
|
||||
|
|
@ -2311,29 +2409,18 @@ defmodule Pleroma.User do
|
|||
@spec add_to_block(User.t(), User.t()) ::
|
||||
{:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
|
||||
defp add_to_block(%User{} = user, %User{} = blocked) do
|
||||
UserRelationship.create_block(user, blocked)
|
||||
with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do
|
||||
@cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
|
||||
{:ok, relationship}
|
||||
end
|
||||
end
|
||||
|
||||
@spec add_to_block(User.t(), User.t()) ::
|
||||
{:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
|
||||
defp remove_from_block(%User{} = user, %User{} = blocked) do
|
||||
UserRelationship.delete_block(user, blocked)
|
||||
end
|
||||
|
||||
defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
|
||||
with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
|
||||
{:ok, user_notification_mute} <-
|
||||
(notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
|
||||
{:ok, nil} do
|
||||
{:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
|
||||
end
|
||||
end
|
||||
|
||||
defp remove_from_mutes(user, %User{} = muted_user) do
|
||||
with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
|
||||
{:ok, user_notification_mute} <-
|
||||
UserRelationship.delete_notification_mute(user, muted_user) do
|
||||
{:ok, [user_mute, user_notification_mute]}
|
||||
with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
|
||||
@cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
|
||||
{:ok, relationship}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -2366,4 +2453,8 @@ defmodule Pleroma.User do
|
|||
|> Map.put(:bio, HTML.filter_tags(user.bio, filter))
|
||||
|> Map.put(:fields, fields)
|
||||
end
|
||||
|
||||
def get_host(%User{ap_id: ap_id} = _user) do
|
||||
URI.parse(ap_id).host
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ defmodule Pleroma.User.Import do
|
|||
identifiers,
|
||||
fn identifier ->
|
||||
with {:ok, %User{} = followed} <- User.get_or_fetch(identifier),
|
||||
{:ok, follower} <- User.maybe_direct_follow(follower, followed),
|
||||
{:ok, follower, followed} <- User.maybe_direct_follow(follower, followed),
|
||||
{:ok, _, _, _} <- CommonAPI.follow(follower, followed) do
|
||||
followed
|
||||
else
|
||||
|
|
|
|||
|
|
@ -85,7 +85,6 @@ defmodule Pleroma.User.Search do
|
|||
|> base_query(following)
|
||||
|> filter_blocked_user(for_user)
|
||||
|> filter_invisible_users()
|
||||
|> filter_discoverable_users()
|
||||
|> filter_internal_users()
|
||||
|> filter_blocked_domains(for_user)
|
||||
|> fts_search(query_string)
|
||||
|
|
@ -163,10 +162,6 @@ defmodule Pleroma.User.Search do
|
|||
from(q in query, where: q.invisible == false)
|
||||
end
|
||||
|
||||
defp filter_discoverable_users(query) do
|
||||
from(q in query, where: q.is_discoverable == true)
|
||||
end
|
||||
|
||||
defp filter_internal_users(query) do
|
||||
from(q in query, where: q.actor_type != "Application")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Utils do
|
||||
@posix_error_codes ~w(
|
||||
eacces eagain ebadf ebadmsg ebusy edeadlk edeadlock edquot eexist efault
|
||||
efbig eftype eintr einval eio eisdir eloop emfile emlink emultihop
|
||||
enametoolong enfile enobufs enodev enolck enolink enoent enomem enospc
|
||||
enosr enostr enosys enotblk enotdir enotsup enxio eopnotsupp eoverflow
|
||||
eperm epipe erange erofs espipe esrch estale etxtbsy exdev
|
||||
)a
|
||||
|
||||
def compile_dir(dir) when is_binary(dir) do
|
||||
dir
|
||||
|> File.ls!()
|
||||
|
|
@ -44,4 +52,12 @@ defmodule Pleroma.Utils do
|
|||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@spec posix_error_message(atom()) :: binary()
|
||||
def posix_error_message(code) when code in @posix_error_codes do
|
||||
error_message = Gettext.dgettext(Pleroma.Web.Gettext, "posix_errors", "#{code}")
|
||||
"(POSIX error: #{error_message})"
|
||||
end
|
||||
|
||||
def posix_error_message(_), do: ""
|
||||
end
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ defmodule Pleroma.Web do
|
|||
below.
|
||||
"""
|
||||
|
||||
alias Pleroma.Helpers.AuthHelper
|
||||
alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug
|
||||
alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
|
||||
alias Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug
|
||||
|
|
@ -75,7 +76,7 @@ defmodule Pleroma.Web do
|
|||
defp maybe_drop_authentication_if_oauth_check_ignored(conn) do
|
||||
if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) and
|
||||
not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do
|
||||
OAuthScopesPlug.drop_auth_info(conn)
|
||||
AuthHelper.drop_auth_info(conn)
|
||||
else
|
||||
conn
|
||||
end
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
require Logger
|
||||
require Pleroma.Constants
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.ActivityPub.Persisting
|
||||
@behaviour Pleroma.Web.ActivityPub.ActivityPub.Streaming
|
||||
|
||||
defp get_recipients(%{"type" => "Create"} = data) do
|
||||
to = Map.get(data, "to", [])
|
||||
cc = Map.get(data, "cc", [])
|
||||
|
|
@ -85,13 +88,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
defp increase_replies_count_if_reply(_create_data), do: :noop
|
||||
|
||||
@object_types ~w[ChatMessage Question Answer Audio Video Event Article]
|
||||
@spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
|
||||
@impl true
|
||||
def persist(%{"type" => type} = object, meta) when type in @object_types do
|
||||
with {:ok, object} <- Object.create(object) do
|
||||
{:ok, object, meta}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def persist(object, meta) do
|
||||
with local <- Keyword.fetch!(meta, :local),
|
||||
{recipients, _, _} <- get_recipients(object),
|
||||
|
|
@ -123,7 +127,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
# Splice in the child object if we have one.
|
||||
activity = Maps.put_if_present(activity, :object, object)
|
||||
|
||||
BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
|
||||
ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn ->
|
||||
Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
|
||||
end)
|
||||
|
||||
{:ok, activity}
|
||||
else
|
||||
|
|
@ -219,6 +225,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
Streamer.stream("participation", participations)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def stream_out_participations(%Object{data: %{"context" => context}}, user) do
|
||||
with %Conversation{} = conversation <- Conversation.get_for_ap_id(context) do
|
||||
conversation = Repo.preload(conversation, :participations)
|
||||
|
|
@ -235,8 +242,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def stream_out_participations(_, _), do: :noop
|
||||
|
||||
@impl true
|
||||
def stream_out(%Activity{data: %{"type" => data_type}} = activity)
|
||||
when data_type in ["Create", "Announce", "Delete"] do
|
||||
activity
|
||||
|
|
@ -244,6 +253,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> Streamer.stream(activity)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def stream_out(_activity) do
|
||||
:noop
|
||||
end
|
||||
|
|
@ -332,15 +342,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
|
||||
@spec flag(map()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
def flag(
|
||||
%{
|
||||
actor: actor,
|
||||
context: _context,
|
||||
account: account,
|
||||
statuses: statuses,
|
||||
content: content
|
||||
} = params
|
||||
) do
|
||||
def flag(params) do
|
||||
with {:ok, result} <- Repo.transaction(fn -> do_flag(params) end) do
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
defp do_flag(
|
||||
%{
|
||||
actor: actor,
|
||||
context: _context,
|
||||
account: account,
|
||||
statuses: statuses,
|
||||
content: content
|
||||
} = params
|
||||
) do
|
||||
# only accept false as false value
|
||||
local = !(params[:local] == false)
|
||||
forward = !(params[:forward] == false)
|
||||
|
|
@ -358,7 +374,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
{: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(stripped_activity) do
|
||||
User.all_superusers()
|
||||
|> Enum.filter(fn user -> not is_nil(user.email) end)
|
||||
|> Enum.each(fn superuser ->
|
||||
|
|
@ -368,6 +385,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end)
|
||||
|
||||
{:ok, activity}
|
||||
else
|
||||
{:error, error} -> Repo.rollback(error)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -590,12 +609,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> Map.put(:muting_user, reading_user)
|
||||
end
|
||||
|
||||
pagination_type = Map.get(params, :pagination_type) || :keyset
|
||||
|
||||
%{
|
||||
godmode: params[:godmode],
|
||||
reading_user: reading_user
|
||||
}
|
||||
|> user_activities_recipients()
|
||||
|> fetch_activities(params)
|
||||
|> fetch_activities(params, pagination_type)
|
||||
|> Enum.reverse()
|
||||
end
|
||||
|
||||
|
|
@ -1329,12 +1350,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
def fetch_follow_information_for_user(user) do
|
||||
with {:ok, following_data} <-
|
||||
Fetcher.fetch_and_contain_remote_object_from_id(user.following_address,
|
||||
force_http: true
|
||||
),
|
||||
Fetcher.fetch_and_contain_remote_object_from_id(user.following_address),
|
||||
{:ok, hide_follows} <- collection_private(following_data),
|
||||
{:ok, followers_data} <-
|
||||
Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address, force_http: true),
|
||||
Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address),
|
||||
{:ok, hide_followers} <- collection_private(followers_data) do
|
||||
{:ok,
|
||||
%{
|
||||
|
|
@ -1408,8 +1427,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
end
|
||||
|
||||
def fetch_and_prepare_user_from_ap_id(ap_id, opts \\ []) do
|
||||
with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id, opts),
|
||||
def fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||
with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
|
||||
{:ok, data} <- user_data_from_user_object(data) do
|
||||
{:ok, maybe_update_follow_information(data)}
|
||||
else
|
||||
|
|
@ -1452,13 +1471,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
end
|
||||
|
||||
def make_user_from_ap_id(ap_id, opts \\ []) do
|
||||
def make_user_from_ap_id(ap_id) do
|
||||
user = User.get_cached_by_ap_id(ap_id)
|
||||
|
||||
if user && !User.ap_enabled?(user) do
|
||||
Transmogrifier.upgrade_user_from_ap_id(ap_id)
|
||||
else
|
||||
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, opts) do
|
||||
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||
if user do
|
||||
user
|
||||
|> User.remote_user_changeset(data)
|
||||
|
|
|
|||
7
lib/pleroma/web/activity_pub/activity_pub/persisting.ex
Normal file
7
lib/pleroma/web/activity_pub/activity_pub/persisting.ex
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ActivityPub.Persisting do
|
||||
@callback persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
|
||||
end
|
||||
12
lib/pleroma/web/activity_pub/activity_pub/streaming.ex
Normal file
12
lib/pleroma/web/activity_pub/activity_pub/streaming.ex
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ActivityPub.Streaming do
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
|
||||
@callback stream_out(Activity.t()) :: any()
|
||||
@callback stream_out_participations(Object.t(), User.t()) :: any()
|
||||
end
|
||||
|
|
@ -82,7 +82,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
def object(conn, _) do
|
||||
with ap_id <- Endpoint.url() <> conn.request_path,
|
||||
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
|
||||
{_, true} <- {:public?, Visibility.is_public?(object)} do
|
||||
{_, true} <- {:public?, Visibility.is_public?(object)},
|
||||
{_, false} <- {:local?, Visibility.is_local_public?(object)} do
|
||||
conn
|
||||
|> assign(:tracking_fun_data, object.id)
|
||||
|> set_cache_ttl_for(object)
|
||||
|
|
@ -92,6 +93,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
else
|
||||
{:public?, false} ->
|
||||
{:error, :not_found}
|
||||
|
||||
{:local?, true} ->
|
||||
{:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -108,7 +112,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
def activity(conn, _params) do
|
||||
with ap_id <- Endpoint.url() <> conn.request_path,
|
||||
%Activity{} = activity <- Activity.normalize(ap_id),
|
||||
{_, true} <- {:public?, Visibility.is_public?(activity)} do
|
||||
{_, true} <- {:public?, Visibility.is_public?(activity)},
|
||||
{_, false} <- {:local?, Visibility.is_local_public?(activity)} do
|
||||
conn
|
||||
|> maybe_set_tracking_data(activity)
|
||||
|> set_cache_ttl_for(activity)
|
||||
|
|
@ -117,6 +122,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
|> render("object.json", object: activity)
|
||||
else
|
||||
{:public?, false} -> {:error, :not_found}
|
||||
{:local?, true} -> {:error, :not_found}
|
||||
nil -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -222,6 +222,9 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
|||
actor.ap_id == Relay.ap_id() ->
|
||||
[actor.follower_address]
|
||||
|
||||
public? and Visibility.is_local_public?(object) ->
|
||||
[actor.follower_address, object.data["actor"], Pleroma.Constants.as_local_public()]
|
||||
|
||||
public? ->
|
||||
[actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,64 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF do
|
||||
require Logger
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF.PipelineFiltering
|
||||
|
||||
@mrf_config_descriptions [
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: :mrf,
|
||||
tab: :mrf,
|
||||
label: "MRF",
|
||||
type: :group,
|
||||
description: "General MRF settings",
|
||||
children: [
|
||||
%{
|
||||
key: :policies,
|
||||
type: [:module, {:list, :module}],
|
||||
description:
|
||||
"A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
|
||||
suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF}
|
||||
},
|
||||
%{
|
||||
key: :transparency,
|
||||
label: "MRF transparency",
|
||||
type: :boolean,
|
||||
description:
|
||||
"Make the content of your Message Rewrite Facility settings public (via nodeinfo)"
|
||||
},
|
||||
%{
|
||||
key: :transparency_exclusions,
|
||||
label: "MRF transparency exclusions",
|
||||
type: {:list, :string},
|
||||
description:
|
||||
"Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.",
|
||||
suggestions: [
|
||||
"exclusion.com"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@default_description %{
|
||||
label: "",
|
||||
description: ""
|
||||
}
|
||||
|
||||
@required_description_keys [:key, :related_policy]
|
||||
|
||||
@callback filter(Map.t()) :: {:ok | :reject, Map.t()}
|
||||
@callback describe() :: {:ok | :error, Map.t()}
|
||||
@callback config_description() :: %{
|
||||
optional(:children) => [map()],
|
||||
key: atom(),
|
||||
related_policy: String.t(),
|
||||
label: String.t(),
|
||||
description: String.t()
|
||||
}
|
||||
@optional_callbacks config_description: 0
|
||||
|
||||
def filter(policies, %{} = message) do
|
||||
policies
|
||||
|
|
@ -15,6 +72,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
|
|||
|
||||
def filter(%{} = object), do: get_policies() |> filter(object)
|
||||
|
||||
@impl true
|
||||
def pipeline_filter(%{} = message, meta) do
|
||||
object = meta[:object_data]
|
||||
ap_id = message["object"]
|
||||
|
|
@ -51,8 +109,6 @@ defmodule Pleroma.Web.ActivityPub.MRF do
|
|||
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
|
||||
end
|
||||
|
||||
@callback describe() :: {:ok | :error, Map.t()}
|
||||
|
||||
def describe(policies) do
|
||||
{:ok, policy_configs} =
|
||||
policies
|
||||
|
|
@ -82,4 +138,41 @@ defmodule Pleroma.Web.ActivityPub.MRF do
|
|||
end
|
||||
|
||||
def describe, do: get_policies() |> describe()
|
||||
|
||||
def config_descriptions do
|
||||
Pleroma.Web.ActivityPub.MRF
|
||||
|> Pleroma.Docs.Generator.list_behaviour_implementations()
|
||||
|> config_descriptions()
|
||||
end
|
||||
|
||||
def config_descriptions(policies) do
|
||||
Enum.reduce(policies, @mrf_config_descriptions, fn policy, acc ->
|
||||
if function_exported?(policy, :config_description, 0) do
|
||||
description =
|
||||
@default_description
|
||||
|> Map.merge(policy.config_description)
|
||||
|> Map.put(:group, :pleroma)
|
||||
|> Map.put(:tab, :mrf)
|
||||
|> Map.put(:type, :group)
|
||||
|
||||
if Enum.all?(@required_description_keys, &Map.has_key?(description, &1)) do
|
||||
[description | acc]
|
||||
else
|
||||
Logger.warn(
|
||||
"#{policy} config description doesn't have one or all required keys #{
|
||||
inspect(@required_description_keys)
|
||||
}"
|
||||
)
|
||||
|
||||
acc
|
||||
end
|
||||
else
|
||||
Logger.debug(
|
||||
"#{policy} is excluded from config descriptions, because does not implement `config_description/0` method."
|
||||
)
|
||||
|
||||
acc
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -40,4 +40,22 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do
|
|||
_ -> Map.put(activity, "expires_at", expires_at)
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def config_description do
|
||||
%{
|
||||
key: :mrf_activity_expiration,
|
||||
related_policy: "Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy",
|
||||
label: "MRF Activity Expiration Policy",
|
||||
description: "Adds automatic expiration to all local activities",
|
||||
children: [
|
||||
%{
|
||||
key: :days,
|
||||
type: :integer,
|
||||
description: "Default global expiration time for all local activities (in days)",
|
||||
suggestions: [90, 365]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -97,4 +97,31 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
|
|||
@impl true
|
||||
def describe,
|
||||
do: {:ok, %{mrf_hellthread: Pleroma.Config.get(:mrf_hellthread) |> Enum.into(%{})}}
|
||||
|
||||
@impl true
|
||||
def config_description do
|
||||
%{
|
||||
key: :mrf_hellthread,
|
||||
related_policy: "Pleroma.Web.ActivityPub.MRF.HellthreadPolicy",
|
||||
label: "MRF Hellthread",
|
||||
description: "Block messages with excessive user mentions",
|
||||
children: [
|
||||
%{
|
||||
key: :delist_threshold,
|
||||
type: :integer,
|
||||
description:
|
||||
"Number of mentioned users after which the message gets removed from timelines and" <>
|
||||
"disables notifications. Set to 0 to disable.",
|
||||
suggestions: [10]
|
||||
},
|
||||
%{
|
||||
key: :reject_threshold,
|
||||
type: :integer,
|
||||
description:
|
||||
"Number of mentioned users after which the messaged gets rejected. Set to 0 to disable.",
|
||||
suggestions: [20]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -126,4 +126,46 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
|
|||
|
||||
{:ok, %{mrf_keyword: mrf_keyword}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def config_description do
|
||||
%{
|
||||
key: :mrf_keyword,
|
||||
related_policy: "Pleroma.Web.ActivityPub.MRF.KeywordPolicy",
|
||||
label: "MRF Keyword",
|
||||
description:
|
||||
"Reject or Word-Replace messages matching a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html).",
|
||||
children: [
|
||||
%{
|
||||
key: :reject,
|
||||
type: {:list, :string},
|
||||
description: """
|
||||
A list of patterns which result in message being rejected.
|
||||
|
||||
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
|
||||
""",
|
||||
suggestions: ["foo", ~r/foo/iu]
|
||||
},
|
||||
%{
|
||||
key: :federated_timeline_removal,
|
||||
type: {:list, :string},
|
||||
description: """
|
||||
A list of patterns which result in message being removed from federated timelines (a.k.a unlisted).
|
||||
|
||||
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
|
||||
""",
|
||||
suggestions: ["foo", ~r/foo/iu]
|
||||
},
|
||||
%{
|
||||
key: :replace,
|
||||
type: {:list, :tuple},
|
||||
description: """
|
||||
**Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
|
||||
|
||||
**Replacement**: a string. Leaving the field empty is permitted.
|
||||
"""
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
|
|||
|
||||
alias Pleroma.HTTP
|
||||
alias Pleroma.Web.MediaProxy
|
||||
alias Pleroma.Workers.BackgroundWorker
|
||||
|
||||
require Logger
|
||||
|
||||
|
|
@ -17,7 +16,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
|
|||
recv_timeout: 10_000
|
||||
]
|
||||
|
||||
def perform(:prefetch, url) do
|
||||
defp prefetch(url) do
|
||||
# Fetching only proxiable resources
|
||||
if MediaProxy.enabled?() and MediaProxy.url_proxiable?(url) do
|
||||
# If preview proxy is enabled, it'll also hit media proxy (so we're caching both requests)
|
||||
|
|
@ -25,17 +24,25 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
|
|||
|
||||
Logger.debug("Prefetching #{inspect(url)} as #{inspect(prefetch_url)}")
|
||||
|
||||
HTTP.get(prefetch_url, [], @adapter_options)
|
||||
if Pleroma.Config.get(:env) == :test do
|
||||
fetch(prefetch_url)
|
||||
else
|
||||
ConcurrentLimiter.limit(MediaProxy, fn ->
|
||||
Task.start(fn -> fetch(prefetch_url) end)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do
|
||||
defp fetch(url), do: HTTP.get(url, [], @adapter_options)
|
||||
|
||||
defp preload(%{"object" => %{"attachment" => attachments}} = _message) do
|
||||
Enum.each(attachments, fn
|
||||
%{"url" => url} when is_list(url) ->
|
||||
url
|
||||
|> Enum.each(fn
|
||||
%{"href" => href} ->
|
||||
BackgroundWorker.enqueue("media_proxy_prefetch", %{"url" => href})
|
||||
prefetch(href)
|
||||
|
||||
x ->
|
||||
Logger.debug("Unhandled attachment URL object #{inspect(x)}")
|
||||
|
|
@ -51,7 +58,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
|
|||
%{"type" => "Create", "object" => %{"attachment" => attachments} = _object} = message
|
||||
)
|
||||
when is_list(attachments) and length(attachments) > 0 do
|
||||
BackgroundWorker.enqueue("media_proxy_preload", %{"message" => message})
|
||||
preload(message)
|
||||
|
||||
{:ok, message}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,4 +25,22 @@ defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
|
|||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
|
||||
@impl true
|
||||
def config_description do
|
||||
%{
|
||||
key: :mrf_mention,
|
||||
related_policy: "Pleroma.Web.ActivityPub.MRF.MentionPolicy",
|
||||
label: "MRF Mention",
|
||||
description: "Block messages which mention a specific user",
|
||||
children: [
|
||||
%{
|
||||
key: :actors,
|
||||
type: {:list, :string},
|
||||
description: "A list of actors for which any post mentioning them will be dropped",
|
||||
suggestions: ["actor1", "actor2"]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
|
|||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@impl true
|
||||
def filter(%{"type" => "Create", "object" => child_object} = object) do
|
||||
scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy])
|
||||
|
||||
|
|
@ -22,5 +23,23 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
|
|||
|
||||
def filter(object), do: {:ok, object}
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
|
||||
@impl true
|
||||
def config_description do
|
||||
%{
|
||||
key: :mrf_normalize_markup,
|
||||
related_policy: "Pleroma.Web.ActivityPub.MRF.NormalizeMarkup",
|
||||
label: "MRF Normalize Markup",
|
||||
description: "MRF NormalizeMarkup settings. Scrub configured hypertext markup.",
|
||||
children: [
|
||||
%{
|
||||
key: :scrub_policy,
|
||||
type: :module,
|
||||
suggestions: [Pleroma.HTML.Scrubber.Default]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -106,4 +106,32 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
|
|||
|
||||
{:ok, %{mrf_object_age: mrf_object_age}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def config_description do
|
||||
%{
|
||||
key: :mrf_object_age,
|
||||
related_policy: "Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy",
|
||||
label: "MRF Object Age",
|
||||
description:
|
||||
"Rejects or delists posts based on their timestamp deviance from your server's clock.",
|
||||
children: [
|
||||
%{
|
||||
key: :threshold,
|
||||
type: :integer,
|
||||
description: "Required age (in seconds) of a post before actions are taken.",
|
||||
suggestions: [172_800]
|
||||
},
|
||||
%{
|
||||
key: :actions,
|
||||
type: {:list, :atom},
|
||||
description:
|
||||
"A list of actions to apply to the post. `:delist` removes the post from public timelines; " <>
|
||||
"`:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines; " <>
|
||||
"`:reject` rejects the message entirely",
|
||||
suggestions: [:delist, :strip_followers, :reject]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
7
lib/pleroma/web/activity_pub/mrf/pipeline_filtering.ex
Normal file
7
lib/pleroma/web/activity_pub/mrf/pipeline_filtering.ex
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.PipelineFiltering do
|
||||
@callback pipeline_filter(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
||||
end
|
||||
|
|
@ -48,4 +48,27 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
|
|||
@impl true
|
||||
def describe,
|
||||
do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
|
||||
|
||||
@impl true
|
||||
def config_description do
|
||||
%{
|
||||
key: :mrf_rejectnonpublic,
|
||||
related_policy: "Pleroma.Web.ActivityPub.MRF.RejectNonPublic",
|
||||
description: "RejectNonPublic drops posts with non-public visibility settings.",
|
||||
label: "MRF Reject Non Public",
|
||||
children: [
|
||||
%{
|
||||
key: :allow_followersonly,
|
||||
label: "Allow followers-only",
|
||||
type: :boolean,
|
||||
description: "Whether to allow followers-only posts"
|
||||
},
|
||||
%{
|
||||
key: :allow_direct,
|
||||
type: :boolean,
|
||||
description: "Whether to allow direct messages"
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -244,4 +244,78 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
|
||||
{:ok, %{mrf_simple: mrf_simple}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def config_description do
|
||||
%{
|
||||
key: :mrf_simple,
|
||||
related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy",
|
||||
label: "MRF Simple",
|
||||
description: "Simple ingress policies",
|
||||
children: [
|
||||
%{
|
||||
key: :media_removal,
|
||||
type: {:list, :string},
|
||||
description: "List of instances to strip media attachments from",
|
||||
suggestions: ["example.com", "*.example.com"]
|
||||
},
|
||||
%{
|
||||
key: :media_nsfw,
|
||||
label: "Media NSFW",
|
||||
type: {:list, :string},
|
||||
description: "List of instances to tag all media as NSFW (sensitive) from",
|
||||
suggestions: ["example.com", "*.example.com"]
|
||||
},
|
||||
%{
|
||||
key: :federated_timeline_removal,
|
||||
type: {:list, :string},
|
||||
description:
|
||||
"List of instances to remove from the Federated (aka The Whole Known Network) Timeline",
|
||||
suggestions: ["example.com", "*.example.com"]
|
||||
},
|
||||
%{
|
||||
key: :reject,
|
||||
type: {:list, :string},
|
||||
description: "List of instances to reject activities from (except deletes)",
|
||||
suggestions: ["example.com", "*.example.com"]
|
||||
},
|
||||
%{
|
||||
key: :accept,
|
||||
type: {:list, :string},
|
||||
description: "List of instances to only accept activities from (except deletes)",
|
||||
suggestions: ["example.com", "*.example.com"]
|
||||
},
|
||||
%{
|
||||
key: :followers_only,
|
||||
type: {:list, :string},
|
||||
description: "Force posts from the given instances to be visible by followers only",
|
||||
suggestions: ["example.com", "*.example.com"]
|
||||
},
|
||||
%{
|
||||
key: :report_removal,
|
||||
type: {:list, :string},
|
||||
description: "List of instances to reject reports from",
|
||||
suggestions: ["example.com", "*.example.com"]
|
||||
},
|
||||
%{
|
||||
key: :avatar_removal,
|
||||
type: {:list, :string},
|
||||
description: "List of instances to strip avatars from",
|
||||
suggestions: ["example.com", "*.example.com"]
|
||||
},
|
||||
%{
|
||||
key: :banner_removal,
|
||||
type: {:list, :string},
|
||||
description: "List of instances to strip banners from",
|
||||
suggestions: ["example.com", "*.example.com"]
|
||||
},
|
||||
%{
|
||||
key: :reject_deletes,
|
||||
type: {:list, :string},
|
||||
description: "List of instances to reject deletions from",
|
||||
suggestions: ["example.com", "*.example.com"]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -39,4 +39,28 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
|
|||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
|
||||
@impl true
|
||||
def config_description do
|
||||
%{
|
||||
key: :mrf_subchain,
|
||||
related_policy: "Pleroma.Web.ActivityPub.MRF.SubchainPolicy",
|
||||
label: "MRF Subchain",
|
||||
description:
|
||||
"This policy processes messages through an alternate pipeline when a given message matches certain criteria." <>
|
||||
" All criteria are configured as a map of regular expressions to lists of policy modules.",
|
||||
children: [
|
||||
%{
|
||||
key: :match_actor,
|
||||
type: {:map, {:list, :string}},
|
||||
description: "Matches a series of regular expressions against the actor field",
|
||||
suggestions: [
|
||||
%{
|
||||
~r/https:\/\/example.com/s => [Pleroma.Web.ActivityPub.MRF.DropPolicy]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -41,4 +41,25 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
|
|||
|
||||
{:ok, %{mrf_user_allowlist: mrf_user_allowlist}}
|
||||
end
|
||||
|
||||
# TODO: change way of getting settings on `lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex:18` to use `hosts` subkey
|
||||
# @impl true
|
||||
# def config_description do
|
||||
# %{
|
||||
# key: :mrf_user_allowlist,
|
||||
# related_policy: "Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy",
|
||||
# description: "Accept-list of users from specified instances",
|
||||
# children: [
|
||||
# %{
|
||||
# key: :hosts,
|
||||
# type: :map,
|
||||
# description:
|
||||
# "The keys in this section are the domain names that the policy should apply to." <>
|
||||
# " Each key should be assigned a list of users that should be allowed " <>
|
||||
# "through by their ActivityPub ID",
|
||||
# suggestions: [%{"example.org" => ["https://example.org/users/admin"]}]
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
# end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
|
|||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@impl true
|
||||
def filter(%{"type" => "Undo", "object" => child_message} = message) do
|
||||
with {:ok, _} <- filter(child_message) do
|
||||
{:ok, message}
|
||||
|
|
@ -36,6 +37,33 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
|
|||
|
||||
def filter(message), do: {:ok, message}
|
||||
|
||||
@impl true
|
||||
def describe,
|
||||
do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary) |> Enum.into(%{})}}
|
||||
|
||||
@impl true
|
||||
def config_description do
|
||||
%{
|
||||
key: :mrf_vocabulary,
|
||||
related_policy: "Pleroma.Web.ActivityPub.MRF.VocabularyPolicy",
|
||||
label: "MRF Vocabulary",
|
||||
description: "Filter messages which belong to certain activity vocabularies",
|
||||
children: [
|
||||
%{
|
||||
key: :accept,
|
||||
type: {:list, :string},
|
||||
description:
|
||||
"A list of ActivityStreams terms to accept. If empty, all supported messages are accepted.",
|
||||
suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
|
||||
},
|
||||
%{
|
||||
key: :reject,
|
||||
type: {:list, :string},
|
||||
description:
|
||||
"A list of ActivityStreams terms to reject. If empty, no messages are rejected.",
|
||||
suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
the system.
|
||||
"""
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Object
|
||||
|
|
@ -32,7 +34,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
|
||||
|
||||
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
||||
@impl true
|
||||
def validate(object, meta)
|
||||
|
||||
def validate(%{"type" => type} = object, meta)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidator.Validating do
|
||||
@callback validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
||||
end
|
||||
|
|
@ -67,7 +67,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
|
|||
%Object{} = object <- Object.get_cached_by_ap_id(object),
|
||||
false <- Visibility.is_public?(object) do
|
||||
same_actor = object.data["actor"] == actor.ap_id
|
||||
is_public = Pleroma.Constants.as_public() in (get_field(cng, :to) ++ get_field(cng, :cc))
|
||||
recipients = get_field(cng, :to) ++ get_field(cng, :cc)
|
||||
local_public = Pleroma.Constants.as_local_public()
|
||||
|
||||
is_public =
|
||||
Enum.member?(recipients, Pleroma.Constants.as_public()) or
|
||||
Enum.member?(recipients, local_public)
|
||||
|
||||
cond do
|
||||
same_actor && is_public ->
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
|
|||
field(:type, :string)
|
||||
field(:mediaType, :string, default: "application/octet-stream")
|
||||
field(:name, :string)
|
||||
field(:blurhash, :string)
|
||||
|
||||
embeds_many :url, UrlObjectValidator, primary_key: false do
|
||||
field(:type, :string)
|
||||
|
|
@ -41,7 +42,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
|
|||
|> fix_url()
|
||||
|
||||
struct
|
||||
|> cast(data, [:type, :mediaType, :name])
|
||||
|> cast(data, [:type, :mediaType, :name, :blurhash])
|
||||
|> cast_embed(:url, with: &url_changeset/2)
|
||||
|> validate_inclusion(:type, ~w[Link Document Audio Image Video])
|
||||
|> validate_required([:type, :mediaType, :url])
|
||||
|
|
|
|||
|
|
@ -11,14 +11,22 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
|
|||
alias Pleroma.Web.ActivityPub.MRF
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
alias Pleroma.Web.ActivityPub.SideEffects
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.Federator
|
||||
|
||||
@side_effects Config.get([:pipeline, :side_effects], SideEffects)
|
||||
@federator Config.get([:pipeline, :federator], Federator)
|
||||
@object_validator Config.get([:pipeline, :object_validator], ObjectValidator)
|
||||
@mrf Config.get([:pipeline, :mrf], MRF)
|
||||
@activity_pub Config.get([:pipeline, :activity_pub], ActivityPub)
|
||||
@config Config.get([:pipeline, :config], Config)
|
||||
|
||||
@spec common_pipeline(map(), keyword()) ::
|
||||
{:ok, Activity.t() | Object.t(), keyword()} | {:error, any()}
|
||||
def common_pipeline(object, meta) do
|
||||
case Repo.transaction(fn -> do_common_pipeline(object, meta) end) do
|
||||
{:ok, {:ok, activity, meta}} ->
|
||||
SideEffects.handle_after_transaction(meta)
|
||||
@side_effects.handle_after_transaction(meta)
|
||||
{:ok, activity, meta}
|
||||
|
||||
{:ok, value} ->
|
||||
|
|
@ -34,13 +42,13 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
|
|||
|
||||
def do_common_pipeline(object, meta) do
|
||||
with {_, {:ok, validated_object, meta}} <-
|
||||
{:validate_object, ObjectValidator.validate(object, meta)},
|
||||
{:validate_object, @object_validator.validate(object, meta)},
|
||||
{_, {:ok, mrfd_object, meta}} <-
|
||||
{:mrf_object, MRF.pipeline_filter(validated_object, meta)},
|
||||
{:mrf_object, @mrf.pipeline_filter(validated_object, meta)},
|
||||
{_, {:ok, activity, meta}} <-
|
||||
{:persist_object, ActivityPub.persist(mrfd_object, meta)},
|
||||
{:persist_object, @activity_pub.persist(mrfd_object, meta)},
|
||||
{_, {:ok, activity, meta}} <-
|
||||
{:execute_side_effects, SideEffects.handle(activity, meta)},
|
||||
{:execute_side_effects, @side_effects.handle(activity, meta)},
|
||||
{_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do
|
||||
{:ok, activity, meta}
|
||||
else
|
||||
|
|
@ -53,9 +61,9 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
|
|||
|
||||
defp maybe_federate(%Activity{} = activity, meta) do
|
||||
with {:ok, local} <- Keyword.fetch(meta, :local) do
|
||||
do_not_federate = meta[:do_not_federate] || !Config.get([:instance, :federating])
|
||||
do_not_federate = meta[:do_not_federate] || !@config.get([:instance, :federating])
|
||||
|
||||
if !do_not_federate && local do
|
||||
if !do_not_federate and local and not Visibility.is_local_public?(activity) do
|
||||
activity =
|
||||
if object = Keyword.get(meta, :object_data) do
|
||||
%{activity | data: Map.put(activity.data, "object", object)}
|
||||
|
|
@ -63,7 +71,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
|
|||
activity
|
||||
end
|
||||
|
||||
Federator.publish(activity)
|
||||
@federator.publish(activity)
|
||||
{:ok, :federated}
|
||||
else
|
||||
{:ok, :not_federated}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.FedSockets
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
|
|
@ -50,28 +49,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
"""
|
||||
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
|
||||
Logger.debug("Federating #{id} to #{inbox}")
|
||||
|
||||
case FedSockets.get_or_create_fed_socket(inbox) do
|
||||
{:ok, fedsocket} ->
|
||||
Logger.debug("publishing via fedsockets - #{inspect(inbox)}")
|
||||
FedSockets.publish(fedsocket, json)
|
||||
|
||||
_ ->
|
||||
Logger.debug("publishing via http - #{inspect(inbox)}")
|
||||
http_publish(inbox, actor, json, params)
|
||||
end
|
||||
end
|
||||
|
||||
def publish_one(%{actor_id: actor_id} = params) do
|
||||
actor = User.get_cached_by_id(actor_id)
|
||||
|
||||
params
|
||||
|> Map.delete(:actor_id)
|
||||
|> Map.put(:actor, actor)
|
||||
|> publish_one()
|
||||
end
|
||||
|
||||
defp http_publish(inbox, actor, json, params) do
|
||||
uri = %{path: path} = URI.parse(inbox)
|
||||
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
|
||||
|
||||
|
|
@ -110,6 +87,15 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
end
|
||||
end
|
||||
|
||||
def publish_one(%{actor_id: actor_id} = params) do
|
||||
actor = User.get_cached_by_id(actor_id)
|
||||
|
||||
params
|
||||
|> Map.delete(:actor_id)
|
||||
|> Map.put(:actor, actor)
|
||||
|> publish_one()
|
||||
end
|
||||
|
||||
defp signature_host(%URI{port: port, scheme: scheme, host: host}) do
|
||||
if port == URI.default_port(scheme) do
|
||||
host
|
||||
|
|
|
|||
|
|
@ -24,15 +24,22 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.Push
|
||||
alias Pleroma.Web.Streamer
|
||||
alias Pleroma.Workers.BackgroundWorker
|
||||
|
||||
require Logger
|
||||
|
||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||
@ap_streamer Pleroma.Config.get([:side_effects, :ap_streamer], ActivityPub)
|
||||
@logger Pleroma.Config.get([:side_effects, :logger], Logger)
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.SideEffects.Handling
|
||||
|
||||
@impl true
|
||||
def handle(object, meta \\ [])
|
||||
|
||||
# Task this handles
|
||||
# - Follows
|
||||
# - Sends a notification
|
||||
@impl true
|
||||
def handle(
|
||||
%{
|
||||
data: %{
|
||||
|
|
@ -48,10 +55,9 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
%User{} = followed <- User.get_cached_by_ap_id(actor),
|
||||
%User{} = follower <- User.get_cached_by_ap_id(follower_id),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do
|
||||
{:ok, _follower, followed} <-
|
||||
FollowingRelationship.update(follower, followed, :follow_accept) do
|
||||
Notification.update_notification_type(followed, follow_activity)
|
||||
User.update_follower_count(followed)
|
||||
User.update_following_count(follower)
|
||||
end
|
||||
|
||||
{:ok, object, meta}
|
||||
|
|
@ -61,6 +67,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
# - Rejects all existing follow activities for this person
|
||||
# - Updates the follow state
|
||||
# - Dismisses notification
|
||||
@impl true
|
||||
def handle(
|
||||
%{
|
||||
data: %{
|
||||
|
|
@ -87,6 +94,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
# - Follows if possible
|
||||
# - Sends a notification
|
||||
# - Generates accept or reject if appropriate
|
||||
@impl true
|
||||
def handle(
|
||||
%{
|
||||
data: %{
|
||||
|
|
@ -100,7 +108,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
) do
|
||||
with %User{} = follower <- User.get_cached_by_ap_id(following_user),
|
||||
%User{} = followed <- User.get_cached_by_ap_id(followed_user),
|
||||
{_, {:ok, _}, _, _} <-
|
||||
{_, {:ok, _, _}, _, _} <-
|
||||
{:following, User.follow(follower, followed, :follow_pending), follower, followed} do
|
||||
if followed.local && !followed.is_locked do
|
||||
{:ok, accept_data, _} = Builder.accept(followed, object)
|
||||
|
|
@ -128,6 +136,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
|
||||
# Tasks this handles:
|
||||
# - Unfollow and block
|
||||
@impl true
|
||||
def handle(
|
||||
%{data: %{"type" => "Block", "object" => blocked_user, "actor" => blocking_user}} =
|
||||
object,
|
||||
|
|
@ -146,6 +155,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
#
|
||||
# For a local user, we also get a changeset with the full information, so we
|
||||
# can update non-federating, non-activitypub settings as well.
|
||||
@impl true
|
||||
def handle(%{data: %{"type" => "Update", "object" => updated_object}} = object, meta) do
|
||||
if changeset = Keyword.get(meta, :user_update_changeset) do
|
||||
changeset
|
||||
|
|
@ -164,6 +174,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
# Tasks this handles:
|
||||
# - Add like to object
|
||||
# - Set up notification
|
||||
@impl true
|
||||
def handle(%{data: %{"type" => "Like"}} = object, meta) do
|
||||
liked_object = Object.get_by_ap_id(object.data["object"])
|
||||
Utils.add_like_to_object(object, liked_object)
|
||||
|
|
@ -181,6 +192,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
# - Increase replies count
|
||||
# - Set up ActivityExpiration
|
||||
# - Set up notifications
|
||||
@impl true
|
||||
def handle(%{data: %{"type" => "Create"}} = activity, meta) do
|
||||
with {:ok, object, meta} <- handle_object_creation(meta[:object_data], meta),
|
||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||
|
|
@ -191,7 +203,9 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
Object.increase_replies_count(in_reply_to)
|
||||
end
|
||||
|
||||
BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
|
||||
ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn ->
|
||||
Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
|
||||
end)
|
||||
|
||||
meta =
|
||||
meta
|
||||
|
|
@ -207,6 +221,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
# - Add announce to object
|
||||
# - Set up notification
|
||||
# - Stream out the announce
|
||||
@impl true
|
||||
def handle(%{data: %{"type" => "Announce"}} = object, meta) do
|
||||
announced_object = Object.get_by_ap_id(object.data["object"])
|
||||
user = User.get_cached_by_ap_id(object.data["actor"])
|
||||
|
|
@ -224,6 +239,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
{:ok, object, meta}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle(%{data: %{"type" => "Undo", "object" => undone_object}} = object, meta) do
|
||||
with undone_object <- Activity.get_by_ap_id(undone_object),
|
||||
:ok <- handle_undoing(undone_object) do
|
||||
|
|
@ -234,6 +250,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
# Tasks this handles:
|
||||
# - Add reaction to object
|
||||
# - Set up notification
|
||||
@impl true
|
||||
def handle(%{data: %{"type" => "EmojiReact"}} = object, meta) do
|
||||
reacted_object = Object.get_by_ap_id(object.data["object"])
|
||||
Utils.add_emoji_reaction_to_object(object, reacted_object)
|
||||
|
|
@ -250,6 +267,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
# - Reduce the user note count
|
||||
# - Reduce the reply count
|
||||
# - Stream out the activity
|
||||
@impl true
|
||||
def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do
|
||||
deleted_object =
|
||||
Object.normalize(deleted_object, false) ||
|
||||
|
|
@ -271,12 +289,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
|
||||
MessageReference.delete_for_object(deleted_object)
|
||||
|
||||
ActivityPub.stream_out(object)
|
||||
ActivityPub.stream_out_participations(deleted_object, user)
|
||||
@ap_streamer.stream_out(object)
|
||||
@ap_streamer.stream_out_participations(deleted_object, user)
|
||||
:ok
|
||||
else
|
||||
{:actor, _} ->
|
||||
Logger.error("The object doesn't have an actor: #{inspect(deleted_object)}")
|
||||
@logger.error("The object doesn't have an actor: #{inspect(deleted_object)}")
|
||||
:no_object_actor
|
||||
end
|
||||
|
||||
|
|
@ -295,6 +313,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
end
|
||||
|
||||
# Nothing to do
|
||||
@impl true
|
||||
def handle(object, meta) do
|
||||
{:ok, object, meta}
|
||||
end
|
||||
|
|
@ -312,7 +331,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
{:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
|
||||
{:ok, cm_ref} = MessageReference.create(chat, object, user.ap_id != actor.ap_id)
|
||||
|
||||
Cachex.put(
|
||||
@cachex.put(
|
||||
:chat_message_id_idempotency_key_cache,
|
||||
cm_ref.id,
|
||||
meta[:idempotency_key]
|
||||
|
|
@ -439,6 +458,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
|> Keyword.put(:notifications, notifications ++ existing)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_after_transaction(meta) do
|
||||
meta
|
||||
|> send_notifications()
|
||||
|
|
|
|||
8
lib/pleroma/web/activity_pub/side_effects/handling.ex
Normal file
8
lib/pleroma/web/activity_pub/side_effects/handling.ex
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.SideEffects.Handling do
|
||||
@callback handle(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
||||
@callback handle_after_transaction(map()) :: map()
|
||||
end
|
||||
|
|
@ -252,6 +252,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
}
|
||||
|> Maps.put_if_present("mediaType", media_type)
|
||||
|> Maps.put_if_present("name", data["name"])
|
||||
|> Maps.put_if_present("blurhash", data["blurhash"])
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
|
@ -918,7 +919,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|
||||
defp build_emoji_tag({name, url}) do
|
||||
%{
|
||||
"icon" => %{"url" => url, "type" => "Image"},
|
||||
"icon" => %{"url" => "#{URI.encode(url)}", "type" => "Image"},
|
||||
"name" => ":" <> name <> ":",
|
||||
"type" => "Emoji",
|
||||
"updated" => "1970-01-01T00:00:00Z",
|
||||
|
|
@ -1007,7 +1008,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|
||||
def upgrade_user_from_ap_id(ap_id) do
|
||||
with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),
|
||||
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id, force_http: true),
|
||||
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
|
||||
{:ok, user} <- update_user(user, data) do
|
||||
TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
|
||||
{:ok, user}
|
||||
|
|
|
|||
|
|
@ -175,7 +175,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
outgoing_blocks = Config.get([:activitypub, :outgoing_blocks])
|
||||
|
||||
with true <- Config.get!([:instance, :federating]),
|
||||
true <- type != "Block" || outgoing_blocks do
|
||||
true <- type != "Block" || outgoing_blocks,
|
||||
false <- Visibility.is_local_public?(activity) do
|
||||
Pleroma.Web.Federator.publish(activity)
|
||||
end
|
||||
|
||||
|
|
@ -701,14 +702,30 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
|
||||
def make_flag_data(_, _), do: %{}
|
||||
|
||||
defp build_flag_object(%{account: account, statuses: statuses} = _) do
|
||||
[account.ap_id] ++ build_flag_object(%{statuses: statuses})
|
||||
defp build_flag_object(%{account: account, statuses: statuses}) do
|
||||
[account.ap_id | build_flag_object(%{statuses: statuses})]
|
||||
end
|
||||
|
||||
defp build_flag_object(%{statuses: statuses}) do
|
||||
Enum.map(statuses || [], &build_flag_object/1)
|
||||
end
|
||||
|
||||
defp build_flag_object(%Activity{data: %{"id" => id}, object: %{data: data}}) do
|
||||
activity_actor = User.get_by_ap_id(data["actor"])
|
||||
|
||||
%{
|
||||
"type" => "Note",
|
||||
"id" => id,
|
||||
"content" => data["content"],
|
||||
"published" => data["published"],
|
||||
"actor" =>
|
||||
AccountView.render(
|
||||
"show.json",
|
||||
%{user: activity_actor, skip_visibility_check: true}
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
defp build_flag_object(act) when is_map(act) or is_binary(act) do
|
||||
id =
|
||||
case act do
|
||||
|
|
@ -719,22 +736,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
|
||||
case Activity.get_by_ap_id_with_object(id) do
|
||||
%Activity{} = activity ->
|
||||
activity_actor = User.get_by_ap_id(activity.object.data["actor"])
|
||||
build_flag_object(activity)
|
||||
|
||||
%{
|
||||
"type" => "Note",
|
||||
"id" => activity.data["id"],
|
||||
"content" => activity.object.data["content"],
|
||||
"published" => activity.object.data["published"],
|
||||
"actor" =>
|
||||
AccountView.render(
|
||||
"show.json",
|
||||
%{user: activity_actor, skip_visibility_check: true}
|
||||
)
|
||||
}
|
||||
|
||||
_ ->
|
||||
%{"id" => id, "deleted" => true}
|
||||
nil ->
|
||||
if activity = Activity.get_by_object_ap_id_with_object(id) do
|
||||
build_flag_object(activity)
|
||||
else
|
||||
%{"id" => id, "deleted" => true}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -110,8 +110,10 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
"endpoints" => endpoints,
|
||||
"attachment" => fields,
|
||||
"tag" => emoji_tags,
|
||||
# Note: key name is indeed "discoverable" (not an error)
|
||||
"discoverable" => user.is_discoverable,
|
||||
"capabilities" => capabilities
|
||||
"capabilities" => capabilities,
|
||||
"alsoKnownAs" => user.also_known_as
|
||||
}
|
||||
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|
||||
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
|
||||
|
|
|
|||
|
|
@ -17,7 +17,19 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
|
|||
def is_public?(%Activity{data: %{"type" => "Move"}}), do: true
|
||||
def is_public?(%Activity{data: data}), do: is_public?(data)
|
||||
def is_public?(%{"directMessage" => true}), do: false
|
||||
def is_public?(data), do: Utils.label_in_message?(Pleroma.Constants.as_public(), data)
|
||||
|
||||
def is_public?(data) do
|
||||
Utils.label_in_message?(Pleroma.Constants.as_public(), data) or
|
||||
Utils.label_in_message?(Pleroma.Constants.as_local_public(), data)
|
||||
end
|
||||
|
||||
def is_local_public?(%Object{data: data}), do: is_local_public?(data)
|
||||
def is_local_public?(%Activity{data: data}), do: is_local_public?(data)
|
||||
|
||||
def is_local_public?(data) do
|
||||
Utils.label_in_message?(Pleroma.Constants.as_local_public(), data) and
|
||||
not Utils.label_in_message?(Pleroma.Constants.as_public(), data)
|
||||
end
|
||||
|
||||
def is_private?(activity) do
|
||||
with false <- is_public?(activity),
|
||||
|
|
@ -114,6 +126,9 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
|
|||
Pleroma.Constants.as_public() in cc ->
|
||||
"unlisted"
|
||||
|
||||
Pleroma.Constants.as_local_public() in to ->
|
||||
"local"
|
||||
|
||||
# this should use the sql for the object's activity
|
||||
Enum.any?(to, &String.contains?(&1, "/followers")) ->
|
||||
"private"
|
||||
|
|
|
|||
|
|
@ -103,13 +103,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
godmode = params["godmode"] == "true" || params["godmode"] == true
|
||||
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
|
||||
{_, page_size} = page_params(params)
|
||||
{page, page_size} = page_params(params)
|
||||
|
||||
activities =
|
||||
ActivityPub.fetch_user_activities(user, nil, %{
|
||||
limit: page_size,
|
||||
offset: (page - 1) * page_size,
|
||||
godmode: godmode,
|
||||
exclude_reblogs: not with_reblogs
|
||||
exclude_reblogs: not with_reblogs,
|
||||
pagination_type: :offset
|
||||
})
|
||||
|
||||
conn
|
||||
|
|
@ -415,7 +417,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
|
||||
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
|
||||
|
||||
User.toggle_confirmation(users)
|
||||
User.confirm(users)
|
||||
|
||||
ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"})
|
||||
|
||||
|
|
|
|||
40
lib/pleroma/web/admin_api/controllers/frontend_controller.ex
Normal file
40
lib/pleroma/web/admin_api/controllers/frontend_controller.ex
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.FrontendController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :install)
|
||||
plug(OAuthScopesPlug, %{scopes: ["read"], admin: true} when action == :index)
|
||||
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.FrontendOperation
|
||||
|
||||
def index(conn, _params) do
|
||||
installed = installed()
|
||||
|
||||
frontends =
|
||||
[:frontends, :available]
|
||||
|> Config.get([])
|
||||
|> Enum.map(fn {name, desc} ->
|
||||
Map.put(desc, "installed", name in installed)
|
||||
end)
|
||||
|
||||
render(conn, "index.json", frontends: frontends)
|
||||
end
|
||||
|
||||
def install(%{body_params: params} = conn, _params) do
|
||||
with :ok <- Pleroma.Frontend.install(params.name, Map.delete(params, :name)) do
|
||||
index(conn, %{})
|
||||
end
|
||||
end
|
||||
|
||||
defp installed do
|
||||
File.ls!(Pleroma.Frontend.dir())
|
||||
end
|
||||
end
|
||||
|
|
@ -9,6 +9,8 @@ defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do
|
|||
alias Pleroma.Web.MediaProxy
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
|
||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
|
||||
plug(
|
||||
|
|
@ -38,7 +40,7 @@ defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do
|
|||
|
||||
defp fetch_entries(params) do
|
||||
MediaProxy.cache_table()
|
||||
|> Cachex.stream!(Cachex.Query.create(true, :key))
|
||||
|> @cachex.stream!(Cachex.Query.create(true, :key))
|
||||
|> filter_entries(params[:query])
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -50,10 +50,13 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
|
|||
Enum.map(reports, fn report ->
|
||||
case CommonAPI.update_report_state(report.id, report.state) do
|
||||
{:ok, activity} ->
|
||||
report = Activity.get_by_id_with_user_actor(activity.id)
|
||||
|
||||
ModerationLog.insert_log(%{
|
||||
action: "report_update",
|
||||
actor: admin,
|
||||
subject: activity
|
||||
subject: activity,
|
||||
subject_actor: report.user_actor
|
||||
})
|
||||
|
||||
activity
|
||||
|
|
@ -73,11 +76,13 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
|
|||
def notes_create(%{assigns: %{user: user}, body_params: %{content: content}} = conn, %{
|
||||
id: report_id
|
||||
}) do
|
||||
with {:ok, _} <- ReportNote.create(user.id, report_id, content) do
|
||||
with {:ok, _} <- ReportNote.create(user.id, report_id, content),
|
||||
report <- Activity.get_by_id_with_user_actor(report_id) do
|
||||
ModerationLog.insert_log(%{
|
||||
action: "report_note",
|
||||
actor: user,
|
||||
subject: Activity.get_by_id(report_id),
|
||||
subject: report,
|
||||
subject_actor: report.user_actor,
|
||||
text: content
|
||||
})
|
||||
|
||||
|
|
@ -91,11 +96,13 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
|
|||
id: note_id,
|
||||
report_id: report_id
|
||||
}) do
|
||||
with {:ok, note} <- ReportNote.destroy(note_id) do
|
||||
with {:ok, note} <- ReportNote.destroy(note_id),
|
||||
report <- Activity.get_by_id_with_user_actor(report_id) do
|
||||
ModerationLog.insert_log(%{
|
||||
action: "report_note_delete",
|
||||
actor: user,
|
||||
subject: Activity.get_by_id(report_id),
|
||||
subject: report,
|
||||
subject_actor: report.user_actor,
|
||||
text: note.content
|
||||
})
|
||||
|
||||
|
|
|
|||
21
lib/pleroma/web/admin_api/views/frontend_view.ex
Normal file
21
lib/pleroma/web/admin_api/views/frontend_view.ex
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.FrontendView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
def render("index.json", %{frontends: frontends}) do
|
||||
render_many(frontends, __MODULE__, "show.json")
|
||||
end
|
||||
|
||||
def render("show.json", %{frontend: frontend}) do
|
||||
%{
|
||||
name: frontend["name"],
|
||||
git: frontend["git"],
|
||||
build_url: frontend["build_url"],
|
||||
ref: frontend["ref"],
|
||||
installed: frontend["installed"]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -21,6 +21,7 @@ defmodule Pleroma.Web.AdminAPI.ModerationLogView do
|
|||
|> DateTime.to_unix()
|
||||
|
||||
%{
|
||||
id: log_entry.id,
|
||||
data: log_entry.data,
|
||||
time: time,
|
||||
message: ModerationLog.get_log_entry_message(log_entry)
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
|
|||
reports:
|
||||
reports[:items]
|
||||
|> Enum.map(&Report.extract_report_info/1)
|
||||
|> Enum.map(&render(__MODULE__, "show.json", &1))
|
||||
|> Enum.reverse(),
|
||||
|> Enum.map(&render(__MODULE__, "show.json", &1)),
|
||||
total: reports[:total]
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -139,6 +139,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
:query,
|
||||
%Schema{type: :array, items: VisibilityScope},
|
||||
"Exclude visibilities"
|
||||
),
|
||||
Operation.parameter(
|
||||
:with_muted,
|
||||
:query,
|
||||
BooleanLike,
|
||||
"Include reactions from muted acccounts."
|
||||
)
|
||||
] ++ pagination_params(),
|
||||
responses: %{
|
||||
|
|
@ -262,6 +268,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
:query,
|
||||
%Schema{allOf: [BooleanLike], default: true},
|
||||
"Mute notifications in addition to statuses? Defaults to `true`."
|
||||
),
|
||||
Operation.parameter(
|
||||
:expires_in,
|
||||
:query,
|
||||
%Schema{type: :integer, default: 0},
|
||||
"Expire the mute in `expires_in` seconds. Default 0 for infinity"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
|
|
@ -602,6 +614,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
nullable: true,
|
||||
description: "Allows automatically follow moved following accounts"
|
||||
},
|
||||
also_known_as: %Schema{
|
||||
type: :array,
|
||||
items: %Schema{type: :string},
|
||||
nullable: true,
|
||||
description: "List of alternate ActivityPub IDs"
|
||||
},
|
||||
pleroma_background_image: %Schema{
|
||||
type: :string,
|
||||
nullable: true,
|
||||
|
|
@ -612,7 +630,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description:
|
||||
"Discovery of this account in search results and other services is allowed."
|
||||
"Discovery (listing, indexing) of this account by external services (search bots etc.) is allowed."
|
||||
},
|
||||
actor_type: ActorType
|
||||
},
|
||||
|
|
@ -632,6 +650,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
|
||||
skip_thread_containment: false,
|
||||
allow_following_move: false,
|
||||
also_known_as: ["https://foo.bar/users/foo"],
|
||||
discoverable: false,
|
||||
actor_type: "Person"
|
||||
}
|
||||
|
|
@ -723,10 +742,17 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
nullable: true,
|
||||
description: "Mute notifications in addition to statuses? Defaults to true.",
|
||||
default: true
|
||||
},
|
||||
expires_in: %Schema{
|
||||
type: :integer,
|
||||
nullable: true,
|
||||
description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"notifications" => true
|
||||
"notifications" => true,
|
||||
"expires_in" => 86_400
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.Admin.FrontendOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
|
||||
import Pleroma.Web.ApiSpec.Helpers
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def index_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Reports"],
|
||||
summary: "Get a list of available frontends",
|
||||
operationId: "AdminAPI.FrontendController.index",
|
||||
security: [%{"oAuth" => ["read"]}],
|
||||
responses: %{
|
||||
200 => Operation.response("Response", "application/json", list_of_frontends()),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def install_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Reports"],
|
||||
summary: "Install a frontend",
|
||||
operationId: "AdminAPI.FrontendController.install",
|
||||
security: [%{"oAuth" => ["read"]}],
|
||||
requestBody: request_body("Parameters", install_request(), required: true),
|
||||
responses: %{
|
||||
200 => Operation.response("Response", "application/json", list_of_frontends()),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError),
|
||||
400 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp list_of_frontends do
|
||||
%Schema{
|
||||
type: :array,
|
||||
items: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
name: %Schema{type: :string},
|
||||
git: %Schema{type: :string, format: :uri, nullable: true},
|
||||
build_url: %Schema{type: :string, format: :uri, nullable: true},
|
||||
ref: %Schema{type: :string},
|
||||
installed: %Schema{type: :boolean}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp install_request do
|
||||
%Schema{
|
||||
title: "FrontendInstallRequest",
|
||||
type: :object,
|
||||
required: [:name],
|
||||
properties: %{
|
||||
name: %Schema{
|
||||
type: :string
|
||||
},
|
||||
ref: %Schema{
|
||||
type: :string
|
||||
},
|
||||
file: %Schema{
|
||||
type: :string
|
||||
},
|
||||
build_url: %Schema{
|
||||
type: :string
|
||||
},
|
||||
build_dir: %Schema{
|
||||
type: :string
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -24,6 +24,12 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do
|
|||
Operation.parameter(:id, :path, FlakeID, "Status ID", required: true),
|
||||
Operation.parameter(:emoji, :path, :string, "Filter by a single unicode emoji",
|
||||
required: nil
|
||||
),
|
||||
Operation.parameter(
|
||||
:with_muted,
|
||||
:query,
|
||||
:boolean,
|
||||
"Include reactions from muted acccounts."
|
||||
)
|
||||
],
|
||||
security: [%{"oAuth" => ["read:statuses"]}],
|
||||
|
|
|
|||
|
|
@ -193,6 +193,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
|
|||
"mention",
|
||||
"pleroma:emoji_reaction",
|
||||
"pleroma:chat_mention",
|
||||
"pleroma:report",
|
||||
"move",
|
||||
"follow_request"
|
||||
],
|
||||
|
|
@ -206,6 +207,8 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
|
|||
- `poll` - A poll you have voted in or created has ended
|
||||
- `move` - Someone moved their account
|
||||
- `pleroma:emoji_reaction` - Someone reacted with emoji to your status
|
||||
- `pleroma:chat_mention` - Someone mentioned you in a chat message
|
||||
- `pleroma:report` - Someone was reported
|
||||
"""
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do
|
|||
422 => Operation.response("Unprocessable Entity", "application/json", ApiError),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError),
|
||||
409 => Operation.response("Conflict", "application/json", ApiError)
|
||||
409 => Operation.response("Conflict", "application/json", ApiError),
|
||||
500 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -169,7 +169,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
|
|||
responses: %{
|
||||
200 => ok_response(),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
404 => Operation.response("Not Found", "application/json", ApiError),
|
||||
500 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
@ -184,7 +185,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
|
|||
parameters: [name_param()],
|
||||
responses: %{
|
||||
200 => Operation.response("Metadata", "application/json", metadata()),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError)
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError),
|
||||
500 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,6 +31,12 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
|||
:query,
|
||||
%Schema{type: :array, items: FlakeID},
|
||||
"Array of status IDs"
|
||||
),
|
||||
Operation.parameter(
|
||||
:with_muted,
|
||||
:query,
|
||||
BooleanLike,
|
||||
"Include reactions from muted acccounts."
|
||||
)
|
||||
],
|
||||
operationId: "StatusController.index",
|
||||
|
|
@ -67,7 +73,15 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
|||
description: "View information about a status",
|
||||
operationId: "StatusController.show",
|
||||
security: [%{"oAuth" => ["read:statuses"]}],
|
||||
parameters: [id_param()],
|
||||
parameters: [
|
||||
id_param(),
|
||||
Operation.parameter(
|
||||
:with_muted,
|
||||
:query,
|
||||
BooleanLike,
|
||||
"Include reactions from muted acccounts."
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 => status_response(),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
|
|
@ -223,7 +237,27 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
|||
security: [%{"oAuth" => ["write:mutes"]}],
|
||||
description: "Do not receive notifications for the thread that this status is part of.",
|
||||
operationId: "StatusController.mute_conversation",
|
||||
parameters: [id_param()],
|
||||
requestBody:
|
||||
request_body("Parameters", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
expires_in: %Schema{
|
||||
type: :integer,
|
||||
nullable: true,
|
||||
description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
|
||||
default: 0
|
||||
}
|
||||
}
|
||||
}),
|
||||
parameters: [
|
||||
id_param(),
|
||||
Operation.parameter(
|
||||
:expires_in,
|
||||
:query,
|
||||
%Schema{type: :integer, default: 0},
|
||||
"Expire the mute in `expires_in` seconds. Default 0 for infinity"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 => status_response(),
|
||||
400 => Operation.response("Error", "application/json", ApiError)
|
||||
|
|
|
|||
|
|
@ -146,6 +146,11 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation do
|
|||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Receive chat notifications?"
|
||||
},
|
||||
"pleroma:emoji_reaction": %Schema{
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Receive emoji reaction notifications?"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -210,6 +215,16 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation do
|
|||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Receive poll notifications?"
|
||||
},
|
||||
"pleroma:chat_mention": %Schema{
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Receive chat notifications?"
|
||||
},
|
||||
"pleroma:emoji_reaction": %Schema{
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Receive emoji reaction notifications?"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
pleroma: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
ap_id: %Schema{type: :string},
|
||||
also_known_as: %Schema{type: :array, items: %Schema{type: :string}},
|
||||
allow_following_move: %Schema{
|
||||
type: :boolean,
|
||||
description: "whether the user allows automatically follow moved following accounts"
|
||||
|
|
@ -127,7 +129,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
discoverable: %Schema{
|
||||
type: :boolean,
|
||||
description:
|
||||
"whether the user allows discovery of the account in search results and other services."
|
||||
"whether the user allows indexing / listing of the account by external services (search engines etc.)."
|
||||
},
|
||||
no_rich_text: %Schema{
|
||||
type: :boolean,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@ defmodule Pleroma.Web.ApiSpec.Schemas.VisibilityScope do
|
|||
title: "VisibilityScope",
|
||||
description: "Status visibility",
|
||||
type: :string,
|
||||
enum: ["public", "unlisted", "private", "direct", "list"]
|
||||
enum: ["public", "unlisted", "local", "private", "direct", "list"]
|
||||
})
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
alias Pleroma.Web.ActivityPub.Pipeline
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.CommonAPI.ActivityDraft
|
||||
|
||||
import Pleroma.Web.Gettext
|
||||
import Pleroma.Web.CommonAPI.Utils
|
||||
|
|
@ -358,7 +359,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
def get_visibility(_, _, %Participation{}), do: {"direct", "direct"}
|
||||
|
||||
def get_visibility(%{visibility: visibility}, in_reply_to, _)
|
||||
when visibility in ~w{public unlisted private direct},
|
||||
when visibility in ~w{public local unlisted private direct},
|
||||
do: {visibility, get_replied_to_visibility(in_reply_to)}
|
||||
|
||||
def get_visibility(%{visibility: "list:" <> list_id}, in_reply_to, _) do
|
||||
|
|
@ -399,31 +400,13 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
|
||||
def listen(user, data) do
|
||||
visibility = Map.get(data, :visibility, "public")
|
||||
|
||||
with {to, cc} <- get_to_and_cc(user, [], nil, visibility, nil),
|
||||
listen_data <-
|
||||
data
|
||||
|> Map.take([:album, :artist, :title, :length])
|
||||
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|
||||
|> Map.put("type", "Audio")
|
||||
|> Map.put("to", to)
|
||||
|> Map.put("cc", cc)
|
||||
|> Map.put("actor", user.ap_id),
|
||||
{:ok, activity} <-
|
||||
ActivityPub.listen(%{
|
||||
actor: user,
|
||||
to: to,
|
||||
object: listen_data,
|
||||
context: Utils.generate_context_id(),
|
||||
additional: %{"cc" => cc}
|
||||
}) do
|
||||
{:ok, activity}
|
||||
with {:ok, draft} <- ActivityDraft.listen(user, data) do
|
||||
ActivityPub.listen(draft.changes)
|
||||
end
|
||||
end
|
||||
|
||||
def post(user, %{status: _} = data) do
|
||||
with {:ok, draft} <- Pleroma.Web.CommonAPI.ActivityDraft.create(user, data) do
|
||||
with {:ok, draft} <- ActivityDraft.create(user, data) do
|
||||
ActivityPub.create(draft.changes, draft.preview?)
|
||||
end
|
||||
end
|
||||
|
|
@ -454,20 +437,46 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
def add_mute(user, activity) do
|
||||
def add_mute(user, activity, params \\ %{}) do
|
||||
expires_in = Map.get(params, :expires_in, 0)
|
||||
|
||||
with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]),
|
||||
_ <- Pleroma.Notification.mark_context_as_read(user, activity.data["context"]) do
|
||||
if expires_in > 0 do
|
||||
Pleroma.Workers.MuteExpireWorker.enqueue(
|
||||
"unmute_conversation",
|
||||
%{"user_id" => user.id, "activity_id" => activity.id},
|
||||
schedule_in: expires_in
|
||||
)
|
||||
end
|
||||
|
||||
{:ok, activity}
|
||||
else
|
||||
{:error, _} -> {:error, dgettext("errors", "conversation is already muted")}
|
||||
end
|
||||
end
|
||||
|
||||
def remove_mute(user, activity) do
|
||||
def remove_mute(%User{} = user, %Activity{} = activity) do
|
||||
ThreadMute.remove_mute(user.id, activity.data["context"])
|
||||
{:ok, activity}
|
||||
end
|
||||
|
||||
def remove_mute(user_id, activity_id) do
|
||||
with {:user, %User{} = user} <- {:user, User.get_by_id(user_id)},
|
||||
{:activity, %Activity{} = activity} <- {:activity, Activity.get_by_id(activity_id)} do
|
||||
remove_mute(user, activity)
|
||||
else
|
||||
{what, result} = error ->
|
||||
Logger.warn(
|
||||
"CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{
|
||||
activity_id
|
||||
}"
|
||||
)
|
||||
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
def thread_muted?(%User{id: user_id}, %{data: %{"context" => context}})
|
||||
when is_binary(context) do
|
||||
ThreadMute.exists?(user_id, context)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
in_reply_to_conversation: nil,
|
||||
visibility: nil,
|
||||
expires_at: nil,
|
||||
poll: nil,
|
||||
extra: nil,
|
||||
emoji: %{},
|
||||
content_html: nil,
|
||||
mentions: [],
|
||||
|
|
@ -35,9 +35,14 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
preview?: false,
|
||||
changes: %{}
|
||||
|
||||
def create(user, params) do
|
||||
def new(user, params) do
|
||||
%__MODULE__{user: user}
|
||||
|> put_params(params)
|
||||
end
|
||||
|
||||
def create(user, params) do
|
||||
user
|
||||
|> new(params)
|
||||
|> status()
|
||||
|> summary()
|
||||
|> with_valid(&attachments/1)
|
||||
|
|
@ -57,6 +62,30 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
|> validate()
|
||||
end
|
||||
|
||||
def listen(user, params) do
|
||||
user
|
||||
|> new(params)
|
||||
|> visibility()
|
||||
|> to_and_cc()
|
||||
|> context()
|
||||
|> listen_object()
|
||||
|> with_valid(&changes/1)
|
||||
|> validate()
|
||||
end
|
||||
|
||||
defp listen_object(draft) do
|
||||
object =
|
||||
draft.params
|
||||
|> Map.take([:album, :artist, :title, :length])
|
||||
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|
||||
|> Map.put("type", "Audio")
|
||||
|> Map.put("to", draft.to)
|
||||
|> Map.put("cc", draft.cc)
|
||||
|> Map.put("actor", draft.user.ap_id)
|
||||
|
||||
%__MODULE__{draft | object: object}
|
||||
end
|
||||
|
||||
defp put_params(draft, params) do
|
||||
params = Map.put_new(params, :in_reply_to_status_id, params[:in_reply_to_id])
|
||||
%__MODULE__{draft | params: params}
|
||||
|
|
@ -121,7 +150,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
defp poll(draft) do
|
||||
case Utils.make_poll_data(draft.params) do
|
||||
{:ok, {poll, poll_emoji}} ->
|
||||
%__MODULE__{draft | poll: poll, emoji: Map.merge(draft.emoji, poll_emoji)}
|
||||
%__MODULE__{draft | extra: poll, emoji: Map.merge(draft.emoji, poll_emoji)}
|
||||
|
||||
{:error, message} ->
|
||||
add_error(draft, message)
|
||||
|
|
@ -129,32 +158,18 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
end
|
||||
|
||||
defp content(draft) do
|
||||
{content_html, mentions, tags} =
|
||||
Utils.make_content_html(
|
||||
draft.status,
|
||||
draft.attachments,
|
||||
draft.params,
|
||||
draft.visibility
|
||||
)
|
||||
{content_html, mentioned_users, tags} = Utils.make_content_html(draft)
|
||||
|
||||
mentions =
|
||||
mentioned_users
|
||||
|> Enum.map(fn {_, mentioned_user} -> mentioned_user.ap_id end)
|
||||
|> Utils.get_addressed_users(draft.params[:to])
|
||||
|
||||
%__MODULE__{draft | content_html: content_html, mentions: mentions, tags: tags}
|
||||
end
|
||||
|
||||
defp to_and_cc(draft) do
|
||||
addressed_users =
|
||||
draft.mentions
|
||||
|> Enum.map(fn {_, mentioned_user} -> mentioned_user.ap_id end)
|
||||
|> Utils.get_addressed_users(draft.params[:to])
|
||||
|
||||
{to, cc} =
|
||||
Utils.get_to_and_cc(
|
||||
draft.user,
|
||||
addressed_users,
|
||||
draft.in_reply_to,
|
||||
draft.visibility,
|
||||
draft.in_reply_to_conversation
|
||||
)
|
||||
|
||||
{to, cc} = Utils.get_to_and_cc(draft)
|
||||
%__MODULE__{draft | to: to, cc: cc}
|
||||
end
|
||||
|
||||
|
|
@ -172,19 +187,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji)
|
||||
|
||||
object =
|
||||
Utils.make_note_data(
|
||||
draft.user.ap_id,
|
||||
draft.to,
|
||||
draft.context,
|
||||
draft.content_html,
|
||||
draft.attachments,
|
||||
draft.in_reply_to,
|
||||
draft.tags,
|
||||
draft.summary,
|
||||
draft.cc,
|
||||
draft.sensitive,
|
||||
draft.poll
|
||||
)
|
||||
Utils.make_note_data(draft)
|
||||
|> Map.put("emoji", emoji)
|
||||
|> Map.put("source", draft.status)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.CommonAPI.ActivityDraft
|
||||
alias Pleroma.Web.MediaProxy
|
||||
alias Pleroma.Web.Plugs.AuthenticationPlug
|
||||
|
||||
|
|
@ -50,67 +51,62 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
{_, descs} = Jason.decode(descs_str)
|
||||
|
||||
Enum.map(ids, fn media_id ->
|
||||
case Repo.get(Object, media_id) do
|
||||
%Object{data: data} ->
|
||||
Map.put(data, "name", descs[media_id])
|
||||
|
||||
_ ->
|
||||
nil
|
||||
with %Object{data: data} <- Repo.get(Object, media_id) do
|
||||
Map.put(data, "name", descs[media_id])
|
||||
end
|
||||
end)
|
||||
|> Enum.reject(&is_nil/1)
|
||||
end
|
||||
|
||||
@spec get_to_and_cc(
|
||||
User.t(),
|
||||
list(String.t()),
|
||||
Activity.t() | nil,
|
||||
String.t(),
|
||||
Participation.t() | nil
|
||||
) :: {list(String.t()), list(String.t())}
|
||||
@spec get_to_and_cc(ActivityDraft.t()) :: {list(String.t()), list(String.t())}
|
||||
|
||||
def get_to_and_cc(_, _, _, _, %Participation{} = participation) do
|
||||
def get_to_and_cc(%{in_reply_to_conversation: %Participation{} = participation}) do
|
||||
participation = Repo.preload(participation, :recipients)
|
||||
{Enum.map(participation.recipients, & &1.ap_id), []}
|
||||
end
|
||||
|
||||
def get_to_and_cc(user, mentioned_users, inReplyTo, "public", _) do
|
||||
to = [Pleroma.Constants.as_public() | mentioned_users]
|
||||
cc = [user.follower_address]
|
||||
def get_to_and_cc(%{visibility: visibility} = draft) when visibility in ["public", "local"] do
|
||||
to =
|
||||
case visibility do
|
||||
"public" -> [Pleroma.Constants.as_public() | draft.mentions]
|
||||
"local" -> [Pleroma.Constants.as_local_public() | draft.mentions]
|
||||
end
|
||||
|
||||
if inReplyTo do
|
||||
{Enum.uniq([inReplyTo.data["actor"] | to]), cc}
|
||||
cc = [draft.user.follower_address]
|
||||
|
||||
if draft.in_reply_to do
|
||||
{Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc}
|
||||
else
|
||||
{to, cc}
|
||||
end
|
||||
end
|
||||
|
||||
def get_to_and_cc(user, mentioned_users, inReplyTo, "unlisted", _) do
|
||||
to = [user.follower_address | mentioned_users]
|
||||
def get_to_and_cc(%{visibility: "unlisted"} = draft) do
|
||||
to = [draft.user.follower_address | draft.mentions]
|
||||
cc = [Pleroma.Constants.as_public()]
|
||||
|
||||
if inReplyTo do
|
||||
{Enum.uniq([inReplyTo.data["actor"] | to]), cc}
|
||||
if draft.in_reply_to do
|
||||
{Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc}
|
||||
else
|
||||
{to, cc}
|
||||
end
|
||||
end
|
||||
|
||||
def get_to_and_cc(user, mentioned_users, inReplyTo, "private", _) do
|
||||
{to, cc} = get_to_and_cc(user, mentioned_users, inReplyTo, "direct", nil)
|
||||
{[user.follower_address | to], cc}
|
||||
def get_to_and_cc(%{visibility: "private"} = draft) do
|
||||
{to, cc} = get_to_and_cc(struct(draft, visibility: "direct"))
|
||||
{[draft.user.follower_address | to], cc}
|
||||
end
|
||||
|
||||
def get_to_and_cc(_user, mentioned_users, inReplyTo, "direct", _) do
|
||||
def get_to_and_cc(%{visibility: "direct"} = draft) do
|
||||
# If the OP is a DM already, add the implicit actor.
|
||||
if inReplyTo && Visibility.is_direct?(inReplyTo) do
|
||||
{Enum.uniq([inReplyTo.data["actor"] | mentioned_users]), []}
|
||||
if draft.in_reply_to && Visibility.is_direct?(draft.in_reply_to) do
|
||||
{Enum.uniq([draft.in_reply_to.data["actor"] | draft.mentions]), []}
|
||||
else
|
||||
{mentioned_users, []}
|
||||
{draft.mentions, []}
|
||||
end
|
||||
end
|
||||
|
||||
def get_to_and_cc(_user, mentions, _inReplyTo, {:list, _}, _), do: {mentions, []}
|
||||
def get_to_and_cc(%{visibility: {:list, _}, mentions: mentions}), do: {mentions, []}
|
||||
|
||||
def get_addressed_users(_, to) when is_list(to) do
|
||||
User.get_ap_ids_by_nicknames(to)
|
||||
|
|
@ -203,30 +199,25 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
end
|
||||
end
|
||||
|
||||
def make_content_html(
|
||||
status,
|
||||
attachments,
|
||||
data,
|
||||
visibility
|
||||
) do
|
||||
def make_content_html(%ActivityDraft{} = draft) do
|
||||
attachment_links =
|
||||
data
|
||||
draft.params
|
||||
|> Map.get("attachment_links", Config.get([:instance, :attachment_links]))
|
||||
|> truthy_param?()
|
||||
|
||||
content_type = get_content_type(data[:content_type])
|
||||
content_type = get_content_type(draft.params[:content_type])
|
||||
|
||||
options =
|
||||
if visibility == "direct" && Config.get([:instance, :safe_dm_mentions]) do
|
||||
if draft.visibility == "direct" && Config.get([:instance, :safe_dm_mentions]) do
|
||||
[safe_mention: true]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
status
|
||||
draft.status
|
||||
|> format_input(content_type, options)
|
||||
|> maybe_add_attachments(attachments, attachment_links)
|
||||
|> maybe_add_nsfw_tag(data)
|
||||
|> maybe_add_attachments(draft.attachments, attachment_links)
|
||||
|> maybe_add_nsfw_tag(draft.params)
|
||||
end
|
||||
|
||||
defp get_content_type(content_type) do
|
||||
|
|
@ -308,33 +299,21 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
|> Formatter.html_escape("text/html")
|
||||
end
|
||||
|
||||
def make_note_data(
|
||||
actor,
|
||||
to,
|
||||
context,
|
||||
content_html,
|
||||
attachments,
|
||||
in_reply_to,
|
||||
tags,
|
||||
summary \\ nil,
|
||||
cc \\ [],
|
||||
sensitive \\ false,
|
||||
extra_params \\ %{}
|
||||
) do
|
||||
def make_note_data(%ActivityDraft{} = draft) do
|
||||
%{
|
||||
"type" => "Note",
|
||||
"to" => to,
|
||||
"cc" => cc,
|
||||
"content" => content_html,
|
||||
"summary" => summary,
|
||||
"sensitive" => truthy_param?(sensitive),
|
||||
"context" => context,
|
||||
"attachment" => attachments,
|
||||
"actor" => actor,
|
||||
"tag" => Keyword.values(tags) |> Enum.uniq()
|
||||
"to" => draft.to,
|
||||
"cc" => draft.cc,
|
||||
"content" => draft.content_html,
|
||||
"summary" => draft.summary,
|
||||
"sensitive" => draft.sensitive,
|
||||
"context" => draft.context,
|
||||
"attachment" => draft.attachments,
|
||||
"actor" => draft.user.ap_id,
|
||||
"tag" => Keyword.values(draft.tags) |> Enum.uniq()
|
||||
}
|
||||
|> add_in_reply_to(in_reply_to)
|
||||
|> Map.merge(extra_params)
|
||||
|> add_in_reply_to(draft.in_reply_to)
|
||||
|> Map.merge(draft.extra)
|
||||
end
|
||||
|
||||
defp add_in_reply_to(object, nil), do: object
|
||||
|
|
|
|||
|
|
@ -37,10 +37,11 @@ defmodule Pleroma.Web.Fallback.RedirectController do
|
|||
|
||||
tags = build_tags(conn, params)
|
||||
preloads = preload_data(conn, params)
|
||||
title = "<title>#{Pleroma.Config.get([:instance, :name])}</title>"
|
||||
|
||||
response =
|
||||
index_content
|
||||
|> String.replace("<!--server-generated-meta-->", tags <> preloads)
|
||||
|> String.replace("<!--server-generated-meta-->", tags <> preloads <> title)
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("text/html")
|
||||
|
|
@ -54,10 +55,11 @@ defmodule Pleroma.Web.Fallback.RedirectController do
|
|||
def redirector_with_preload(conn, params) do
|
||||
{:ok, index_content} = File.read(index_file_path())
|
||||
preloads = preload_data(conn, params)
|
||||
title = "<title>#{Pleroma.Config.get([:instance, :name])}</title>"
|
||||
|
||||
response =
|
||||
index_content
|
||||
|> String.replace("<!--server-generated-meta-->", preloads)
|
||||
|> String.replace("<!--server-generated-meta-->", preloads <> title)
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("text/html")
|
||||
|
|
|
|||
|
|
@ -1,185 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.FedSockets do
|
||||
@moduledoc """
|
||||
This documents the FedSockets framework. A framework for federating
|
||||
ActivityPub objects between servers via persistant WebSocket connections.
|
||||
|
||||
FedSockets allow servers to authenticate on first contact and maintain that
|
||||
connection, eliminating the need to authenticate every time data needs to be shared.
|
||||
|
||||
## Protocol
|
||||
FedSockets currently support 2 types of data transfer:
|
||||
* `publish` method which doesn't require a response
|
||||
* `fetch` method requires a response be sent
|
||||
|
||||
### Publish
|
||||
The publish operation sends a json encoded map of the shape:
|
||||
%{action: :publish, data: json}
|
||||
and accepts (but does not require) a reply of form:
|
||||
%{"action" => "publish_reply"}
|
||||
|
||||
The outgoing params represent
|
||||
* data: ActivityPub object encoded into json
|
||||
|
||||
|
||||
### Fetch
|
||||
The fetch operation sends a json encoded map of the shape:
|
||||
%{action: :fetch, data: id, uuid: fetch_uuid}
|
||||
and requires a reply of form:
|
||||
%{"action" => "fetch_reply", "uuid" => uuid, "data" => data}
|
||||
|
||||
The outgoing params represent
|
||||
* id: an ActivityPub object URI
|
||||
* uuid: a unique uuid generated by the sender
|
||||
|
||||
The reply params represent
|
||||
* data: an ActivityPub object encoded into json
|
||||
* uuid: the uuid sent along with the fetch request
|
||||
|
||||
## Examples
|
||||
Clients of FedSocket transfers shouldn't need to use any of the functions outside of this module.
|
||||
|
||||
A typical publish operation can be performed through the following code, and a fetch operation in a similar manner.
|
||||
|
||||
case FedSockets.get_or_create_fed_socket(inbox) do
|
||||
{:ok, fedsocket} ->
|
||||
FedSockets.publish(fedsocket, json)
|
||||
|
||||
_ ->
|
||||
alternative_publish(inbox, actor, json, params)
|
||||
end
|
||||
|
||||
## Configuration
|
||||
FedSockets have the following config settings
|
||||
|
||||
config :pleroma, :fed_sockets,
|
||||
enabled: true,
|
||||
ping_interval: :timer.seconds(15),
|
||||
connection_duration: :timer.hours(1),
|
||||
rejection_duration: :timer.hours(1),
|
||||
fed_socket_fetches: [
|
||||
default: 12_000,
|
||||
interval: 3_000,
|
||||
lazy: false
|
||||
]
|
||||
* enabled - turn FedSockets on or off with this flag. Can be toggled at runtime.
|
||||
* connection_duration - How long a FedSocket can sit idle before it's culled.
|
||||
* rejection_duration - After failing to make a FedSocket connection a host will be excluded
|
||||
from further connections for this amount of time
|
||||
* fed_socket_fetches - Use these parameters to pass options to the Cachex queue backing the FetchRegistry
|
||||
* fed_socket_rejections - Use these parameters to pass options to the Cachex queue backing the FedRegistry
|
||||
|
||||
Cachex options are
|
||||
* default: the minimum amount of time a fetch can wait before it times out.
|
||||
* interval: the interval between checks for timed out entries. This plus the default represent the maximum time allowed
|
||||
* lazy: leave at false for consistant and fast lookups, set to true for stricter timeout enforcement
|
||||
|
||||
"""
|
||||
require Logger
|
||||
|
||||
alias Pleroma.Web.FedSockets.FedRegistry
|
||||
alias Pleroma.Web.FedSockets.FedSocket
|
||||
alias Pleroma.Web.FedSockets.SocketInfo
|
||||
|
||||
@doc """
|
||||
returns a FedSocket for the given origin. Will reuse an existing one or create a new one.
|
||||
|
||||
address is expected to be a fully formed URL such as:
|
||||
"http://www.example.com" or "http://www.example.com:8080"
|
||||
|
||||
It can and usually does include additional path parameters,
|
||||
but these are ignored as the FedSockets are organized by host and port info alone.
|
||||
"""
|
||||
def get_or_create_fed_socket(address) do
|
||||
with {:cache, {:error, :missing}} <- {:cache, get_fed_socket(address)},
|
||||
{:connect, {:ok, _pid}} <- {:connect, FedSocket.connect_to_host(address)},
|
||||
{:cache, {:ok, fed_socket}} <- {:cache, get_fed_socket(address)} do
|
||||
Logger.debug("fedsocket created for - #{inspect(address)}")
|
||||
{:ok, fed_socket}
|
||||
else
|
||||
{:cache, {:ok, socket}} ->
|
||||
Logger.debug("fedsocket found in cache - #{inspect(address)}")
|
||||
{:ok, socket}
|
||||
|
||||
{:cache, {:error, :rejected} = e} ->
|
||||
e
|
||||
|
||||
{:connect, {:error, _host}} ->
|
||||
Logger.debug("set host rejected for - #{inspect(address)}")
|
||||
FedRegistry.set_host_rejected(address)
|
||||
{:error, :rejected}
|
||||
|
||||
{_, {:error, :disabled}} ->
|
||||
{:error, :disabled}
|
||||
|
||||
{_, {:error, reason}} ->
|
||||
Logger.warn("get_or_create_fed_socket error - #{inspect(reason)}")
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
returns a FedSocket for the given origin. Will not create a new FedSocket if one does not exist.
|
||||
|
||||
address is expected to be a fully formed URL such as:
|
||||
"http://www.example.com" or "http://www.example.com:8080"
|
||||
"""
|
||||
def get_fed_socket(address) do
|
||||
origin = SocketInfo.origin(address)
|
||||
|
||||
with {:config, true} <- {:config, Pleroma.Config.get([:fed_sockets, :enabled], false)},
|
||||
{:ok, socket} <- FedRegistry.get_fed_socket(origin) do
|
||||
{:ok, socket}
|
||||
else
|
||||
{:config, _} ->
|
||||
{:error, :disabled}
|
||||
|
||||
{:error, :rejected} ->
|
||||
Logger.debug("FedSocket previously rejected - #{inspect(origin)}")
|
||||
{:error, :rejected}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends the supplied data via the publish protocol.
|
||||
It will not block waiting for a reply.
|
||||
Returns :ok but this is not an indication of a successful transfer.
|
||||
|
||||
the data is expected to be JSON encoded binary data.
|
||||
"""
|
||||
def publish(%SocketInfo{} = fed_socket, json) do
|
||||
FedSocket.publish(fed_socket, json)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends the supplied data via the fetch protocol.
|
||||
It will block waiting for a reply or timeout.
|
||||
|
||||
Returns {:ok, object} where object is the requested object (or nil)
|
||||
{:error, :timeout} in the event the message was not responded to
|
||||
|
||||
the id is expected to be the URI of an ActivityPub object.
|
||||
"""
|
||||
def fetch(%SocketInfo{} = fed_socket, id) do
|
||||
FedSocket.fetch(fed_socket, id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Disconnect all and restart FedSockets.
|
||||
This is mainly used in development and testing but could be useful in production.
|
||||
"""
|
||||
def reset do
|
||||
FedRegistry
|
||||
|> Process.whereis()
|
||||
|> Process.exit(:testing)
|
||||
end
|
||||
|
||||
def uri_for_origin(origin),
|
||||
do: "ws://#{origin}/api/fedsocket/v1"
|
||||
end
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.FedSockets.FedRegistry do
|
||||
@moduledoc """
|
||||
The FedRegistry stores the active FedSockets for quick retrieval.
|
||||
|
||||
The storage and retrieval portion of the FedRegistry is done in process through
|
||||
elixir's `Registry` module for speed and its ability to monitor for terminated processes.
|
||||
|
||||
Dropped connections will be caught by `Registry` and deleted. Since the next
|
||||
message will initiate a new connection there is no reason to try and reconnect at that point.
|
||||
|
||||
Normally outside modules should have no need to call or use the FedRegistry themselves.
|
||||
"""
|
||||
|
||||
alias Pleroma.Web.FedSockets.FedSocket
|
||||
alias Pleroma.Web.FedSockets.SocketInfo
|
||||
|
||||
require Logger
|
||||
|
||||
@default_rejection_duration 15 * 60 * 1000
|
||||
@rejections :fed_socket_rejections
|
||||
|
||||
@doc """
|
||||
Retrieves a FedSocket from the Registry given it's origin.
|
||||
|
||||
The origin is expected to be a string identifying the endpoint "example.com" or "example2.com:8080"
|
||||
|
||||
Will return:
|
||||
* {:ok, fed_socket} for working FedSockets
|
||||
* {:error, :rejected} for origins that have been tried and refused within the rejection duration interval
|
||||
* {:error, some_reason} usually :missing for unknown origins
|
||||
"""
|
||||
def get_fed_socket(origin) do
|
||||
case get_registry_data(origin) do
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
|
||||
{:ok, %{state: :connected} = socket_info} ->
|
||||
{:ok, socket_info}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds a connected FedSocket to the Registry.
|
||||
|
||||
Always returns {:ok, fed_socket}
|
||||
"""
|
||||
def add_fed_socket(origin, pid \\ nil) do
|
||||
origin
|
||||
|> SocketInfo.build(pid)
|
||||
|> SocketInfo.connect()
|
||||
|> add_socket_info
|
||||
end
|
||||
|
||||
defp add_socket_info(%{origin: origin, state: :connected} = socket_info) do
|
||||
case Registry.register(FedSockets.Registry, origin, socket_info) do
|
||||
{:ok, _owner} ->
|
||||
clear_prior_rejection(origin)
|
||||
Logger.debug("fedsocket added: #{inspect(origin)}")
|
||||
|
||||
{:ok, socket_info}
|
||||
|
||||
{:error, {:already_registered, _pid}} ->
|
||||
FedSocket.close(socket_info)
|
||||
existing_socket_info = Registry.lookup(FedSockets.Registry, origin)
|
||||
|
||||
{:ok, existing_socket_info}
|
||||
|
||||
_ ->
|
||||
{:error, :error_adding_socket}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Mark this origin as having rejected a connection attempt.
|
||||
This will keep it from getting additional connection attempts
|
||||
for a period of time specified in the config.
|
||||
|
||||
Always returns {:ok, new_reg_data}
|
||||
"""
|
||||
def set_host_rejected(uri) do
|
||||
new_reg_data =
|
||||
uri
|
||||
|> SocketInfo.origin()
|
||||
|> get_or_create_registry_data()
|
||||
|> set_to_rejected()
|
||||
|> save_registry_data()
|
||||
|
||||
{:ok, new_reg_data}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Retrieves the FedRegistryData from the Registry given it's origin.
|
||||
|
||||
The origin is expected to be a string identifying the endpoint "example.com" or "example2.com:8080"
|
||||
|
||||
Will return:
|
||||
* {:ok, fed_registry_data} for known origins
|
||||
* {:error, :missing} for uniknown origins
|
||||
* {:error, :cache_error} indicating some low level runtime issues
|
||||
"""
|
||||
def get_registry_data(origin) do
|
||||
case Registry.lookup(FedSockets.Registry, origin) do
|
||||
[] ->
|
||||
if is_rejected?(origin) do
|
||||
Logger.debug("previously rejected fedsocket requested")
|
||||
{:error, :rejected}
|
||||
else
|
||||
{:error, :missing}
|
||||
end
|
||||
|
||||
[{_pid, %{state: :connected} = socket_info}] ->
|
||||
{:ok, socket_info}
|
||||
|
||||
_ ->
|
||||
{:error, :cache_error}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Retrieves a map of all sockets from the Registry. The keys are the origins and the values are the corresponding SocketInfo
|
||||
"""
|
||||
def list_all do
|
||||
(list_all_connected() ++ list_all_rejected())
|
||||
|> Enum.into(%{})
|
||||
end
|
||||
|
||||
defp list_all_connected do
|
||||
FedSockets.Registry
|
||||
|> Registry.select([{{:"$1", :_, :"$3"}, [], [{{:"$1", :"$3"}}]}])
|
||||
end
|
||||
|
||||
defp list_all_rejected do
|
||||
{:ok, keys} = Cachex.keys(@rejections)
|
||||
|
||||
{:ok, registry_data} =
|
||||
Cachex.execute(@rejections, fn worker ->
|
||||
Enum.map(keys, fn k -> {k, Cachex.get!(worker, k)} end)
|
||||
end)
|
||||
|
||||
registry_data
|
||||
end
|
||||
|
||||
defp clear_prior_rejection(origin),
|
||||
do: Cachex.del(@rejections, origin)
|
||||
|
||||
defp is_rejected?(origin) do
|
||||
case Cachex.get(@rejections, origin) do
|
||||
{:ok, nil} ->
|
||||
false
|
||||
|
||||
{:ok, _} ->
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
defp get_or_create_registry_data(origin) do
|
||||
case get_registry_data(origin) do
|
||||
{:error, :missing} ->
|
||||
%SocketInfo{origin: origin}
|
||||
|
||||
{:ok, socket_info} ->
|
||||
socket_info
|
||||
end
|
||||
end
|
||||
|
||||
defp save_registry_data(%SocketInfo{origin: origin, state: :connected} = socket_info) do
|
||||
{:ok, true} = Registry.update_value(FedSockets.Registry, origin, fn _ -> socket_info end)
|
||||
socket_info
|
||||
end
|
||||
|
||||
defp save_registry_data(%SocketInfo{origin: origin, state: :rejected} = socket_info) do
|
||||
rejection_expiration =
|
||||
Pleroma.Config.get([:fed_sockets, :rejection_duration], @default_rejection_duration)
|
||||
|
||||
{:ok, true} = Cachex.put(@rejections, origin, socket_info, ttl: rejection_expiration)
|
||||
socket_info
|
||||
end
|
||||
|
||||
defp set_to_rejected(%SocketInfo{} = socket_info),
|
||||
do: %SocketInfo{socket_info | state: :rejected}
|
||||
end
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.FedSockets.FedSocket do
|
||||
@moduledoc """
|
||||
The FedSocket module abstracts the actions to be taken taken on connections regardless of
|
||||
whether the connection started as inbound or outbound.
|
||||
|
||||
|
||||
Normally outside modules will have no need to call the FedSocket module directly.
|
||||
"""
|
||||
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Containment
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ObjectView
|
||||
alias Pleroma.Web.ActivityPub.UserView
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.FedSockets.FetchRegistry
|
||||
alias Pleroma.Web.FedSockets.IngesterWorker
|
||||
alias Pleroma.Web.FedSockets.OutgoingHandler
|
||||
alias Pleroma.Web.FedSockets.SocketInfo
|
||||
|
||||
require Logger
|
||||
|
||||
@shake "61dd18f7-f1e6-49a4-939a-a749fcdc1103"
|
||||
|
||||
def connect_to_host(uri) do
|
||||
case OutgoingHandler.start_link(uri) do
|
||||
{:ok, pid} ->
|
||||
{:ok, pid}
|
||||
|
||||
error ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
def close(%SocketInfo{pid: socket_pid}),
|
||||
do: Process.send(socket_pid, :close, [])
|
||||
|
||||
def publish(%SocketInfo{pid: socket_pid}, json) do
|
||||
%{action: :publish, data: json}
|
||||
|> Jason.encode!()
|
||||
|> send_packet(socket_pid)
|
||||
end
|
||||
|
||||
def fetch(%SocketInfo{pid: socket_pid}, id) do
|
||||
fetch_uuid = FetchRegistry.register_fetch(id)
|
||||
|
||||
%{action: :fetch, data: id, uuid: fetch_uuid}
|
||||
|> Jason.encode!()
|
||||
|> send_packet(socket_pid)
|
||||
|
||||
wait_for_fetch_to_return(fetch_uuid, 0)
|
||||
end
|
||||
|
||||
def receive_package(%SocketInfo{} = fed_socket, json) do
|
||||
json
|
||||
|> Jason.decode!()
|
||||
|> process_package(fed_socket)
|
||||
end
|
||||
|
||||
defp wait_for_fetch_to_return(uuid, cntr) do
|
||||
case FetchRegistry.check_fetch(uuid) do
|
||||
{:error, :waiting} ->
|
||||
Process.sleep(:math.pow(cntr, 3) |> Kernel.trunc())
|
||||
wait_for_fetch_to_return(uuid, cntr + 1)
|
||||
|
||||
{:error, :missing} ->
|
||||
Logger.error("FedSocket fetch timed out - #{inspect(uuid)}")
|
||||
{:error, :timeout}
|
||||
|
||||
{:ok, _fr} ->
|
||||
FetchRegistry.pop_fetch(uuid)
|
||||
end
|
||||
end
|
||||
|
||||
defp process_package(%{"action" => "publish", "data" => data}, %{origin: origin} = _fed_socket) do
|
||||
if Containment.contain_origin(origin, data) do
|
||||
IngesterWorker.enqueue("ingest", %{"object" => data})
|
||||
end
|
||||
|
||||
{:reply, %{"action" => "publish_reply", "status" => "processed"}}
|
||||
end
|
||||
|
||||
defp process_package(%{"action" => "fetch_reply", "uuid" => uuid, "data" => data}, _fed_socket) do
|
||||
FetchRegistry.register_fetch_received(uuid, data)
|
||||
{:noreply, nil}
|
||||
end
|
||||
|
||||
defp process_package(%{"action" => "fetch", "uuid" => uuid, "data" => ap_id}, _fed_socket) do
|
||||
{:ok, data} = render_fetched_data(ap_id, uuid)
|
||||
{:reply, data}
|
||||
end
|
||||
|
||||
defp process_package(%{"action" => "publish_reply"}, _fed_socket) do
|
||||
{:noreply, nil}
|
||||
end
|
||||
|
||||
defp process_package(other, _fed_socket) do
|
||||
Logger.warn("unknown json packages received #{inspect(other)}")
|
||||
{:noreply, nil}
|
||||
end
|
||||
|
||||
defp render_fetched_data(ap_id, uuid) do
|
||||
{:ok,
|
||||
%{
|
||||
"action" => "fetch_reply",
|
||||
"status" => "processed",
|
||||
"uuid" => uuid,
|
||||
"data" => represent_item(ap_id)
|
||||
}}
|
||||
end
|
||||
|
||||
defp represent_item(ap_id) do
|
||||
case User.get_by_ap_id(ap_id) do
|
||||
nil ->
|
||||
object = Object.get_cached_by_ap_id(ap_id)
|
||||
|
||||
if Visibility.is_public?(object) do
|
||||
Phoenix.View.render_to_string(ObjectView, "object.json", object: object)
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
user ->
|
||||
Phoenix.View.render_to_string(UserView, "user.json", user: user)
|
||||
end
|
||||
end
|
||||
|
||||
defp send_packet(data, socket_pid) do
|
||||
Process.send(socket_pid, {:send, data}, [])
|
||||
end
|
||||
|
||||
def shake, do: @shake
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue