Page MenuHomePhorge

No OneTemporary

Size
185 KB
Referenced Files
None
Subscribers
None
diff --git a/benchmarks/load_testing/activities.ex b/benchmarks/load_testing/activities.ex
index b9f6b24da..7f262d228 100644
--- a/benchmarks/load_testing/activities.ex
+++ b/benchmarks/load_testing/activities.ex
@@ -1,595 +1,595 @@
defmodule Pleroma.LoadTesting.Activities do
@moduledoc """
Module for generating different activities.
"""
import Ecto.Query
import Pleroma.LoadTesting.Helper, only: [to_sec: 1]
alias Ecto.UUID
alias Pleroma.Constants
alias Pleroma.LoadTesting.Users
alias Pleroma.Repo
alias Pleroma.Web.CommonAPI
require Constants
@defaults [
iterations: 170,
friends_used: 20,
non_friends_used: 20
]
@max_concurrency 10
@visibility ~w(public private direct unlisted)
@types [
:simple,
:simple_filtered,
:emoji,
:mentions,
:hell_thread,
:attachment,
:tag,
:like,
:reblog,
:simple_thread
]
@groups [:friends_local, :friends_remote, :non_friends_local, :non_friends_local]
@remote_groups [:friends_remote, :non_friends_remote]
@friends_groups [:friends_local, :friends_remote]
@non_friends_groups [:non_friends_local, :non_friends_remote]
@spec generate(User.t(), keyword()) :: :ok
def generate(user, opts \\ []) do
{:ok, _} =
Agent.start_link(fn -> %{} end,
name: :benchmark_state
)
opts = Keyword.merge(@defaults, opts)
users = Users.prepare_users(user, opts)
{:ok, _} = Agent.start_link(fn -> users[:non_friends_remote] end, name: :non_friends_remote)
task_data =
for visibility <- @visibility,
type <- @types,
group <- [:user | @groups],
do: {visibility, type, group}
IO.puts("Starting generating #{opts[:iterations]} iterations of activities...")
public_long_thread = fn ->
generate_long_thread("public", users, opts)
end
private_long_thread = fn ->
generate_long_thread("private", users, opts)
end
iterations = opts[:iterations]
{time, _} =
:timer.tc(fn ->
Enum.each(
1..iterations,
fn
i when i == iterations - 2 ->
spawn(public_long_thread)
spawn(private_long_thread)
generate_activities(users, Enum.shuffle(task_data), opts)
_ ->
generate_activities(users, Enum.shuffle(task_data), opts)
end
)
end)
IO.puts("Generating iterations of activities took #{to_sec(time)} sec.\n")
:ok
end
def generate_power_intervals(opts \\ []) do
count = Keyword.get(opts, :count, 20)
power = Keyword.get(opts, :power, 2)
IO.puts("Generating #{count} intervals for a power #{power} series...")
counts = Enum.map(1..count, fn n -> :math.pow(n, power) end)
sum = Enum.sum(counts)
densities =
Enum.map(counts, fn c ->
c / sum
end)
densities
|> Enum.reduce(0, fn density, acc ->
if acc == 0 do
[{0, density}]
else
[{_, lower} | _] = acc
[{lower, lower + density} | acc]
end
end)
|> Enum.reverse()
end
def generate_tagged_activities(opts \\ []) do
tag_count = Keyword.get(opts, :tag_count, 20)
users = Keyword.get(opts, :users, Repo.all(Pleroma.User))
activity_count = Keyword.get(opts, :count, 200_000)
intervals = generate_power_intervals(count: tag_count)
IO.puts(
"Generating #{activity_count} activities using #{tag_count} different tags of format `tag_n`, starting at tag_0"
)
Enum.each(1..activity_count, fn _ ->
random = :rand.uniform()
i = Enum.find_index(intervals, fn {lower, upper} -> lower <= random && upper > random end)
CommonAPI.post(Enum.random(users), %{status: "a post with the tag #tag_#{i}"})
end)
end
defp generate_long_thread(visibility, users, _opts) do
group =
if visibility == "public",
do: :friends_local,
else: :user
tasks = get_reply_tasks(visibility, group) |> Stream.cycle() |> Enum.take(50)
{:ok, activity} =
CommonAPI.post(users[:user], %{
status: "Start of #{visibility} long thread",
visibility: visibility
})
Agent.update(:benchmark_state, fn state ->
key =
if visibility == "public",
do: :public_thread,
else: :private_thread
Map.put(state, key, activity)
end)
acc = {activity.id, ["@" <> users[:user].nickname, "reply to long thread"]}
insert_replies_for_long_thread(tasks, visibility, users, acc)
IO.puts("Generating #{visibility} long thread ended\n")
end
defp insert_replies_for_long_thread(tasks, visibility, users, acc) do
Enum.reduce(tasks, acc, fn
:user, {id, data} ->
user = users[:user]
insert_reply(user, List.delete(data, "@" <> user.nickname), id, visibility)
group, {id, data} ->
replier = Enum.random(users[group])
insert_reply(replier, List.delete(data, "@" <> replier.nickname), id, visibility)
end)
end
defp generate_activities(users, task_data, opts) do
Task.async_stream(
task_data,
fn {visibility, type, group} ->
insert_activity(type, visibility, group, users, opts)
end,
max_concurrency: @max_concurrency,
timeout: 30_000
)
|> Stream.run()
end
defp insert_local_activity(visibility, group, users, status) do
{:ok, _} =
group
|> get_actor(users)
|> CommonAPI.post(%{status: status, visibility: visibility})
end
defp insert_remote_activity(visibility, group, users, status) do
actor = get_actor(group, users)
{act_data, obj_data} = prepare_activity_data(actor, visibility, users[:user])
{activity_data, object_data} = other_data(actor, status)
activity_data
|> Map.merge(act_data)
|> Map.put("object", Map.merge(object_data, obj_data))
|> Pleroma.Web.ActivityPub.ActivityPub.insert(false)
end
defp user_mentions(users) do
user_mentions =
Enum.reduce(
@groups,
[],
fn group, acc ->
acc ++ get_random_mentions(users[group], Enum.random(0..2))
end
)
if Enum.random([true, false]),
do: ["@" <> users[:user].nickname | user_mentions],
else: user_mentions
end
defp hell_thread_mentions(users) do
with {:ok, nil} <- Cachex.get(:user_cache, "hell_thread_mentions") do
cached =
@groups
|> Enum.reduce([users[:user]], fn group, acc ->
acc ++ Enum.take(users[group], 5)
end)
|> Enum.map(&"@#{&1.nickname}")
|> Enum.join(", ")
Cachex.put(:user_cache, "hell_thread_mentions", cached)
cached
else
{:ok, cached} -> cached
end
end
defp insert_activity(:simple, visibility, group, users, _opts)
when group in @remote_groups do
insert_remote_activity(visibility, group, users, "Remote status")
end
defp insert_activity(:simple, visibility, group, users, _opts) do
insert_local_activity(visibility, group, users, "Simple status")
end
defp insert_activity(:simple_filtered, visibility, group, users, _opts)
when group in @remote_groups do
insert_remote_activity(visibility, group, users, "Remote status which must be filtered")
end
defp insert_activity(:simple_filtered, visibility, group, users, _opts) do
insert_local_activity(visibility, group, users, "Simple status which must be filtered")
end
defp insert_activity(:emoji, visibility, group, users, _opts)
when group in @remote_groups do
insert_remote_activity(visibility, group, users, "Remote status with emoji :firefox:")
end
defp insert_activity(:emoji, visibility, group, users, _opts) do
insert_local_activity(visibility, group, users, "Simple status with emoji :firefox:")
end
defp insert_activity(:mentions, visibility, group, users, _opts)
when group in @remote_groups do
mentions = user_mentions(users)
status = Enum.join(mentions, ", ") <> " remote status with mentions"
insert_remote_activity(visibility, group, users, status)
end
defp insert_activity(:mentions, visibility, group, users, _opts) do
mentions = user_mentions(users)
status = Enum.join(mentions, ", ") <> " simple status with mentions"
insert_remote_activity(visibility, group, users, status)
end
defp insert_activity(:hell_thread, visibility, group, users, _)
when group in @remote_groups do
mentions = hell_thread_mentions(users)
insert_remote_activity(visibility, group, users, mentions <> " remote hell thread status")
end
defp insert_activity(:hell_thread, visibility, group, users, _opts) do
mentions = hell_thread_mentions(users)
insert_local_activity(visibility, group, users, mentions <> " hell thread status")
end
defp insert_activity(:attachment, visibility, group, users, _opts) do
actor = get_actor(group, users)
obj_data = %{
"actor" => actor.ap_id,
"name" => "4467-11.jpg",
"type" => "Document",
"url" => [
%{
"href" =>
"#{Pleroma.Web.Endpoint.url()}/media/b1b873552422a07bf53af01f3c231c841db4dfc42c35efde681abaf0f2a4eab7.jpg",
"mediaType" => "image/jpeg",
"type" => "Link"
}
]
}
object = Repo.insert!(%Pleroma.Object{data: obj_data})
{:ok, _activity} =
CommonAPI.post(actor, %{
status: "Post with attachment",
visibility: visibility,
media_ids: [object.id]
})
end
defp insert_activity(:tag, visibility, group, users, _opts) do
insert_local_activity(visibility, group, users, "Status with #tag")
end
defp insert_activity(:like, visibility, group, users, opts) do
actor = get_actor(group, users)
with activity_id when not is_nil(activity_id) <- get_random_create_activity_id(),
{:ok, _activity} <- CommonAPI.favorite(actor, activity_id) do
:ok
else
{:error, _} ->
insert_activity(:like, visibility, group, users, opts)
nil ->
Process.sleep(15)
insert_activity(:like, visibility, group, users, opts)
end
end
defp insert_activity(:reblog, visibility, group, users, opts) do
actor = get_actor(group, users)
with activity_id when not is_nil(activity_id) <- get_random_create_activity_id(),
{:ok, _activity} <- CommonAPI.repeat(activity_id, actor) do
:ok
else
{:error, _} ->
insert_activity(:reblog, visibility, group, users, opts)
nil ->
Process.sleep(15)
insert_activity(:reblog, visibility, group, users, opts)
end
end
defp insert_activity(:simple_thread, "direct", group, users, _opts) do
actor = get_actor(group, users)
tasks = get_reply_tasks("direct", group)
list =
case group do
:user ->
group = Enum.random(@friends_groups)
Enum.take(users[group], 3)
_ ->
Enum.take(users[group], 3)
end
data = Enum.map(list, &("@" <> &1.nickname))
{:ok, activity} =
CommonAPI.post(actor, %{
status: Enum.join(data, ", ") <> "simple status",
visibility: "direct"
})
acc = {activity.id, ["@" <> users[:user].nickname | data] ++ ["reply to status"]}
insert_direct_replies(tasks, users[:user], list, acc)
end
defp insert_activity(:simple_thread, visibility, group, users, _opts) do
actor = get_actor(group, users)
tasks = get_reply_tasks(visibility, group)
{:ok, activity} =
CommonAPI.post(users[:user], %{status: "Simple status", visibility: visibility})
acc = {activity.id, ["@" <> actor.nickname, "reply to status"]}
insert_replies(tasks, visibility, users, acc)
end
defp get_actor(:user, %{user: user}), do: user
defp get_actor(group, users), do: Enum.random(users[group])
defp other_data(actor, content) do
%{host: host} = URI.parse(actor.ap_id)
- datetime = DateTime.utc_now()
+ datetime = DateTime.utc_now() |> to_string()
context_id = "https://#{host}/contexts/#{UUID.generate()}"
activity_id = "https://#{host}/activities/#{UUID.generate()}"
object_id = "https://#{host}/objects/#{UUID.generate()}"
activity_data = %{
"actor" => actor.ap_id,
"context" => context_id,
"id" => activity_id,
"published" => datetime,
"type" => "Create",
"directMessage" => false
}
object_data = %{
"actor" => actor.ap_id,
"attachment" => [],
"attributedTo" => actor.ap_id,
"bcc" => [],
"bto" => [],
"content" => content,
"context" => context_id,
"conversation" => context_id,
"emoji" => %{},
"id" => object_id,
"published" => datetime,
"sensitive" => false,
"summary" => "",
"tag" => [],
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"type" => "Note"
}
{activity_data, object_data}
end
defp prepare_activity_data(actor, "public", _mention) do
obj_data = %{
"cc" => [actor.follower_address],
"to" => [Constants.as_public()]
}
act_data = %{
"cc" => [actor.follower_address],
"to" => [Constants.as_public()]
}
{act_data, obj_data}
end
defp prepare_activity_data(actor, "private", _mention) do
obj_data = %{
"cc" => [],
"to" => [actor.follower_address]
}
act_data = %{
"cc" => [],
"to" => [actor.follower_address]
}
{act_data, obj_data}
end
defp prepare_activity_data(actor, "unlisted", _mention) do
obj_data = %{
"cc" => [Constants.as_public()],
"to" => [actor.follower_address]
}
act_data = %{
"cc" => [Constants.as_public()],
"to" => [actor.follower_address]
}
{act_data, obj_data}
end
defp prepare_activity_data(_actor, "direct", mention) do
%{host: mentioned_host} = URI.parse(mention.ap_id)
obj_data = %{
"cc" => [],
"content" =>
"<span class=\"h-card\"><a class=\"u-url mention\" href=\"#{mention.ap_id}\" rel=\"ugc\">@<span>#{
mention.nickname
}</span></a></span> direct message",
"tag" => [
%{
"href" => mention.ap_id,
"name" => "@#{mention.nickname}@#{mentioned_host}",
"type" => "Mention"
}
],
"to" => [mention.ap_id]
}
act_data = %{
"cc" => [],
"directMessage" => true,
"to" => [mention.ap_id]
}
{act_data, obj_data}
end
defp get_reply_tasks("public", :user) do
[:friends_local, :friends_remote, :non_friends_local, :non_friends_remote, :user]
end
defp get_reply_tasks("public", group) when group in @friends_groups do
[:non_friends_local, :non_friends_remote, :user, :friends_local, :friends_remote]
end
defp get_reply_tasks("public", group) when group in @non_friends_groups do
[:user, :friends_local, :friends_remote, :non_friends_local, :non_friends_remote]
end
defp get_reply_tasks(visibility, :user) when visibility in ["unlisted", "private"] do
[:friends_local, :friends_remote, :user, :friends_local, :friends_remote]
end
defp get_reply_tasks(visibility, group)
when visibility in ["unlisted", "private"] and group in @friends_groups do
[:user, :friends_remote, :friends_local, :user]
end
defp get_reply_tasks(visibility, group)
when visibility in ["unlisted", "private"] and
group in @non_friends_groups,
do: []
defp get_reply_tasks("direct", :user), do: [:friends_local, :user, :friends_remote]
defp get_reply_tasks("direct", group) when group in @friends_groups,
do: [:user, group, :user]
defp get_reply_tasks("direct", group) when group in @non_friends_groups do
[:user, :non_friends_remote, :user, :non_friends_local]
end
defp insert_replies(tasks, visibility, users, acc) do
Enum.reduce(tasks, acc, fn
:user, {id, data} ->
insert_reply(users[:user], data, id, visibility)
group, {id, data} ->
replier = Enum.random(users[group])
insert_reply(replier, data, id, visibility)
end)
end
defp insert_direct_replies(tasks, user, list, acc) do
Enum.reduce(tasks, acc, fn
:user, {id, data} ->
{reply_id, _} = insert_reply(user, List.delete(data, "@" <> user.nickname), id, "direct")
{reply_id, data}
_, {id, data} ->
actor = Enum.random(list)
{reply_id, _} =
insert_reply(actor, List.delete(data, "@" <> actor.nickname), id, "direct")
{reply_id, data}
end)
end
defp insert_reply(actor, data, activity_id, visibility) do
{:ok, reply} =
CommonAPI.post(actor, %{
status: Enum.join(data, ", "),
visibility: visibility,
in_reply_to_status_id: activity_id
})
{reply.id, ["@" <> actor.nickname | data]}
end
defp get_random_mentions(_users, count) when count == 0, do: []
defp get_random_mentions(users, count) do
users
|> Enum.shuffle()
|> Enum.take(count)
|> Enum.map(&"@#{&1.nickname}")
end
defp get_random_create_activity_id do
Repo.one(
from(a in Pleroma.Activity,
where: fragment("(?)->>'type' = ?", a.data, ^"Create"),
order_by: fragment("RANDOM()"),
limit: 1,
select: a.id
)
)
end
end
diff --git a/benchmarks/mix/tasks/pleroma/benchmarks/tags.ex b/benchmarks/mix/tasks/pleroma/benchmarks/tags.ex
index c051335a5..a32de2db4 100644
--- a/benchmarks/mix/tasks/pleroma/benchmarks/tags.ex
+++ b/benchmarks/mix/tasks/pleroma/benchmarks/tags.ex
@@ -1,113 +1,114 @@
defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do
use Mix.Task
import Pleroma.LoadTesting.Helper, only: [clean_tables: 0]
import Ecto.Query
alias Pleroma.Repo
def run(_args) do
Mix.Pleroma.start_pleroma()
activities_count = Repo.aggregate(from(a in Pleroma.Activity), :count, :id)
if activities_count == 0 do
IO.puts("Did not find any activities, cleaning and generating")
clean_tables()
Pleroma.LoadTesting.Users.generate_users(10)
Pleroma.LoadTesting.Activities.generate_tagged_activities()
else
IO.puts("Found #{activities_count} activities, won't generate new ones")
end
tags = Enum.map(0..20, fn i -> {"For #tag_#{i}", "tag_#{i}"} end)
Enum.each(tags, fn {_, tag} ->
query =
from(o in Pleroma.Object,
where: fragment("(?)->'tag' \\? (?)", o.data, ^tag)
)
count = Repo.aggregate(query, :count, :id)
IO.puts("Database contains #{count} posts tagged with #{tag}")
end)
user = Repo.all(Pleroma.User) |> List.first()
Benchee.run(
%{
"Hashtag fetching, any" => fn tags ->
hashtag_fetching(
%{
"any" => tags
},
user,
false
)
end,
# Will always return zero results because no overlapping hashtags are generated.
"Hashtag fetching, all" => fn tags ->
hashtag_fetching(
%{
"all" => tags
},
user,
false
)
end
},
inputs:
tags
|> Enum.map(fn {_, v} -> v end)
|> Enum.chunk_every(2)
|> Enum.map(fn tags -> {"For #{inspect(tags)}", tags} end),
time: 5
)
Benchee.run(
%{
"Hashtag fetching" => fn tag ->
hashtag_fetching(
%{
"tag" => tag
},
user,
false
)
end
},
inputs: tags,
time: 5
)
end
defp hashtag_fetching(params, user, local_only) do
tags =
[params["tag"], params["any"]]
|> List.flatten()
|> Enum.uniq()
|> Enum.filter(& &1)
|> Enum.map(&String.downcase(&1))
tag_all =
params
|> Map.get("all", [])
|> Enum.map(&String.downcase(&1))
tag_reject =
params
|> Map.get("none", [])
|> Enum.map(&String.downcase(&1))
_activities =
- params
- |> Map.put(:type, "Create")
- |> Map.put(:local_only, local_only)
- |> Map.put(:blocking_user, user)
- |> Map.put(:muting_user, user)
- |> Map.put(:user, user)
- |> Map.put(:tag, tags)
- |> Map.put(:tag_all, tag_all)
- |> Map.put(:tag_reject, tag_reject)
+ %{
+ type: "Create",
+ local_only: local_only,
+ blocking_user: user,
+ muting_user: user,
+ user: user,
+ tag: tags,
+ tag_all: tag_all,
+ tag_reject: tag_reject,
+ }
|> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities()
end
end
diff --git a/benchmarks/mix/tasks/pleroma/benchmarks/timelines.ex b/benchmarks/mix/tasks/pleroma/benchmarks/timelines.ex
index aed32f194..3770ca163 100644
--- a/benchmarks/mix/tasks/pleroma/benchmarks/timelines.ex
+++ b/benchmarks/mix/tasks/pleroma/benchmarks/timelines.ex
@@ -1,70 +1,70 @@
defmodule Mix.Tasks.Pleroma.Benchmarks.Timelines do
use Mix.Task
import Pleroma.LoadTesting.Helper, only: [clean_tables: 0]
alias Pleroma.Web.CommonAPI
alias Plug.Conn
def run(_args) do
Mix.Pleroma.start_pleroma()
# Cleaning tables
clean_tables()
[{:ok, user} | users] = Pleroma.LoadTesting.Users.generate_users(1000)
# Let the user make 100 posts
1..100
- |> Enum.each(fn i -> CommonAPI.post(user, %{"status" => to_string(i)}) end)
+ |> Enum.each(fn i -> CommonAPI.post(user, %{status: to_string(i)}) end)
# Let 10 random users post
posts =
users
|> Enum.take_random(10)
|> Enum.map(fn {:ok, random_user} ->
- {:ok, activity} = CommonAPI.post(random_user, %{"status" => "."})
+ {:ok, activity} = CommonAPI.post(random_user, %{status: "."})
activity
end)
# let our user repeat them
posts
|> Enum.each(fn activity ->
CommonAPI.repeat(activity.id, user)
end)
Benchee.run(
%{
"user timeline, no followers" => fn reading_user ->
conn =
Phoenix.ConnTest.build_conn()
|> Conn.assign(:user, reading_user)
|> Conn.assign(:skip_link_headers, true)
- Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{"id" => user.id})
+ Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{id: user.id})
end
},
inputs: %{"user" => user, "no user" => nil},
time: 60
)
users
- |> Enum.each(fn {:ok, follower, user} -> Pleroma.User.follow(follower, user) end)
+ |> Enum.each(fn {:ok, follower} -> Pleroma.User.follow(follower, user) end)
Benchee.run(
%{
"user timeline, all following" => fn reading_user ->
conn =
Phoenix.ConnTest.build_conn()
|> Conn.assign(:user, reading_user)
|> Conn.assign(:skip_link_headers, true)
- Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{"id" => user.id})
+ Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{id: user.id})
end
},
inputs: %{"user" => user, "no user" => nil},
time: 60
)
end
end
diff --git a/config/benchmark.exs b/config/benchmark.exs
index a4d048f1b..9a7ea5669 100644
--- a/config/benchmark.exs
+++ b/config/benchmark.exs
@@ -1,88 +1,87 @@
import Config
# We don't run a server during test. If one is required,
# you can enable the server option below.
config :pleroma, Pleroma.Web.Endpoint,
http: [port: 4001],
- url: [port: 4001],
- server: true
+ url: [port: 4001]
# Disable captha for tests
config :pleroma, Pleroma.Captcha,
# It should not be enabled for automatic tests
enabled: false,
# A fake captcha service for tests
method: Pleroma.Captcha.Mock
# Print only warnings and errors during test
config :logger, level: :warn
config :pleroma, :auth, oauth_consumer_strategies: []
config :pleroma, Pleroma.Upload, filters: [], link_name: false
config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads"
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Test, enabled: true
config :pleroma, :instance,
email: "admin@example.com",
notify_email: "noreply@example.com",
skip_thread_containment: false,
federating: false,
external_user_synchronization: false
config :pleroma, :activitypub, sign_object_fetches: false
# Configure your database
config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres,
username: "postgres",
password: "postgres",
database: "pleroma_benchmark",
hostname: System.get_env("DB_HOST") || "localhost",
pool_size: 10
# Reduce hash rounds for testing
-config :pbkdf2_elixir, rounds: 1
+config :pleroma, :password, iterations: 1
config :tesla, adapter: Tesla.Mock
config :pleroma, :rich_media,
enabled: false,
ignore_hosts: [],
ignore_tld: ["local", "localdomain", "lan"]
config :web_push_encryption, :vapid_details,
subject: "mailto:administrator@example.com",
public_key:
"BLH1qVhJItRGCfxgTtONfsOKDc9VRAraXw-3NsmjMngWSh7NxOizN6bkuRA7iLTMPS82PjwJAr3UoK9EC1IFrz4",
private_key: "_-XZ0iebPrRfZ_o0-IatTdszYa8VCH1yLN-JauK7HHA"
config :pleroma, Pleroma.ScheduledActivity,
daily_user_limit: 2,
total_user_limit: 3,
enabled: false
config :pleroma, :rate_limit,
search: [{1000, 30}, {1000, 30}],
app_account_creation: {10_000, 5},
password_reset: {1000, 30}
config :pleroma, :http_security, report_uri: "https://endpoint.com"
config :pleroma, :http, send_user_agent: false
rum_enabled = System.get_env("RUM_ENABLED") == "true"
config :pleroma, :database, rum_enabled: rum_enabled
IO.puts("RUM enabled: #{rum_enabled}")
config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
if File.exists?("./config/benchmark.secret.exs") do
import_config "benchmark.secret.exs"
else
IO.puts(
"You may want to create benchmark.secret.exs to declare custom database connection parameters."
)
end
diff --git a/config/config.exs b/config/config.exs
index e7d1014bb..581f3831a 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -1,857 +1,859 @@
# .i;;;;i.
# iYcviii;vXY:
# .YXi .i1c.
# .YC. . in7.
# .vc. ...... ;1c.
# i7, .. .;1;
# i7, .. ... .Y1i
# ,7v .6MMM@; .YX,
# .7;. ..IMMMMMM1 :t7.
# .;Y. ;$MMMMMM9. :tc.
# vY. .. .nMMM@MMU. ;1v.
# i7i ... .#MM@M@C. .....:71i
# it: .... $MMM@9;.,i;;;i,;tti
# :t7. ..... 0MMMWv.,iii:::,,;St.
# .nC. ..... IMMMQ..,::::::,.,czX.
# .ct: ....... .ZMMMI..,:::::::,,:76Y.
# c2: ......,i..Y$M@t..:::::::,,..inZY
# vov ......:ii..c$MBc..,,,,,,,,,,..iI9i
# i9Y ......iii:..7@MA,..,,,,,,,,,....;AA:
# iIS. ......:ii::..;@MI....,............;Ez.
# .I9. ......:i::::...8M1..................C0z.
# .z9; ......:i::::,.. .i:...................zWX.
# vbv ......,i::::,,. ................. :AQY
# c6Y. .,...,::::,,..:t0@@QY. ................ :8bi
# :6S. ..,,...,:::,,,..EMMMMMMI. ............... .;bZ,
# :6o, .,,,,..:::,,,..i#MMMMMM#v................. YW2.
# .n8i ..,,,,,,,::,,,,.. tMMMMM@C:.................. .1Wn
# 7Uc. .:::,,,,,::,,,,.. i1t;,..................... .UEi
# 7C...::::::::::::,,,,.. .................... vSi.
# ;1;...,,::::::,......... .................. Yz:
# v97,......... .voC.
# izAotX7777777777777777777777777777777777777777Y7n92:
# .;CoIIIIIUAA666666699999ZZZZZZZZZZZZZZZZZZZZ6ov.
#
# !!! ATTENTION !!!
# DO NOT EDIT THIS FILE! THIS FILE CONTAINS THE DEFAULT VALUES FOR THE CON-
# FIGURATION! EDIT YOUR SECRET FILE (either prod.secret.exs, dev.secret.exs).
#
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
import Config
# General application configuration
config :pleroma, ecto_repos: [Pleroma.Repo]
config :pleroma, Pleroma.Repo,
telemetry_event: [Pleroma.Repo.Instrumenter],
migration_lock: nil
config :pleroma, Pleroma.Captcha,
enabled: true,
seconds_valid: 300,
method: Pleroma.Captcha.Native
config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.ch"
# Upload configuration
config :pleroma, Pleroma.Upload,
uploader: Pleroma.Uploaders.Local,
filters: [Pleroma.Upload.Filter.Dedupe],
link_name: false,
proxy_remote: false,
filename_display_max_length: 30,
default_description: nil,
base_url: nil
config :pleroma, Pleroma.Uploaders.Local, uploads: "uploads"
config :pleroma, Pleroma.Uploaders.S3,
bucket: nil,
bucket_namespace: nil,
truncated_namespace: nil,
streaming_enabled: true
config :ex_aws, :s3,
# host: "s3.wasabisys.com", # required if not Amazon AWS
access_key_id: nil,
secret_access_key: nil,
# region: "us-east-1", # may be required for Amazon AWS
scheme: "https://"
config :pleroma, :emoji,
shortcode_globs: ["/emoji/custom/**/*.png"],
pack_extensions: [".png", ".gif"],
groups: [
Custom: ["/emoji/*.png", "/emoji/**/*.png"]
],
default_manifest: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json",
shared_pack_cache_seconds_per_file: 60
config :pleroma, :uri_schemes,
valid_schemes: [
"https",
"http",
"dat",
"dweb",
"gopher",
"hyper",
"ipfs",
"ipns",
"irc",
"ircs",
"magnet",
"mailto",
"mumble",
"ssb",
"xmpp"
]
websocket_config = [
path: "/websocket",
serializer: [
{Phoenix.Socket.V1.JSONSerializer, "~> 1.0.0"},
{Phoenix.Socket.V2.JSONSerializer, "~> 2.0.0"}
],
timeout: 60_000,
transport_log: false,
compress: false
]
# Configures the endpoint
config :pleroma, Pleroma.Web.Endpoint,
url: [host: "localhost"],
http: [
ip: {127, 0, 0, 1},
dispatch: [
{:_,
[
{"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
{"/websocket", Phoenix.Endpoint.CowboyWebSocket,
{Phoenix.Transports.WebSocket,
{Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}},
{:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
]}
]
],
protocol: "https",
secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl",
signing_salt: "CqaoopA2",
render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)],
pubsub_server: Pleroma.PubSub,
secure_cookie_flag: true,
extra_cookie_attrs: [
"SameSite=Lax"
]
# Configures Elixir's Logger
config :logger, :console,
level: :debug,
format: "\n$time $metadata[$level] $message\n",
metadata: [:request_id]
config :logger, :ex_syslogger,
level: :debug,
ident: "pleroma",
format: "$metadata[$level] $message",
metadata: [:request_id]
config :quack,
level: :warn,
meta: [:all],
webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE"
config :mime, :types, %{
"application/xml" => ["xml"],
"application/xrd+xml" => ["xrd+xml"],
"application/jrd+json" => ["jrd+json"],
"application/activity+json" => ["activity+json"],
"application/ld+json" => ["activity+json"]
}
config :tesla, adapter: Tesla.Adapter.Hackney
# Configures http settings, upstream proxy etc.
config :pleroma, :http,
proxy_url: nil,
send_user_agent: true,
user_agent: :default,
adapter: []
config :pleroma, :instance,
name: "Pleroma",
email: "example@example.com",
notify_email: "noreply@example.com",
description: "Pleroma: An efficient and flexible fediverse server",
background_image: "/images/city.jpg",
instance_thumbnail: "/instance/thumbnail.jpeg",
limit: 5_000,
description_limit: 5_000,
remote_limit: 100_000,
upload_limit: 16_000_000,
avatar_upload_limit: 2_000_000,
background_upload_limit: 4_000_000,
banner_upload_limit: 4_000_000,
poll_limits: %{
max_options: 20,
max_option_chars: 200,
min_expiration: 0,
max_expiration: 365 * 24 * 60 * 60
},
registrations_open: true,
invites_enabled: false,
account_activation_required: false,
account_approval_required: false,
federating: true,
federation_incoming_replies_max_depth: 100,
federation_reachability_timeout_days: 7,
federation_publisher_modules: [
Pleroma.Web.ActivityPub.Publisher
],
allow_relay: true,
public: true,
quarantined_instances: [],
static_dir: "instance/static/",
allowed_post_formats: [
"text/plain",
"text/html",
"text/markdown",
"text/bbcode"
],
staff_transparency: [],
autofollowed_nicknames: [],
autofollowing_nicknames: [],
max_pinned_statuses: 1,
attachment_links: false,
max_report_comment_size: 1000,
safe_dm_mentions: false,
healthcheck: false,
remote_post_retention_days: 90,
skip_thread_containment: true,
limit_to_local_content: :unauthenticated,
user_bio_length: 5000,
user_name_length: 100,
max_account_fields: 10,
max_remote_account_fields: 20,
account_field_name_length: 512,
account_field_value_length: 2048,
registration_reason_length: 500,
external_user_synchronization: true,
extended_nickname_format: true,
cleanup_attachments: false,
multi_factor_authentication: [
totp: [
# digits 6 or 8
digits: 6,
period: 30
],
backup_codes: [
number: 5,
length: 16
]
],
show_reactions: true,
password_reset_token_validity: 60 * 60 * 24
config :pleroma, :welcome,
direct_message: [
enabled: false,
sender_nickname: nil,
message: nil
],
chat_message: [
enabled: false,
sender_nickname: nil,
message: nil
],
email: [
enabled: false,
sender: nil,
subject: "Welcome to <%= instance_name %>",
html: "Welcome to <%= instance_name %>",
text: "Welcome to <%= instance_name %>"
]
config :pleroma, :feed,
post_title: %{
max_length: 100,
omission: "..."
}
config :pleroma, :markup,
# XXX - unfortunately, inline images must be enabled by default right now, because
# of custom emoji. Issue #275 discusses defanging that somehow.
allow_inline_images: true,
allow_headings: false,
allow_tables: false,
allow_fonts: false,
scrub_policy: [
Pleroma.HTML.Scrubber.Default,
Pleroma.HTML.Transform.MediaProxy
]
config :pleroma, :frontend_configurations,
pleroma_fe: %{
alwaysShowSubjectInput: true,
background: "/images/city.jpg",
collapseMessageWithSubject: false,
disableChat: false,
greentext: false,
hideFilteredStatuses: false,
hideMutedPosts: false,
hidePostStats: false,
hideSitename: false,
hideUserStats: false,
loginMethod: "password",
logo: "/static/logo.svg",
logoMargin: ".1em",
logoMask: true,
minimalScopesMode: false,
noAttachmentLinks: false,
nsfwCensorImage: "",
postContentType: "text/plain",
redirectRootLogin: "/main/friends",
redirectRootNoLogin: "/main/all",
scopeCopy: true,
sidebarRight: false,
showFeaturesPanel: true,
showInstanceSpecificPanel: false,
subjectLineBehavior: "email",
theme: "pleroma-dark",
webPushNotifications: false
}
config :pleroma, :assets,
mascots: [
pleroma_fox_tan: %{
url: "/images/pleroma-fox-tan-smol.png",
mime_type: "image/png"
},
pleroma_fox_tan_shy: %{
url: "/images/pleroma-fox-tan-shy.png",
mime_type: "image/png"
}
],
default_mascot: :pleroma_fox_tan
config :pleroma, :manifest,
icons: [
%{
src: "/static/logo.svg",
type: "image/svg+xml"
}
],
theme_color: "#282c37",
background_color: "#191b22"
config :pleroma, :activitypub,
unfollow_blocked: true,
outgoing_blocks: true,
blockers_visible: true,
follow_handshake_timeout: 500,
note_replies_output_limit: 5,
sign_object_fetches: true,
authorized_fetch_mode: false
config :pleroma, :streamer,
workers: 3,
overflow_workers: 2
config :pleroma, :user, deny_follow_blocked: true
config :pleroma, :mrf_normalize_markup, scrub_policy: Pleroma.HTML.Scrubber.Default
config :pleroma, :mrf_rejectnonpublic,
allow_followersonly: false,
allow_direct: false
config :pleroma, :mrf_hellthread,
delist_threshold: 10,
reject_threshold: 20
config :pleroma, :mrf_simple,
media_removal: [],
media_nsfw: [],
federated_timeline_removal: [],
report_removal: [],
reject: [],
followers_only: [],
accept: [],
avatar_removal: [],
banner_removal: [],
reject_deletes: []
config :pleroma, :mrf_keyword,
reject: [],
federated_timeline_removal: [],
replace: []
config :pleroma, :mrf_hashtag,
sensitive: ["nsfw"],
reject: [],
federated_timeline_removal: []
config :pleroma, :mrf_subchain, match_actor: %{}
config :pleroma, :mrf_activity_expiration, days: 365
config :pleroma, :mrf_vocabulary,
accept: [],
reject: []
# threshold of 7 days
config :pleroma, :mrf_object_age,
threshold: 604_800,
actions: [:delist, :strip_followers]
config :pleroma, :mrf_follow_bot, follower_nickname: nil
config :pleroma, :rich_media,
enabled: true,
ignore_hosts: [],
ignore_tld: ["local", "localdomain", "lan"],
parsers: [
Pleroma.Web.RichMedia.Parsers.TwitterCard,
Pleroma.Web.RichMedia.Parsers.OEmbed
],
failure_backoff: 60_000,
ttl_setters: [Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl]
config :pleroma, :media_proxy,
enabled: false,
invalidation: [
enabled: false,
provider: Pleroma.Web.MediaProxy.Invalidation.Script
],
proxy_opts: [
redirect_on_failure: false,
max_body_length: 25 * 1_048_576,
# Note: max_read_duration defaults to Pleroma.ReverseProxy.max_read_duration_default/1
max_read_duration: 30_000,
http: [
follow_redirect: true,
pool: :media
]
],
whitelist: []
config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Http,
method: :purge,
headers: [],
options: []
config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Script,
script_path: nil,
url_format: nil
# Note: media preview proxy depends on media proxy to be enabled
config :pleroma, :media_preview_proxy,
enabled: false,
thumbnail_max_width: 600,
thumbnail_max_height: 600,
image_quality: 85,
min_content_length: 100 * 1024
config :pleroma, :shout,
enabled: true,
limit: 5_000
config :phoenix, :format_encoders, json: Jason, "activity+json": Jason
config :phoenix, :json_library, Jason
config :phoenix, :filter_parameters, ["password", "confirm"]
config :pleroma, :gopher,
enabled: false,
ip: {0, 0, 0, 0},
port: 9999
config :pleroma, Pleroma.Web.Metadata,
providers: [
Pleroma.Web.Metadata.Providers.OpenGraph,
Pleroma.Web.Metadata.Providers.TwitterCard
],
unfurl_nsfw: false
config :pleroma, Pleroma.Web.Preload,
providers: [
Pleroma.Web.Preload.Providers.Instance
]
config :pleroma, :http_security,
enabled: true,
sts: false,
sts_max_age: 31_536_000,
ct_max_age: 2_592_000,
referrer_policy: "same-origin"
config :cors_plug,
max_age: 86_400,
methods: ["POST", "PUT", "DELETE", "GET", "PATCH", "OPTIONS"],
expose: [
"Link",
"X-RateLimit-Reset",
"X-RateLimit-Limit",
"X-RateLimit-Remaining",
"X-Request-Id",
"Idempotency-Key"
],
credentials: true,
headers: ["Authorization", "Content-Type", "Idempotency-Key"]
config :pleroma, Pleroma.User,
restricted_nicknames: [
".well-known",
"~",
"about",
"activities",
"api",
"auth",
"check_password",
"dev",
"friend-requests",
"inbox",
"internal",
"main",
"media",
"nodeinfo",
"notice",
"oauth",
"objects",
"ostatus_subscribe",
"pleroma",
"proxy",
"push",
"registration",
"relay",
"settings",
"status",
"tag",
"user-search",
"user_exists",
"users",
"web",
"verify_credentials",
"update_credentials",
"relationships",
"search",
"confirmation_resend",
"mfa"
],
email_blacklist: []
config :pleroma, Oban,
repo: Pleroma.Repo,
log: false,
queues: [
activity_expiration: 10,
token_expiration: 5,
filter_expiration: 1,
backup: 1,
federator_incoming: 50,
federator_outgoing: 50,
ingestion_queue: 50,
web_push: 50,
mailer: 10,
transmogrifier: 20,
scheduled_activities: 10,
poll_notifications: 10,
background: 5,
remote_fetcher: 2,
attachments_cleanup: 1,
new_users_digest: 1,
mute_expire: 5
],
plugins: [Oban.Plugins.Pruner],
crontab: [
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
]
config :pleroma, :workers,
retries: [
federator_incoming: 5,
federator_outgoing: 5
]
config :pleroma, Pleroma.Formatter,
class: false,
rel: "ugc",
new_window: false,
truncate: false,
strip_prefix: false,
extra: true,
validate_tld: :no_scheme
config :pleroma, :ldap,
enabled: System.get_env("LDAP_ENABLED") == "true",
host: System.get_env("LDAP_HOST") || "localhost",
port: String.to_integer(System.get_env("LDAP_PORT") || "389"),
ssl: System.get_env("LDAP_SSL") == "true",
sslopts: [],
tls: System.get_env("LDAP_TLS") == "true",
tlsopts: [],
base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
uid: System.get_env("LDAP_UID") || "cn"
config :esshd,
enabled: false
oauth_consumer_strategies =
System.get_env("OAUTH_CONSUMER_STRATEGIES")
|> to_string()
|> String.split()
|> Enum.map(&hd(String.split(&1, ":")))
ueberauth_providers =
for strategy <- oauth_consumer_strategies do
strategy_module_name = "Elixir.Ueberauth.Strategy.#{String.capitalize(strategy)}"
strategy_module = String.to_atom(strategy_module_name)
{String.to_atom(strategy), {strategy_module, [callback_params: ["state"]]}}
end
config :ueberauth,
Ueberauth,
base_path: "/oauth",
providers: ueberauth_providers
config :pleroma, :auth, oauth_consumer_strategies: oauth_consumer_strategies
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Sendmail, enabled: false
config :pleroma, Pleroma.Emails.UserEmail,
logo: nil,
styling: %{
link_color: "#d8a070",
background_color: "#2C3645",
content_background_color: "#1B2635",
header_color: "#d8a070",
text_color: "#b9b9ba",
text_muted_color: "#b9b9ba"
}
config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: false
config :prometheus, Pleroma.Web.Endpoint.MetricsExporter,
enabled: false,
auth: false,
ip_whitelist: [],
path: "/api/pleroma/app_metrics",
format: :text
config :pleroma, Pleroma.ScheduledActivity,
daily_user_limit: 25,
total_user_limit: 300,
enabled: true
config :pleroma, :email_notifications,
digest: %{
active: false,
interval: 7,
inactivity_threshold: 7
}
config :pleroma, :oauth2,
token_expires_in: 3600 * 24 * 365 * 100,
issue_new_refresh_token: true,
clean_expired_tokens: false
config :pleroma, :database, rum_enabled: false
config :pleroma, :features, improved_hashtag_timeline: :auto
config :pleroma, :populate_hashtags_table, fault_rate_allowance: 0.01
config :pleroma, :env, Mix.env()
config :http_signatures,
adapter: Pleroma.Signature
config :pleroma, :rate_limit,
authentication: {60_000, 15},
timeline: {500, 3},
search: [{1000, 10}, {1000, 30}],
app_account_creation: {1_800_000, 25},
relations_actions: {10_000, 10},
relation_id_action: {60_000, 2},
statuses_actions: {10_000, 15},
status_id_action: {60_000, 3},
password_reset: {1_800_000, 5},
account_confirmation_resend: {8_640_000, 5},
ap_routes: {60_000, 15}
config :pleroma, Pleroma.Workers.PurgeExpiredActivity, enabled: true, min_lifetime: 600
config :pleroma, Pleroma.Web.Plugs.RemoteIp,
enabled: true,
headers: ["x-forwarded-for"],
proxies: [],
reserved: [
"127.0.0.0/8",
"::1/128",
"fc00::/7",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16"
]
config :pleroma, :static_fe, enabled: false
# Example of frontend configuration
# This example will make us serve the primary frontend from the
# frontends directory within your `:pleroma, :instance, static_dir`.
# e.g., instance/static/frontends/pleroma/develop/
#
# With no frontend configuration, the bundled files from the `static` directory will
# be used.
#
# config :pleroma, :frontends,
# primary: %{"name" => "pleroma-fe", "ref" => "develop"},
# admin: %{"name" => "admin-fe", "ref" => "stable"},
# available: %{...}
config :pleroma, :frontends,
available: %{
"kenoma" => %{
"name" => "kenoma",
"git" => "https://git.pleroma.social/lambadalambda/kenoma",
"build_url" =>
"https://git.pleroma.social/lambadalambda/kenoma/-/jobs/artifacts/${ref}/download?job=build",
"ref" => "master"
},
"pleroma-fe" => %{
"name" => "pleroma-fe",
"git" => "https://git.pleroma.social/pleroma/pleroma-fe",
"build_url" =>
"https://git.pleroma.social/pleroma/pleroma-fe/-/jobs/artifacts/${ref}/download?job=build",
"ref" => "develop"
},
"fedi-fe" => %{
"name" => "fedi-fe",
"git" => "https://git.pleroma.social/pleroma/fedi-fe",
"build_url" =>
"https://git.pleroma.social/pleroma/fedi-fe/-/jobs/artifacts/${ref}/download?job=build",
"ref" => "master",
"custom-http-headers" => [
{"service-worker-allowed", "/"}
]
},
"admin-fe" => %{
"name" => "admin-fe",
"git" => "https://git.pleroma.social/pleroma/admin-fe",
"build_url" =>
"https://git.pleroma.social/pleroma/admin-fe/-/jobs/artifacts/${ref}/download?job=build",
"ref" => "develop"
},
"soapbox-fe" => %{
"name" => "soapbox-fe",
"git" => "https://gitlab.com/soapbox-pub/soapbox-fe",
"build_url" =>
"https://gitlab.com/soapbox-pub/soapbox-fe/-/jobs/artifacts/${ref}/download?job=build-production",
"ref" => "v1.0.0",
"build_dir" => "static"
}
}
config :pleroma, :web_cache_ttl,
activity_pub: nil,
activity_pub_question: 30_000
config :pleroma, :modules, runtime_dir: "instance/modules"
config :pleroma, configurable_from_database: false
config :pleroma, Pleroma.Repo,
parameters: [gin_fuzzy_search_limit: "500"],
prepare: :unnamed
config :pleroma, :connections_pool,
reclaim_multiplier: 0.1,
connection_acquisition_wait: 250,
connection_acquisition_retries: 5,
max_connections: 250,
max_idle_time: 30_000,
retry: 0,
connect_timeout: 5_000
config :pleroma, :pools,
federation: [
size: 50,
max_waiting: 10,
recv_timeout: 10_000
],
media: [
size: 50,
max_waiting: 20,
recv_timeout: 15_000
],
upload: [
size: 25,
max_waiting: 5,
recv_timeout: 15_000
],
default: [
size: 10,
max_waiting: 2,
recv_timeout: 5_000
]
config :pleroma, :hackney_pools,
federation: [
max_connections: 50,
timeout: 150_000
],
media: [
max_connections: 50,
timeout: 150_000
],
upload: [
max_connections: 25,
timeout: 300_000
]
config :pleroma, :majic_pool, size: 2
private_instance? = :if_instance_is_private
config :pleroma, :restrict_unauthenticated,
timelines: %{local: private_instance?, federated: private_instance?},
profiles: %{local: private_instance?, remote: private_instance?},
activities: %{local: private_instance?, remote: private_instance?}
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: false
config :pleroma, :mrf,
policies: [Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy, Pleroma.Web.ActivityPub.MRF.TagPolicy],
transparency: true,
transparency_exclusions: []
config :tzdata, :http_client, Pleroma.HTTP.Tzdata
config :ex_aws, http_client: Pleroma.HTTP.ExAws
config :web_push_encryption, http_client: Pleroma.HTTP.WebPush
config :pleroma, :instances_favicons, enabled: false
config :floki, :html_parser, Floki.HTMLParser.FastHtml
config :pleroma, Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.PleromaAuthenticator
config :pleroma, Pleroma.User.Backup,
purge_after_days: 30,
limit_days: 7,
dir: nil
config :pleroma, ConcurrentLimiter, [
{Pleroma.Web.RichMedia.Helpers, [max_running: 5, max_waiting: 5]},
{Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, [max_running: 5, max_waiting: 5]}
]
+config :pleroma, :search, provider: Pleroma.Search.Builtin
+
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"
diff --git a/lib/mix/tasks/pleroma/search.ex b/lib/mix/tasks/pleroma/search.ex
new file mode 100644
index 000000000..9e0f376c3
--- /dev/null
+++ b/lib/mix/tasks/pleroma/search.ex
@@ -0,0 +1,46 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Pleroma.Search do
+ use Mix.Task
+ import Mix.Pleroma
+ import Ecto.Query
+ alias Pleroma.Activity
+ alias Pleroma.Pagination
+
+ @shortdoc "Manages elasticsearch"
+
+ def run(["import" | _rest]) do
+ start_pleroma()
+
+ from(a in Activity, where: not ilike(a.actor, "%/relay"))
+ |> Activity.with_preloaded_object()
+ |> Activity.with_preloaded_user_actor()
+ |> get_all
+ end
+
+ defp get_all(query, max_id \\ nil) do
+ params = %{limit: 20}
+
+ params =
+ if max_id == nil do
+ params
+ else
+ Map.put(params, :max_id, max_id)
+ end
+
+ res =
+ query
+ |> Pagination.fetch_paginated(params)
+
+ if res == [] do
+ :ok
+ else
+ res
+ |> Pleroma.Elasticsearch.bulk_post(:activities)
+
+ get_all(query, List.last(res).id)
+ end
+ end
+end
diff --git a/lib/pleroma/elasticsearch/document_mappings/note.ex b/lib/pleroma/elasticsearch/document_mappings/note.ex
new file mode 100644
index 000000000..35b70dd4a
--- /dev/null
+++ b/lib/pleroma/elasticsearch/document_mappings/note.ex
@@ -0,0 +1,15 @@
+defmodule Pleroma.Elasticsearch.DocumentMappings.Activity do
+ alias Pleroma.Object
+
+ def id(obj), do: obj.id
+
+ def encode(%{object: %{data: %{"type" => "Note"}}} = activity) do
+ %{
+ _timestamp: activity.inserted_at,
+ user: activity.user_actor.nickname,
+ content: activity.object.data["content"],
+ instance: URI.parse(activity.user_actor.ap_id).host,
+ hashtags: Object.hashtags(activity.object)
+ }
+ end
+end
diff --git a/lib/pleroma/elasticsearch/store.ex b/lib/pleroma/elasticsearch/store.ex
new file mode 100644
index 000000000..31f77fadf
--- /dev/null
+++ b/lib/pleroma/elasticsearch/store.ex
@@ -0,0 +1,46 @@
+defmodule Pleroma.Elasticsearch do
+ alias Pleroma.Activity
+ alias Pleroma.Elasticsearch.DocumentMappings
+
+ defp url do
+ Pleroma.Config.get([:elasticsearch, :url])
+ end
+
+ def put(%Activity{} = activity) do
+ Elastix.Document.index(
+ url(),
+ "activities",
+ "activity",
+ DocumentMappings.Activity.id(activity),
+ DocumentMappings.Activity.encode(activity)
+ )
+ end
+
+ def bulk_post(data, :activities) do
+ d =
+ data
+ |> Enum.map(fn d ->
+ [
+ %{index: %{_id: DocumentMappings.Activity.id(d)}},
+ DocumentMappings.Activity.encode(d)
+ ]
+ end)
+ |> List.flatten()
+
+ Elastix.Bulk.post(
+ url(),
+ d,
+ index: "activities",
+ type: "activity"
+ )
+ end
+
+ def search_activities(q) do
+ Elastix.Search.search(
+ url(),
+ "activities",
+ ["activity"],
+ q
+ )
+ end
+end
diff --git a/lib/pleroma/search.ex b/lib/pleroma/search.ex
new file mode 100644
index 000000000..99bce632c
--- /dev/null
+++ b/lib/pleroma/search.ex
@@ -0,0 +1,12 @@
+defmodule Pleroma.Search do
+ @type search_map :: %{
+ statuses: [map],
+ accounts: [map],
+ hashtags: [map]
+ }
+
+ @doc """
+ Searches for stuff
+ """
+ @callback search(map, map, keyword) :: search_map
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/search/builtin.ex
similarity index 50%
copy from lib/pleroma/web/mastodon_api/controllers/search_controller.ex
copy to lib/pleroma/search/builtin.ex
index 64b177eb3..019713f52 100644
--- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
+++ b/lib/pleroma/search/builtin.ex
@@ -1,192 +1,137 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
+defmodule Pleroma.Search.Builtin do
+ @behaviour Pleroma.Search
-defmodule Pleroma.Web.MastodonAPI.SearchController do
- use Pleroma.Web, :controller
-
- alias Pleroma.Activity
alias Pleroma.Repo
alias Pleroma.User
- alias Pleroma.Web.ControllerHelper
- alias Pleroma.Web.Endpoint
+ alias Pleroma.Activity
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView
- alias Pleroma.Web.Plugs.OAuthScopesPlug
- alias Pleroma.Web.Plugs.RateLimiter
+ alias Pleroma.Web.Endpoint
require Logger
- plug(Pleroma.Web.ApiSpec.CastAndValidate)
-
- # Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search)
- plug(OAuthScopesPlug, %{scopes: ["read:search"], fallback: :proceed_unauthenticated})
-
- # Note: on private instances auth is required (EnsurePublicOrAuthenticatedPlug is not skipped)
-
- plug(RateLimiter, [name: :search] when action in [:search, :search2, :account_search])
-
- defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.SearchOperation
-
- def account_search(%{assigns: %{user: user}} = conn, %{q: query} = params) do
- accounts = User.search(query, search_options(params, user))
-
- conn
- |> put_view(AccountView)
- |> render("index.json",
- users: accounts,
- for: user,
- as: :user
- )
- end
-
- def search2(conn, params), do: do_search(:v2, conn, params)
- def search(conn, params), do: do_search(:v1, conn, params)
-
- defp do_search(version, %{assigns: %{user: user}} = conn, %{q: query} = params) do
- query = String.trim(query)
- options = search_options(params, user)
+ @impl Pleroma.Search
+ def search(_conn, %{q: query} = params, options) do
+ version = Keyword.get(options, :version)
timeout = Keyword.get(Repo.config(), :timeout, 15_000)
default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []}
- result =
- default_values
- |> Enum.map(fn {resource, default_value} ->
- if params[:type] in [nil, resource] do
- {resource, fn -> resource_search(version, resource, query, options) end}
- else
- {resource, fn -> default_value end}
- end
- end)
- |> Task.async_stream(fn {resource, f} -> {resource, with_fallback(f)} end,
- timeout: timeout,
- on_timeout: :kill_task
- )
- |> Enum.reduce(default_values, fn
- {:ok, {resource, result}}, acc ->
- Map.put(acc, resource, result)
-
- _error, acc ->
- acc
- end)
-
- json(conn, result)
- end
+ default_values
+ |> Enum.map(fn {resource, default_value} ->
+ if params[:type] in [nil, resource] do
+ {resource, fn -> resource_search(version, resource, query, options) end}
+ else
+ {resource, fn -> default_value end}
+ end
+ end)
+ |> Task.async_stream(fn {resource, f} -> {resource, with_fallback(f)} end,
+ timeout: timeout,
+ on_timeout: :kill_task
+ )
+ |> Enum.reduce(default_values, fn
+ {:ok, {resource, result}}, acc ->
+ Map.put(acc, resource, result)
- defp search_options(params, user) do
- [
- resolve: params[:resolve],
- following: params[:following],
- limit: params[:limit],
- offset: params[:offset],
- type: params[:type],
- author: get_author(params),
- embed_relationships: ControllerHelper.embed_relationships?(params),
- for_user: user
- ]
- |> Enum.filter(&elem(&1, 1))
+ _error, acc ->
+ acc
+ end)
end
defp resource_search(_, "accounts", query, options) do
accounts = with_fallback(fn -> User.search(query, options) end)
AccountView.render("index.json",
users: accounts,
for: options[:for_user],
embed_relationships: options[:embed_relationships]
)
end
defp resource_search(_, "statuses", query, options) do
statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end)
StatusView.render("index.json",
activities: statuses,
for: options[:for_user],
as: :activity
)
end
defp resource_search(:v2, "hashtags", query, options) do
tags_path = Endpoint.url() <> "/tag/"
query
|> prepare_tags(options)
|> Enum.map(fn tag ->
%{name: tag, url: tags_path <> tag}
end)
end
defp resource_search(:v1, "hashtags", query, options) do
prepare_tags(query, options)
end
defp prepare_tags(query, options) do
tags =
query
|> preprocess_uri_query()
|> String.split(~r/[^#\w]+/u, trim: true)
|> Enum.uniq_by(&String.downcase/1)
explicit_tags = Enum.filter(tags, fn tag -> String.starts_with?(tag, "#") end)
tags =
if Enum.any?(explicit_tags) do
explicit_tags
else
tags
end
tags = Enum.map(tags, fn tag -> String.trim_leading(tag, "#") end)
tags =
if Enum.empty?(explicit_tags) && !options[:skip_joined_tag] do
add_joined_tag(tags)
else
tags
end
Pleroma.Pagination.paginate(tags, options)
end
- defp add_joined_tag(tags) do
- tags
- |> Kernel.++([joined_tag(tags)])
- |> Enum.uniq_by(&String.downcase/1)
- end
-
# If `query` is a URI, returns last component of its path, otherwise returns `query`
defp preprocess_uri_query(query) do
if query =~ ~r/https?:\/\// do
query
|> String.trim_trailing("/")
|> URI.parse()
|> Map.get(:path)
|> String.split("/")
|> Enum.at(-1)
else
query
end
end
+ defp add_joined_tag(tags) do
+ tags
+ |> Kernel.++([joined_tag(tags)])
+ |> Enum.uniq_by(&String.downcase/1)
+ end
+
defp joined_tag(tags) do
tags
|> Enum.map(fn tag -> String.capitalize(tag) end)
|> Enum.join()
end
defp with_fallback(f, fallback \\ []) do
try do
f.()
rescue
error ->
Logger.error("#{__MODULE__} search error: #{inspect(error)}")
fallback
end
end
-
- defp get_author(%{account_id: account_id}) when is_binary(account_id),
- do: User.get_cached_by_id(account_id)
-
- defp get_author(_params), do: nil
end
diff --git a/lib/pleroma/search/elasticsearch.ex b/lib/pleroma/search/elasticsearch.ex
new file mode 100644
index 000000000..af2e13e48
--- /dev/null
+++ b/lib/pleroma/search/elasticsearch.ex
@@ -0,0 +1,78 @@
+defmodule Pleroma.Search.Elasticsearch do
+ @behaviour Pleroma.Search
+
+ alias Pleroma.Web.MastodonAPI.StatusView
+
+ defp to_es(term) when is_binary(term) do
+ %{
+ match: %{
+ content: %{
+ query: term,
+ operator: "AND"
+ }
+ }
+ }
+ end
+
+ defp to_es({:quoted, term}), do: to_es(term)
+
+ defp to_es({:filter, ["hashtag", query]}) do
+ %{
+ term: %{
+ hashtags: %{
+ value: query
+ }
+ }
+ }
+ end
+
+ defp to_es({:filter, [field, query]}) do
+ %{
+ term: %{
+ field => %{
+ value: query
+ }
+ }
+ }
+ end
+
+ defp parse(query) do
+ query
+ |> SearchParser.parse!()
+ |> Enum.map(&to_es/1)
+ end
+
+ @impl Pleroma.Search
+ def search(%{assigns: %{user: user}} = _conn, %{q: query} = _params, _options) do
+ q = %{
+ query: %{
+ bool: %{
+ must: parse(query)
+ }
+ }
+ }
+
+ out = Pleroma.Elasticsearch.search_activities(q)
+
+ with {:ok, raw_results} <- out do
+ results =
+ raw_results
+ |> Map.get(:body, %{})
+ |> Map.get("hits", %{})
+ |> Map.get("hits", [])
+ |> Enum.map(fn result -> result["_id"] end)
+ |> Pleroma.Activity.all_by_ids_with_object()
+
+ %{
+ "accounts" => [],
+ "hashtags" => [],
+ "statuses" =>
+ StatusView.render("index.json",
+ activities: results,
+ for: user,
+ as: :activity
+ )
+ }
+ end
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
index 837787b9f..59fef42d6 100644
--- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
@@ -1,99 +1,101 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
import Ecto.Changeset
@primary_key false
embedded_schema 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)
field(:href, ObjectValidators.Uri)
field(:mediaType, :string, default: "application/octet-stream")
field(:width, :integer)
field(:height, :integer)
end
end
def cast_and_validate(data) do
data
|> cast_data()
|> validate_data()
end
def cast_data(data) do
%__MODULE__{}
|> changeset(data)
end
def changeset(struct, data) do
data =
data
|> fix_media_type()
|> fix_url()
struct
|> 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])
end
def url_changeset(struct, data) do
data = fix_media_type(data)
struct
|> cast(data, [:type, :href, :mediaType, :width, :height])
|> validate_inclusion(:type, ["Link"])
|> validate_required([:type, :href, :mediaType])
end
def fix_media_type(data) do
data = Map.put_new(data, "mediaType", data["mimeType"])
if is_bitstring(data["mediaType"]) && MIME.extensions(data["mediaType"]) != [] do
data
else
Map.put(data, "mediaType", "application/octet-stream")
end
end
- defp handle_href(href, mediaType) do
+ defp handle_href(href, mediaType, data) do
[
%{
"href" => href,
"type" => "Link",
- "mediaType" => mediaType
+ "mediaType" => mediaType,
+ "width" => data["width"],
+ "height" => data["height"]
}
]
end
defp fix_url(data) do
cond do
is_binary(data["url"]) ->
- Map.put(data, "url", handle_href(data["url"], data["mediaType"]))
+ Map.put(data, "url", handle_href(data["url"], data["mediaType"], data))
is_binary(data["href"]) and data["url"] == nil ->
- Map.put(data, "url", handle_href(data["href"], data["mediaType"]))
+ Map.put(data, "url", handle_href(data["href"], data["mediaType"], data))
true ->
data
end
end
defp validate_data(cng) do
cng
|> validate_inclusion(:type, ~w[Document Audio Image Video])
|> validate_required([:mediaType, :url, :type])
end
end
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index 4f29a4411..ed99079e2 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -1,282 +1,281 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Publisher do
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Delivery
alias Pleroma.HTTP
alias Pleroma.Instances
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
require Pleroma.Constants
import Pleroma.Web.ActivityPub.Visibility
@behaviour Pleroma.Web.Federator.Publisher
require Logger
@moduledoc """
ActivityPub outgoing federation module.
"""
@doc """
Determine if an activity can be represented by running it through Transmogrifier.
"""
def is_representable?(%Activity{} = activity) do
with {:ok, _data} <- Transmogrifier.prepare_outgoing(activity.data) do
true
else
_e ->
false
end
end
@doc """
Publish a single message to a peer. Takes a struct with the following
parameters set:
* `inbox`: the inbox to publish to
* `json`: the JSON message body representing the ActivityPub message
* `actor`: the actor which is signing the message
* `id`: the ActivityStreams URI of the message
"""
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
Logger.debug("Federating #{id} to #{inbox}")
uri = %{path: path} = URI.parse(inbox)
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
date = Pleroma.Signature.signed_date()
signature =
Pleroma.Signature.sign(actor, %{
"(request-target)": "post #{path}",
host: signature_host(uri),
"content-length": byte_size(json),
digest: digest,
date: date
})
- with {:ok, %{status: code}} when code in 200..299 <-
- result =
- HTTP.post(
- inbox,
- json,
- [
- {"Content-Type", "application/activity+json"},
- {"Date", date},
- {"signature", signature},
- {"digest", digest}
- ]
- ) do
+ with {:ok, %{status: code}} = result when code in 200..299 <-
+ HTTP.post(
+ inbox,
+ json,
+ [
+ {"Content-Type", "application/activity+json"},
+ {"Date", date},
+ {"signature", signature},
+ {"digest", digest}
+ ]
+ ) do
if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do
Instances.set_reachable(inbox)
end
result
else
{_post_result, response} ->
unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
{:error, response}
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
else
"#{host}:#{port}"
end
end
defp should_federate?(inbox, public) do
if public do
true
else
%{host: host} = URI.parse(inbox)
quarantined_instances =
Config.get([:instance, :quarantined_instances], [])
|> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
end
end
@spec recipients(User.t(), Activity.t()) :: list(User.t()) | []
defp recipients(actor, activity) do
followers =
if actor.follower_address in activity.recipients do
User.get_external_followers(actor)
else
[]
end
fetchers =
with %Activity{data: %{"type" => "Delete"}} <- activity,
%Object{id: object_id} <- Object.normalize(activity, fetch: false),
fetchers <- User.get_delivered_users_by_object_id(object_id),
_ <- Delivery.delete_all_by_object_id(object_id) do
fetchers
else
_ ->
[]
end
Pleroma.Web.Federator.Publisher.remote_users(actor, activity) ++ followers ++ fetchers
end
defp get_cc_ap_ids(ap_id, recipients) do
host = Map.get(URI.parse(ap_id), :host)
recipients
|> Enum.filter(fn %User{ap_id: ap_id} -> Map.get(URI.parse(ap_id), :host) == host end)
|> Enum.map(& &1.ap_id)
end
defp maybe_use_sharedinbox(%User{shared_inbox: nil, inbox: inbox}), do: inbox
defp maybe_use_sharedinbox(%User{shared_inbox: shared_inbox}), do: shared_inbox
@doc """
Determine a user inbox to use based on heuristics. These heuristics
are based on an approximation of the ``sharedInbox`` rules in the
[ActivityPub specification][ap-sharedinbox].
Please do not edit this function (or its children) without reading
the spec, as editing the code is likely to introduce some breakage
without some familiarity.
[ap-sharedinbox]: https://www.w3.org/TR/activitypub/#shared-inbox-delivery
"""
def determine_inbox(
%Activity{data: activity_data},
%User{inbox: inbox} = user
) do
to = activity_data["to"] || []
cc = activity_data["cc"] || []
type = activity_data["type"]
cond do
type == "Delete" ->
maybe_use_sharedinbox(user)
Pleroma.Constants.as_public() in to || Pleroma.Constants.as_public() in cc ->
maybe_use_sharedinbox(user)
length(to) + length(cc) > 1 ->
maybe_use_sharedinbox(user)
true ->
inbox
end
end
@doc """
Publishes an activity with BCC to all relevant peers.
"""
def publish(%User{} = actor, %{data: %{"bcc" => bcc}} = activity)
when is_list(bcc) and bcc != [] do
public = is_public?(activity)
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
recipients = recipients(actor, activity)
inboxes =
recipients
|> Enum.filter(&User.ap_enabled?/1)
|> Enum.map(fn actor -> actor.inbox end)
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|> Instances.filter_reachable()
Repo.checkout(fn ->
Enum.each(inboxes, fn {inbox, unreachable_since} ->
%User{ap_id: ap_id} = Enum.find(recipients, fn actor -> actor.inbox == inbox end)
# Get all the recipients on the same host and add them to cc. Otherwise, a remote
# instance would only accept a first message for the first recipient and ignore the rest.
cc = get_cc_ap_ids(ap_id, recipients)
json =
data
|> Map.put("cc", cc)
|> Jason.encode!()
Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{
inbox: inbox,
json: json,
actor_id: actor.id,
id: activity.data["id"],
unreachable_since: unreachable_since
})
end)
end)
end
# Publishes an activity to all relevant peers.
def publish(%User{} = actor, %Activity{} = activity) do
public = is_public?(activity)
if public && Config.get([:instance, :allow_relay]) do
Logger.debug(fn -> "Relaying #{activity.data["id"]} out" end)
Relay.publish(activity)
end
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
json = Jason.encode!(data)
recipients(actor, activity)
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
|> Enum.map(fn %User{} = user ->
determine_inbox(activity, user)
end)
|> Enum.uniq()
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|> Instances.filter_reachable()
|> Enum.each(fn {inbox, unreachable_since} ->
Pleroma.Web.Federator.Publisher.enqueue_one(
__MODULE__,
%{
inbox: inbox,
json: json,
actor_id: actor.id,
id: activity.data["id"],
unreachable_since: unreachable_since
}
)
end)
end
def gather_webfinger_links(%User{} = user) do
[
%{"rel" => "self", "type" => "application/activity+json", "href" => user.ap_id},
%{
"rel" => "self",
"type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"href" => user.ap_id
},
%{
"rel" => "http://ostatus.org/schema/1.0/subscribe",
"template" => "#{Pleroma.Web.Endpoint.url()}/ostatus_subscribe?acct={uri}"
}
]
end
def gather_nodeinfo_protocol_names, do: ["activitypub"]
end
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
index 6f685cb7b..0c93b1976 100644
--- a/lib/pleroma/web/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -1,600 +1,618 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Activity
alias Pleroma.Conversation.Participation
alias Pleroma.Formatter
alias Pleroma.Object
alias Pleroma.ThreadMute
alias Pleroma.User
alias Pleroma.UserRelationship
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI.ActivityDraft
+ alias Pleroma.Elasticsearch
+ alias Pleroma.Config
import Pleroma.Web.Gettext
import Pleroma.Web.CommonAPI.Utils
require Pleroma.Constants
require Logger
def block(blocker, blocked) do
with {:ok, block_data, _} <- Builder.block(blocker, blocked),
{:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do
{:ok, block}
end
end
def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
:ok <- validate_chat_content_length(content, !!maybe_attachment),
{_, {:ok, chat_message_data, _meta}} <-
{:build_object,
Builder.chat_message(
user,
recipient.ap_id,
content |> format_chat_content,
attachment: maybe_attachment
)},
{_, {:ok, create_activity_data, _meta}} <-
{:build_create_activity, Builder.create(user, chat_message_data, [recipient.ap_id])},
{_, {:ok, %Activity{} = activity, _meta}} <-
{:common_pipeline,
Pipeline.common_pipeline(create_activity_data,
local: true,
idempotency_key: opts[:idempotency_key]
)} do
{:ok, activity}
else
{:common_pipeline, {:reject, _} = e} -> e
e -> e
end
end
defp format_chat_content(nil), do: nil
defp format_chat_content(content) do
{text, _, _} =
content
|> Formatter.html_escape("text/plain")
|> Formatter.linkify()
|> (fn {text, mentions, tags} ->
{String.replace(text, ~r/\r?\n/, "<br>"), mentions, tags}
end).()
text
end
defp validate_chat_content_length(_, true), do: :ok
defp validate_chat_content_length(nil, false), do: {:error, :no_content}
defp validate_chat_content_length(content, _) do
if String.length(content) <= Pleroma.Config.get([:instance, :chat_limit]) do
:ok
else
{:error, :content_too_long}
end
end
def unblock(blocker, blocked) do
with {_, %Activity{} = block} <- {:fetch_block, Utils.fetch_latest_block(blocker, blocked)},
{:ok, unblock_data, _} <- Builder.undo(blocker, block),
{:ok, unblock, _} <- Pipeline.common_pipeline(unblock_data, local: true) do
{:ok, unblock}
else
{:fetch_block, nil} ->
if User.blocks?(blocker, blocked) do
User.unblock(blocker, blocked)
{:ok, :no_activity}
else
{:error, :not_blocking}
end
e ->
e
end
end
def follow(follower, followed) do
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
with {:ok, follow_data, _} <- Builder.follow(follower, followed),
{:ok, activity, _} <- Pipeline.common_pipeline(follow_data, local: true),
{:ok, follower, followed} <- User.wait_and_refresh(timeout, follower, followed) do
if activity.data["state"] == "reject" do
{:error, :rejected}
else
{:ok, follower, followed, activity}
end
end
end
def unfollow(follower, unfollowed) do
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed),
{:ok, _subscription} <- User.unsubscribe(follower, unfollowed) do
{:ok, follower}
end
end
def accept_follow_request(follower, followed) do
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, accept_data, _} <- Builder.accept(followed, follow_activity),
{:ok, _activity, _} <- Pipeline.common_pipeline(accept_data, local: true) do
{:ok, follower}
end
end
def reject_follow_request(follower, followed) do
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, reject_data, _} <- Builder.reject(followed, follow_activity),
{:ok, _activity, _} <- Pipeline.common_pipeline(reject_data, local: true) do
{:ok, follower}
end
end
def delete(activity_id, user) do
with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <-
{:find_activity, Activity.get_by_id(activity_id)},
{_, %Object{} = object, _} <-
{:find_object, Object.normalize(activity, fetch: false), activity},
true <- User.superuser?(user) || user.ap_id == object.data["actor"],
{:ok, delete_data, _} <- Builder.delete(user, object.data["id"]),
{:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do
{:ok, delete}
else
{:find_activity, _} ->
{:error, :not_found}
{:find_object, nil, %Activity{data: %{"actor" => actor, "object" => object}}} ->
# We have the create activity, but not the object, it was probably pruned.
# Insert a tombstone and try again
with {:ok, tombstone_data, _} <- Builder.tombstone(actor, object),
{:ok, _tombstone} <- Object.create(tombstone_data) do
delete(activity_id, user)
else
_ ->
Logger.error(
"Could not insert tombstone for missing object on deletion. Object is #{object}."
)
{:error, dgettext("errors", "Could not delete")}
end
_ ->
{:error, dgettext("errors", "Could not delete")}
end
end
def repeat(id, user, params \\ %{}) do
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
object = %Object{} <- Object.normalize(activity, fetch: false),
{_, nil} <- {:existing_announce, Utils.get_existing_announce(user.ap_id, object)},
public = public_announce?(object, params),
{:ok, announce, _} <- Builder.announce(user, object, public: public),
{:ok, activity, _} <- Pipeline.common_pipeline(announce, local: true) do
{:ok, activity}
else
{:existing_announce, %Activity{} = announce} ->
{:ok, announce}
_ ->
{:error, :not_found}
end
end
def unrepeat(id, user) do
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
{:find_activity, Activity.get_by_id(id)},
%Object{} = note <- Object.normalize(activity, fetch: false),
%Activity{} = announce <- Utils.get_existing_announce(user.ap_id, note),
{:ok, undo, _} <- Builder.undo(user, announce),
{:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do
{:ok, activity}
else
{:find_activity, _} -> {:error, :not_found}
_ -> {:error, dgettext("errors", "Could not unrepeat")}
end
end
@spec favorite(User.t(), binary()) :: {:ok, Activity.t() | :already_liked} | {:error, any()}
def favorite(%User{} = user, id) do
case favorite_helper(user, id) do
{:ok, _} = res ->
res
{:error, :not_found} = res ->
res
{:error, e} ->
Logger.error("Could not favorite #{id}. Error: #{inspect(e, pretty: true)}")
{:error, dgettext("errors", "Could not favorite")}
end
end
def favorite_helper(user, id) do
with {_, %Activity{object: object}} <- {:find_object, Activity.get_by_id_with_object(id)},
{_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)},
{_, {:ok, %Activity{} = activity, _meta}} <-
{:common_pipeline,
Pipeline.common_pipeline(like_object, Keyword.put(meta, :local, true))} do
{:ok, activity}
else
{:find_object, _} ->
{:error, :not_found}
{:common_pipeline, {:error, {:validate, {:error, changeset}}}} = e ->
if {:object, {"already liked by this actor", []}} in changeset.errors do
{:ok, :already_liked}
else
{:error, e}
end
e ->
{:error, e}
end
end
def unfavorite(id, user) do
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
{:find_activity, Activity.get_by_id(id)},
%Object{} = note <- Object.normalize(activity, fetch: false),
%Activity{} = like <- Utils.get_existing_like(user.ap_id, note),
{:ok, undo, _} <- Builder.undo(user, like),
{:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do
{:ok, activity}
else
{:find_activity, _} -> {:error, :not_found}
_ -> {:error, dgettext("errors", "Could not unfavorite")}
end
end
def react_with_emoji(id, user, emoji) do
with %Activity{} = activity <- Activity.get_by_id(id),
object <- Object.normalize(activity, fetch: false),
{:ok, emoji_react, _} <- Builder.emoji_react(user, object, emoji),
{:ok, activity, _} <- Pipeline.common_pipeline(emoji_react, local: true) do
{:ok, activity}
else
_ ->
{:error, dgettext("errors", "Could not add reaction emoji")}
end
end
def unreact_with_emoji(id, user, emoji) do
with %Activity{} = reaction_activity <- Utils.get_latest_reaction(id, user, emoji),
{:ok, undo, _} <- Builder.undo(user, reaction_activity),
{:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do
{:ok, activity}
else
_ ->
{:error, dgettext("errors", "Could not remove reaction emoji")}
end
end
def vote(user, %{data: %{"type" => "Question"}} = object, choices) do
with :ok <- validate_not_author(object, user),
:ok <- validate_existing_votes(user, object),
{:ok, options, choices} <- normalize_and_validate_choices(choices, object) do
answer_activities =
Enum.map(choices, fn index ->
{:ok, answer_object, _meta} =
Builder.answer(user, object, Enum.at(options, index)["name"])
{:ok, activity_data, _meta} = Builder.create(user, answer_object, [])
{:ok, activity, _meta} =
activity_data
|> Map.put("cc", answer_object["cc"])
|> Map.put("context", answer_object["context"])
|> Pipeline.common_pipeline(local: true)
# TODO: Do preload of Pleroma.Object in Pipeline
Activity.normalize(activity.data)
end)
object = Object.get_cached_by_ap_id(object.data["id"])
{:ok, answer_activities, object}
end
end
defp validate_not_author(%{data: %{"actor" => ap_id}}, %{ap_id: ap_id}),
do: {:error, dgettext("errors", "Poll's author can't vote")}
defp validate_not_author(_, _), do: :ok
defp validate_existing_votes(%{ap_id: ap_id}, object) do
if Utils.get_existing_votes(ap_id, object) == [] do
:ok
else
{:error, dgettext("errors", "Already voted")}
end
end
defp get_options_and_max_count(%{data: %{"anyOf" => any_of}})
when is_list(any_of) and any_of != [],
do: {any_of, Enum.count(any_of)}
defp get_options_and_max_count(%{data: %{"oneOf" => one_of}})
when is_list(one_of) and one_of != [],
do: {one_of, 1}
defp normalize_and_validate_choices(choices, object) do
choices = Enum.map(choices, fn i -> if is_binary(i), do: String.to_integer(i), else: i end)
{options, max_count} = get_options_and_max_count(object)
count = Enum.count(options)
with {_, true} <- {:valid_choice, Enum.all?(choices, &(&1 < count))},
{_, true} <- {:count_check, Enum.count(choices) <= max_count} do
{:ok, options, choices}
else
{:valid_choice, _} -> {:error, dgettext("errors", "Invalid indices")}
{:count_check, _} -> {:error, dgettext("errors", "Too many choices")}
end
end
def public_announce?(_, %{visibility: visibility})
when visibility in ~w{public unlisted private direct},
do: visibility in ~w(public unlisted)
def public_announce?(object, _) do
Visibility.is_public?(object)
end
def get_visibility(_, _, %Participation{}), do: {"direct", "direct"}
def get_visibility(%{visibility: visibility}, in_reply_to, _)
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
visibility = {:list, String.to_integer(list_id)}
{visibility, get_replied_to_visibility(in_reply_to)}
end
def get_visibility(_, in_reply_to, _) when not is_nil(in_reply_to) do
visibility = get_replied_to_visibility(in_reply_to)
{visibility, visibility}
end
def get_visibility(_, in_reply_to, _), do: {"public", get_replied_to_visibility(in_reply_to)}
def get_replied_to_visibility(nil), do: nil
def get_replied_to_visibility(activity) do
with %Object{} = object <- Object.normalize(activity, fetch: false) do
Visibility.get_visibility(object)
end
end
def check_expiry_date({:ok, nil} = res), do: res
def check_expiry_date({:ok, in_seconds}) do
expiry = DateTime.add(DateTime.utc_now(), in_seconds)
if Pleroma.Workers.PurgeExpiredActivity.expires_late_enough?(expiry) do
{:ok, expiry}
else
{:error, "Expiry date is too soon"}
end
end
def check_expiry_date(expiry_str) do
Ecto.Type.cast(:integer, expiry_str)
|> check_expiry_date()
end
def listen(user, data) do
with {:ok, draft} <- ActivityDraft.listen(user, data) do
ActivityPub.listen(draft.changes)
end
end
+ def maybe_put_into_elasticsearch({:ok, activity}) do
+ if Config.get([:search, :provider]) == Pleroma.Search.Elasticsearch do
+ actor = Pleroma.Activity.user_actor(activity)
+
+ activity
+ |> Map.put(:user_actor, actor)
+ |> Elasticsearch.put()
+ end
+ end
+
+ def maybe_put_into_elasticsearch(_) do
+ {:ok, :skipped}
+ end
+
def post(user, %{status: _} = data) do
with {:ok, draft} <- ActivityDraft.create(user, data) do
- ActivityPub.create(draft.changes, draft.preview?)
+ activity = ActivityPub.create(draft.changes, draft.preview?)
+ maybe_put_into_elasticsearch(activity)
+ activity
end
end
@spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()}
def pin(id, %User{} = user) do
with %Activity{} = activity <- create_activity_by_id(id),
true <- activity_belongs_to_actor(activity, user.ap_id),
true <- object_type_is_allowed_for_pin(activity.object),
true <- activity_is_public(activity),
{:ok, pin_data, _} <- Builder.pin(user, activity.object),
{:ok, _pin, _} <-
Pipeline.common_pipeline(pin_data,
local: true,
activity_id: id
) do
{:ok, activity}
else
{:error, {:side_effects, error}} -> error
error -> error
end
end
defp create_activity_by_id(id) do
with nil <- Activity.create_by_id_with_object(id) do
{:error, :not_found}
end
end
defp activity_belongs_to_actor(%{actor: actor}, actor), do: true
defp activity_belongs_to_actor(_, _), do: {:error, :ownership_error}
defp object_type_is_allowed_for_pin(%{data: %{"type" => type}}) do
with false <- type in ["Note", "Article", "Question"] do
{:error, :not_allowed}
end
end
defp activity_is_public(activity) do
with false <- Visibility.is_public?(activity) do
{:error, :visibility_error}
end
end
@spec unpin(String.t(), User.t()) :: {:ok, User.t()} | {:error, term()}
def unpin(id, user) do
with %Activity{} = activity <- create_activity_by_id(id),
{:ok, unpin_data, _} <- Builder.unpin(user, activity.object),
{:ok, _unpin, _} <-
Pipeline.common_pipeline(unpin_data,
local: true,
activity_id: activity.id,
expires_at: activity.data["expires_at"],
featured_address: user.featured_address
) do
{:ok, activity}
end
end
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{} = 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)
end
def thread_muted?(_, _), do: false
def report(user, data) do
with {:ok, account} <- get_reported_account(data.account_id),
{:ok, {content_html, _, _}} <- make_report_content_html(data[:comment]),
{:ok, statuses} <- get_report_statuses(account, data) do
ActivityPub.flag(%{
context: Utils.generate_context_id(),
actor: user,
account: account,
statuses: statuses,
content: content_html,
forward: Map.get(data, :forward, false)
})
end
end
defp get_reported_account(account_id) do
case User.get_cached_by_id(account_id) do
%User{} = account -> {:ok, account}
_ -> {:error, dgettext("errors", "Account not found")}
end
end
def update_report_state(activity_ids, state) when is_list(activity_ids) do
case Utils.update_report_state(activity_ids, state) do
:ok -> {:ok, activity_ids}
_ -> {:error, dgettext("errors", "Could not update state")}
end
end
def update_report_state(activity_id, state) do
with %Activity{} = activity <- Activity.get_by_id(activity_id) do
Utils.update_report_state(activity, state)
else
nil -> {:error, :not_found}
_ -> {:error, dgettext("errors", "Could not update state")}
end
end
def update_activity_scope(activity_id, opts \\ %{}) do
with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
{:ok, activity} <- toggle_sensitive(activity, opts) do
set_visibility(activity, opts)
else
nil -> {:error, :not_found}
{:error, reason} -> {:error, reason}
end
end
defp toggle_sensitive(activity, %{sensitive: sensitive}) when sensitive in ~w(true false) do
toggle_sensitive(activity, %{sensitive: String.to_existing_atom(sensitive)})
end
defp toggle_sensitive(%Activity{object: object} = activity, %{sensitive: sensitive})
when is_boolean(sensitive) do
new_data = Map.put(object.data, "sensitive", sensitive)
{:ok, object} =
object
|> Object.change(%{data: new_data})
|> Object.update_and_set_cache()
{:ok, Map.put(activity, :object, object)}
end
defp toggle_sensitive(activity, _), do: {:ok, activity}
defp set_visibility(activity, %{visibility: visibility}) do
Utils.update_activity_visibility(activity, visibility)
end
defp set_visibility(activity, _), do: {:ok, activity}
def hide_reblogs(%User{} = user, %User{} = target) do
UserRelationship.create_reblog_mute(user, target)
end
def show_reblogs(%User{} = user, %User{} = target) do
UserRelationship.delete_reblog_mute(user, target)
end
def get_user(ap_id, fake_record_fallback \\ true) do
cond do
user = User.get_cached_by_ap_id(ap_id) ->
user
user = User.get_by_guessed_nickname(ap_id) ->
user
fake_record_fallback ->
# TODO: refactor (fake records is never a good idea)
User.error_user(ap_id)
true ->
nil
end
end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
index 64b177eb3..c8f820f00 100644
--- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
@@ -1,192 +1,69 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.SearchController do
use Pleroma.Web, :controller
- alias Pleroma.Activity
- alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ControllerHelper
- alias Pleroma.Web.Endpoint
alias Pleroma.Web.MastodonAPI.AccountView
- alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.Plugs.RateLimiter
require Logger
plug(Pleroma.Web.ApiSpec.CastAndValidate)
# Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search)
plug(OAuthScopesPlug, %{scopes: ["read:search"], fallback: :proceed_unauthenticated})
# Note: on private instances auth is required (EnsurePublicOrAuthenticatedPlug is not skipped)
plug(RateLimiter, [name: :search] when action in [:search, :search2, :account_search])
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.SearchOperation
def account_search(%{assigns: %{user: user}} = conn, %{q: query} = params) do
accounts = User.search(query, search_options(params, user))
conn
|> put_view(AccountView)
|> render("index.json",
users: accounts,
for: user,
as: :user
)
end
def search2(conn, params), do: do_search(:v2, conn, params)
def search(conn, params), do: do_search(:v1, conn, params)
- defp do_search(version, %{assigns: %{user: user}} = conn, %{q: query} = params) do
- query = String.trim(query)
- options = search_options(params, user)
- timeout = Keyword.get(Repo.config(), :timeout, 15_000)
- default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []}
-
- result =
- default_values
- |> Enum.map(fn {resource, default_value} ->
- if params[:type] in [nil, resource] do
- {resource, fn -> resource_search(version, resource, query, options) end}
- else
- {resource, fn -> default_value end}
- end
- end)
- |> Task.async_stream(fn {resource, f} -> {resource, with_fallback(f)} end,
- timeout: timeout,
- on_timeout: :kill_task
- )
- |> Enum.reduce(default_values, fn
- {:ok, {resource, result}}, acc ->
- Map.put(acc, resource, result)
-
- _error, acc ->
- acc
- end)
-
- json(conn, result)
+ defp do_search(version, %{assigns: %{user: user}} = conn, params) do
+ options =
+ search_options(params, user)
+ |> Keyword.put(:version, version)
+
+ search_provider = Pleroma.Config.get([:search, :provider])
+ json(conn, search_provider.search(conn, params, options))
end
defp search_options(params, user) do
[
resolve: params[:resolve],
following: params[:following],
limit: params[:limit],
offset: params[:offset],
type: params[:type],
author: get_author(params),
embed_relationships: ControllerHelper.embed_relationships?(params),
for_user: user
]
|> Enum.filter(&elem(&1, 1))
end
- defp resource_search(_, "accounts", query, options) do
- accounts = with_fallback(fn -> User.search(query, options) end)
-
- AccountView.render("index.json",
- users: accounts,
- for: options[:for_user],
- embed_relationships: options[:embed_relationships]
- )
- end
-
- defp resource_search(_, "statuses", query, options) do
- statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end)
-
- StatusView.render("index.json",
- activities: statuses,
- for: options[:for_user],
- as: :activity
- )
- end
-
- defp resource_search(:v2, "hashtags", query, options) do
- tags_path = Endpoint.url() <> "/tag/"
-
- query
- |> prepare_tags(options)
- |> Enum.map(fn tag ->
- %{name: tag, url: tags_path <> tag}
- end)
- end
-
- defp resource_search(:v1, "hashtags", query, options) do
- prepare_tags(query, options)
- end
-
- defp prepare_tags(query, options) do
- tags =
- query
- |> preprocess_uri_query()
- |> String.split(~r/[^#\w]+/u, trim: true)
- |> Enum.uniq_by(&String.downcase/1)
-
- explicit_tags = Enum.filter(tags, fn tag -> String.starts_with?(tag, "#") end)
-
- tags =
- if Enum.any?(explicit_tags) do
- explicit_tags
- else
- tags
- end
-
- tags = Enum.map(tags, fn tag -> String.trim_leading(tag, "#") end)
-
- tags =
- if Enum.empty?(explicit_tags) && !options[:skip_joined_tag] do
- add_joined_tag(tags)
- else
- tags
- end
-
- Pleroma.Pagination.paginate(tags, options)
- end
-
- defp add_joined_tag(tags) do
- tags
- |> Kernel.++([joined_tag(tags)])
- |> Enum.uniq_by(&String.downcase/1)
- end
-
- # If `query` is a URI, returns last component of its path, otherwise returns `query`
- defp preprocess_uri_query(query) do
- if query =~ ~r/https?:\/\// do
- query
- |> String.trim_trailing("/")
- |> URI.parse()
- |> Map.get(:path)
- |> String.split("/")
- |> Enum.at(-1)
- else
- query
- end
- end
-
- defp joined_tag(tags) do
- tags
- |> Enum.map(fn tag -> String.capitalize(tag) end)
- |> Enum.join()
- end
-
- defp with_fallback(f, fallback \\ []) do
- try do
- f.()
- rescue
- error ->
- Logger.error("#{__MODULE__} search error: #{inspect(error)}")
- fallback
- end
- end
-
defp get_author(%{account_id: account_id}) when is_binary(account_id),
do: User.get_cached_by_id(account_id)
defp get_author(_params), do: nil
end
diff --git a/mix.exs b/mix.exs
index 9385f7cf4..195fd3a9d 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,362 +1,367 @@
defmodule Pleroma.Mixfile do
use Mix.Project
def project do
[
app: :pleroma,
version: version("2.4.51"),
elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
elixirc_options: [warnings_as_errors: warnings_as_errors(Mix.env())],
xref: [exclude: [:eldap]],
start_permanent: Mix.env() == :prod,
aliases: aliases(),
deps: deps(),
test_coverage: [tool: ExCoveralls],
preferred_cli_env: ["coveralls.html": :test],
# Docs
name: "Pleroma",
homepage_url: "https://pleroma.social/",
source_url: "https://git.pleroma.social/pleroma/pleroma",
docs: [
source_url_pattern:
"https://git.pleroma.social/pleroma/pleroma/blob/develop/%{path}#L%{line}",
logo: "priv/static/images/logo.png",
extras: ["README.md", "CHANGELOG.md"] ++ Path.wildcard("docs/**/*.md"),
groups_for_extras: [
"Installation manuals": Path.wildcard("docs/installation/*.md"),
Configuration: Path.wildcard("docs/config/*.md"),
Administration: Path.wildcard("docs/admin/*.md"),
"Pleroma's APIs and Mastodon API extensions": Path.wildcard("docs/api/*.md")
],
main: "readme",
output: "priv/static/doc"
],
releases: [
pleroma: [
include_executables_for: [:unix],
applications: [ex_syslogger: :load, syslog: :load, eldap: :transient],
steps: [:assemble, &put_otp_version/1, &copy_files/1, &copy_nginx_config/1],
config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, []}]
]
]
]
end
def put_otp_version(%{path: target_path} = release) do
File.write!(
Path.join([target_path, "OTP_VERSION"]),
Pleroma.OTPVersion.version()
)
release
end
def copy_files(%{path: target_path} = release) do
File.cp_r!("./rel/files", target_path)
release
end
def copy_nginx_config(%{path: target_path} = release) do
File.cp!(
"./installation/pleroma.nginx",
Path.join([target_path, "installation", "pleroma.nginx"])
)
release
end
# Configuration for the OTP application.
#
# Type `mix help compile.app` for more information.
def application do
[
mod: {Pleroma.Application, []},
extra_applications: [
:logger,
:runtime_tools,
:comeonin,
:quack,
:fast_sanitize,
:ssl
],
included_applications: [:ex_syslogger]
]
end
# Specifies which paths to compile per environment.
- defp elixirc_paths(:benchmark), do: ["lib", "benchmarks"]
+ defp elixirc_paths(:benchmark), do: ["lib", "benchmarks", "priv/scrubbers"]
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
defp warnings_as_errors(:prod), do: false
defp warnings_as_errors(_), do: true
# Specifies OAuth dependencies.
defp oauth_deps do
oauth_strategy_packages =
System.get_env("OAUTH_CONSUMER_STRATEGIES")
|> to_string()
|> String.split()
|> Enum.map(fn strategy_entry ->
with [_strategy, dependency] <- String.split(strategy_entry, ":") do
dependency
else
[strategy] -> "ueberauth_#{strategy}"
end
end)
for s <- oauth_strategy_packages, do: {String.to_atom(s), ">= 0.0.0"}
end
# Specifies your project dependencies.
#
# Type `mix help deps` for examples and options.
defp deps do
[
{:phoenix, "~> 1.5.5"},
{:tzdata, "~> 1.0.3"},
{:plug_cowboy, "~> 2.3"},
{:phoenix_pubsub, "~> 2.0"},
{:phoenix_ecto, "~> 4.0"},
{:ecto_enum, "~> 1.4"},
{:ecto_sql, "~> 3.6.2"},
{:postgrex, ">= 0.15.5"},
{:oban, "~> 2.3.4"},
{:gettext, "~> 0.18"},
{:bcrypt_elixir, "~> 2.2"},
{:trailing_format_plug, "~> 0.0.7"},
{:fast_sanitize, "~> 0.2.0"},
{:html_entities, "~> 0.5", override: true},
{:phoenix_html, "~> 2.14"},
{:calendar, "~> 1.0"},
{:cachex, "~> 3.2"},
{:poison, "~> 3.0", override: true},
{:tesla, "~> 1.4.0", override: true},
{:castore, "~> 0.1"},
{:cowlib, "~> 2.9", override: true},
{:gun, "~> 2.0.0-rc.1", override: true},
{:jason, "~> 1.2"},
{:mogrify, "~> 0.9.1"},
{:ex_aws, "~> 2.1.6"},
{:ex_aws_s3, "~> 2.0"},
{:sweet_xml, "~> 0.6.6"},
{:earmark, "~> 1.4.15"},
{:bbcode_pleroma, "~> 0.2.0"},
{:crypt,
git: "https://github.com/msantos/crypt.git",
ref: "f75cd55325e33cbea198fb41fe41871392f8fb76"},
{:cors_plug, "~> 2.0"},
{:web_push_encryption,
git: "https://github.com/lanodan/elixir-web-push-encryption.git", branch: "bugfix/otp-24"},
{:swoosh, "~> 1.0"},
{:phoenix_swoosh, "~> 0.3"},
{:gen_smtp, "~> 0.13"},
{:ex_syslogger, "~> 1.4"},
{:floki, "~> 0.27"},
{:timex, "~> 3.6"},
{:ueberauth, "~> 0.4"},
{:linkify, "~> 0.5.1"},
{:http_signatures, "~> 0.1.1"},
{:telemetry, "~> 0.3"},
{:poolboy, "~> 1.5"},
{:prometheus, "~> 4.6"},
{:prometheus_ex,
git: "https://git.pleroma.social/pleroma/elixir-libraries/prometheus.ex.git",
ref: "a4e9beb3c1c479d14b352fd9d6dd7b1f6d7deee5",
override: true},
{:prometheus_plugs, "~> 1.1"},
{:prometheus_phoenix, "~> 1.3"},
# Note: once `prometheus_phx` is integrated into `prometheus_phoenix`, remove the former:
{:prometheus_phx,
git: "https://git.pleroma.social/pleroma/elixir-libraries/prometheus-phx.git",
branch: "no-logging"},
{:prometheus_ecto, "~> 1.4"},
{:recon, "~> 2.5"},
{:quack, "~> 0.1.1"},
{:joken, "~> 2.0"},
{:benchee, "~> 1.0"},
{:pot, "~> 1.0"},
{:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)},
{:ex_const, "~> 0.2"},
{:plug_static_index_html, "~> 1.0.0"},
{:flake_id, "~> 0.1.0"},
{:concurrent_limiter,
git: "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git",
ref: "d81be41024569330f296fc472e24198d7499ba78"},
{:remote_ip,
git: "https://git.pleroma.social/pleroma/remote_ip.git",
ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"},
{:captcha,
git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
{:restarter, path: "./restarter"},
{:majic,
git: "https://git.pleroma.social/pleroma/elixir-libraries/majic.git",
ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"},
{:eblurhash, "~> 1.1.0"},
{:open_api_spex, "~> 3.10"},
+ {:elastix, ">= 0.0.0"},
+ {:search_parser,
+ git: "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git",
+ ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"},
+ {:nimble_parsec, "~> 1.0", override: true},
# indirect dependency version override
{:plug, "~> 1.10.4", override: true},
## dev & test
{:ex_doc, "~> 0.22", only: :dev, runtime: false},
{:ex_machina, "~> 2.4", only: :test},
{:credo, "~> 1.4", only: [:dev, :test], runtime: false},
{:mock, "~> 0.3.5", only: :test},
# temporary downgrade for excoveralls, hackney until hackney max_connections bug will be fixed
{:excoveralls, "0.12.3", only: :test},
{:hackney, "~> 1.18.0", override: true},
{:mox, "~> 1.0", only: :test},
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test}
] ++ oauth_deps()
end
# Aliases are shortcuts or tasks specific to the current project.
# For example, to create, migrate and run the seeds file at once:
#
# $ mix ecto.setup
#
# See the documentation for `Mix` for more info on aliases.
defp aliases do
[
"ecto.migrate": ["pleroma.ecto.migrate"],
"ecto.rollback": ["pleroma.ecto.rollback"],
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"],
test: ["ecto.create --quiet", "ecto.migrate", "test"],
docs: ["pleroma.docs", "docs"],
analyze: ["credo --strict --only=warnings,todo,fixme,consistency,readability"],
copyright: &add_copyright/1,
"copyright.bump": &bump_copyright/1
]
end
# Builds a version string made of:
# * the application version
# * a pre-release if ahead of the tag: the describe string (-count-commithash)
# * branch name
# * build metadata:
# * a build name if `PLEROMA_BUILD_NAME` or `:pleroma, :build_name` is defined
# * the mix environment if different than prod
defp version(version) do
identifier_filter = ~r/[^0-9a-z\-]+/i
git_available? = match?({_output, 0}, System.cmd("sh", ["-c", "command -v git"]))
git_pre_release =
if git_available? do
{tag, tag_err} =
System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true)
{describe, describe_err} = System.cmd("git", ["describe", "--tags", "--abbrev=8"])
{commit_hash, commit_hash_err} = System.cmd("git", ["rev-parse", "--short", "HEAD"])
# Pre-release version, denoted from patch version with a hyphen
cond do
tag_err == 0 and describe_err == 0 ->
describe
|> String.trim()
|> String.replace(String.trim(tag), "")
|> String.trim_leading("-")
|> String.trim()
commit_hash_err == 0 ->
"0-g" <> String.trim(commit_hash)
true ->
nil
end
end
# Branch name as pre-release version component, denoted with a dot
branch_name =
with true <- git_available?,
{branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]),
branch_name <- String.trim(branch_name),
branch_name <- System.get_env("PLEROMA_BUILD_BRANCH") || branch_name,
true <-
!Enum.any?(["master", "HEAD", "release/", "stable"], fn name ->
String.starts_with?(name, branch_name)
end) do
branch_name =
branch_name
|> String.trim()
|> String.replace(identifier_filter, "-")
branch_name
else
_ -> ""
end
build_name =
cond do
name = Application.get_env(:pleroma, :build_name) -> name
name = System.get_env("PLEROMA_BUILD_NAME") -> name
true -> nil
end
env_name = if Mix.env() != :prod, do: to_string(Mix.env())
env_override = System.get_env("PLEROMA_BUILD_ENV")
env_name =
case env_override do
nil -> env_name
env_override when env_override in ["", "prod"] -> nil
env_override -> env_override
end
# Pre-release version, denoted by appending a hyphen
# and a series of dot separated identifiers
pre_release =
[git_pre_release, branch_name]
|> Enum.filter(fn string -> string && string != "" end)
|> Enum.join(".")
|> (fn
"" -> nil
string -> "-" <> String.replace(string, identifier_filter, "-")
end).()
# Build metadata, denoted with a plus sign
build_metadata =
[build_name, env_name]
|> Enum.filter(fn string -> string && string != "" end)
|> Enum.join(".")
|> (fn
"" -> nil
string -> "+" <> String.replace(string, identifier_filter, "-")
end).()
[version, pre_release, build_metadata]
|> Enum.filter(fn string -> string && string != "" end)
|> Enum.join()
end
defp add_copyright(_) do
year = NaiveDateTime.utc_now().year
template = ~s[\
# Pleroma: A lightweight social networking server
# Copyright © 2017-#{year} Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
] |> String.replace("\n", "\\n")
find = "find lib test priv -type f \\( -name '*.ex' -or -name '*.exs' \\) -exec "
grep = "grep -L '# Copyright © [0-9\-]* Pleroma' {} \\;"
xargs = "xargs -n1 sed -i'' '1s;^;#{template};'"
:os.cmd(String.to_charlist("#{find}#{grep} | #{xargs}"))
end
defp bump_copyright(_) do
year = NaiveDateTime.utc_now().year
find = "find lib test priv -type f \\( -name '*.ex' -or -name '*.exs' \\)"
xargs =
"xargs sed -i'' 's;# Copyright © [0-9\-]* Pleroma.*$;# Copyright © 2017-#{year} Pleroma Authors <https://pleroma.social/>;'"
:os.cmd(String.to_charlist("#{find} | #{xargs}"))
end
end
diff --git a/mix.lock b/mix.lock
index 18d5e3bea..20e95c19f 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,130 +1,135 @@
%{
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"},
"base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
"bbcode": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/bbcode.git", "f2d267675e9a7e1ad1ea9beb4cc23382762b66c2", [ref: "v0.2.0"]},
"bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.0", "6cb662d5c1b0a8858801cf20997bd006e7016aa8c52959c9ef80e0f34fb60b7a", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2c81d61d4f6ed0e5cf7bf27a9109b791ff216a1034b3d541327484f46dd43769"},
"benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"cachex": {:hex, :cachex, "3.3.0", "6f2ebb8f27491fe39121bd207c78badc499214d76c695658b19d6079beeca5c2", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "d90e5ee1dde14cef33f6b187af4335b88748b72b30c038969176cd4e6ccc31a1"},
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e0f16822d578866e186a0974d65ad58cddc1e2ab", [ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"]},
"castore": {:hex, :castore, "0.1.10", "b01a007416a0ae4188e70b3b306236021b16c11474038ead7aff79dd75538c23", [:mix], [], "hexpm", "a48314e0cb45682db2ea27b8ebfa11bd6fa0a6e21a65e5772ad83ca136ff2665"},
"certifi": {:hex, :certifi, "2.8.0", "d4fb0a6bb20b7c9c3643e22507e42f356ac090a1dcea9ab99e27e0376d695eba", [:rebar3], [], "hexpm", "6ac7efc1c6f8600b08d625292d4bbf584e14847ce1b6b5c44d983d273e1097ea"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.3.2", "5c2f893d05c56ae3f5e24c1b983c2d5dfb88c6d979c9287a76a7feb1e1d8d646", [:mix], [], "hexpm", "d0993402844c49539aeadb3fe46a3c9bd190f1ecf86b6f9ebd71957534c95f04"},
"concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", "d81be41024569330f296fc472e24198d7499ba78", [ref: "d81be41024569330f296fc472e24198d7499ba78"]},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cors_plug": {:hex, :cors_plug, "2.0.3", "316f806d10316e6d10f09473f19052d20ba0a0ce2a1d910ddf57d663dac402ae", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ee4ae1418e6ce117fc42c2ba3e6cbdca4e95ecd2fe59a05ec6884ca16d469aea"},
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
"credo": {:hex, :credo, "1.5.5", "e8f422026f553bc3bebb81c8e8bf1932f498ca03339856c7fec63d3faac8424b", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dd8623ab7091956a855dc9f3062486add9c52d310dfd62748779c4315d8247de"},
"crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
"crypt": {:git, "https://github.com/msantos/crypt.git", "f75cd55325e33cbea198fb41fe41871392f8fb76", [ref: "f75cd55325e33cbea198fb41fe41871392f8fb76"]},
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
"db_connection": {:hex, :db_connection, "2.4.0", "d04b1b73795dae60cead94189f1b8a51cc9e1f911c234cc23074017c43c031e5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad416c21ad9f61b3103d254a71b63696ecadb6a917b36f563921e0de00d7d7c8"},
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
"earmark": {:hex, :earmark, "1.4.18", "618c4ff1563450d1832b7fb41dc6755e470f91a6fd4c70f350a58b14f64a7db8", [:mix], [{:earmark_parser, ">= 1.4.17", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "57ac3b6da3958ed09c669a9b159e86377fcccda56bacde8a209fa4dcdef52560"},
"earmark_parser": {:hex, :earmark_parser, "1.4.17", "6f3c7e94170377ba45241d394389e800fb15adc5de51d0a3cd52ae766aafd63f", [:mix], [], "hexpm", "f93ac89c9feca61c165b264b5837bf82344d13bebc634cd575cb711e2e342023"},
"eblurhash": {:hex, :eblurhash, "1.1.0", "e10ccae762598507ebfacf0b645ed49520f2afa3e7e9943e73a91117dffce415", [:rebar3], [], "hexpm", "2e6b889d09fddd374e3c5ac57c486138768763264e99ac1074ae5fa7fc9ab51d"},
"ecto": {:hex, :ecto, "3.6.2", "efdf52acfc4ce29249bab5417415bd50abd62db7b0603b8bab0d7b996548c2bc", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "efad6dfb04e6f986b8a3047822b0f826d9affe8e4ebdd2aeedbfcb14fd48884e"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
"ecto_sql": {:hex, :ecto_sql, "3.6.2", "9526b5f691701a5181427634c30655ac33d11e17e4069eff3ae1176c764e0ba3", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.6.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.4.0 or ~> 0.5.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5ec9d7e6f742ea39b63aceaea9ac1d1773d574ea40df5a53ef8afbd9242fdb6b"},
"eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"},
+ "elasticsearch": {:hex, :elasticsearch, "1.0.1", "8339538d90af6b280f10ecd02b1eae372f09373e629b336a13461babf7366495", [:mix], [{:httpoison, ">= 0.0.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sigaws, "~> 0.7", [hex: :sigaws, repo: "hexpm", optional: true]}, {:vex, "~> 0.6", [hex: :vex, repo: "hexpm", optional: false]}], "hexpm", "83e7d8b8bee3e7e19a06ab4d357d24845ac1da894e79678227fd52c0b7f71867"},
+ "elastix": {:hex, :elastix, "0.10.0", "7567da885677ba9deffc20063db5f3ca8cd10f23cff1ab3ed9c52b7063b7e340", [:mix], [{:httpoison, "~> 1.4", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0", [hex: :poison, repo: "hexpm", optional: true]}, {:retry, "~> 0.8", [hex: :retry, repo: "hexpm", optional: false]}], "hexpm", "5fb342ce068b20f7845f5dd198c2dc80d967deafaa940a6e51b846db82696d1d"},
"elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"},
"esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"},
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
"ex_aws": {:hex, :ex_aws, "2.1.9", "dc4865ecc20a05190a34a0ac5213e3e5e2b0a75a0c2835e923ae7bfeac5e3c31", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "3e6c776703c9076001fbe1f7c049535f042cb2afa0d2cbd3b47cbc4e92ac0d10"},
"ex_aws_s3": {:hex, :ex_aws_s3, "2.2.0", "07a09de557070320e264893c0acc8a1d2e7ddf80155736e0aed966486d1988e6", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "15175c613371e29e1f88b78ec8a4327389ca1ec5b34489744b175727496b21bd"},
"ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
"ex_doc": {:hex, :ex_doc, "0.24.2", "e4c26603830c1a2286dae45f4412a4d1980e1e89dc779fcd0181ed1d5a05c8d9", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "e134e1d9e821b8d9e4244687fb2ace58d479b67b282de5158333b0d57c6fb7da"},
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
"ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"},
"excoveralls": {:hex, :excoveralls, "0.12.3", "2142be7cb978a3ae78385487edda6d1aff0e482ffc6123877bb7270a8ffbcfe0", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "568a3e616c264283f5dea5b020783ae40eef3f7ee2163f7a67cbd7b35bcadada"},
"fast_html": {:hex, :fast_html, "2.0.4", "4910ee49f2f6b19692e3bf30bf97f1b6b7dac489cd6b0f34cd0fe3042c56ba30", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "3bb49d541dfc02ad5e425904f53376d758c09f89e521afc7d2b174b3227761ea"},
"fast_sanitize": {:hex, :fast_sanitize, "0.2.2", "3cbbaebaea6043865dfb5b4ecb0f1af066ad410a51470e353714b10c42007b81", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "69f204db9250afa94a0d559d9110139850f57de2b081719fbafa1e9a89e94466"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
"floki": {:hex, :floki, "0.30.1", "75d35526d3a1459920b6e87fdbc2e0b8a3670f965dd0903708d2b267e0904c55", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "e9c03524447d1c4cbfccd672d739b8c18453eee377846b119d4fd71b1a176bb8"},
"gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
"gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"},
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"},
"gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
"gun": {:hex, :gun, "2.0.0-rc.2", "7c489a32dedccb77b6e82d1f3c5a7dadfbfa004ec14e322cdb5e579c438632d2", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "6b9d1eae146410d727140dbf8b404b9631302ecc2066d1d12f22097ad7d254fc"},
"hackney": {:hex, :hackney, "1.18.0", "c4443d960bb9fba6d01161d01cd81173089686717d9490e5d3606644c48d121f", [:rebar3], [{:certifi, "~>2.8.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "9afcda620704d720db8c6a3123e9848d09c87586dc1c10479c42627b905b5c5e"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
"http_signatures": {:hex, :http_signatures, "0.1.1", "ca7ebc1b61542b163644c8c3b1f0e0f41037d35f2395940d3c6c7deceab41fd8", [:mix], [], "hexpm", "cc3b8a007322cc7b624c0c15eec49ee58ac977254ff529a3c482f681465942a3"},
"httpoison": {:hex, :httpoison, "1.8.0", "6b85dea15820b7804ef607ff78406ab449dd78bed923a49c7160e1886e987a3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "28089eaa98cf90c66265b6b5ad87c59a3729bea2e74e9d08f9b51eb9729b3c3a"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
"joken": {:hex, :joken, "2.3.0", "62a979c46f2c81dcb8ddc9150453b60d3757d1ac393c72bb20fc50a7b0827dc6", [:mix], [{:jose, "~> 1.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "57b263a79c0ec5d536ac02d569c01e6b4de91bd1cb825625fe90eab4feb7bc1e"},
"jose": {:hex, :jose, "1.11.1", "59da64010c69aad6cde2f5b9248b896b84472e99bd18f246085b7b9fe435dcdb", [:mix, :rebar3], [], "hexpm", "078f6c9fb3cd2f4cfafc972c814261a7d1e8d2b3685c0a76eb87e158efff1ac5"},
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
"libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"},
"linkify": {:hex, :linkify, "0.5.1", "6dc415cbc948b2f6ecec7cb226aab7ba9d3a1815bb501ae33e042334d707ecee", [:mix], [], "hexpm", "a3128c7e22fada4aa7214009501d8131e1fa3faf2f0a68b33dba379dc84ff944"},
"majic": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", "289cda1b6d0d70ccb2ba508a2b0bd24638db2880", [ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"]},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
"mock": {:hex, :mock, "0.3.7", "75b3bbf1466d7e486ea2052a73c6e062c6256fb429d6797999ab02fa32f29e03", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4da49a4609e41fd99b7836945c26f373623ea968cfb6282742bcb94440cf7e5c"},
"mogrify": {:hex, :mogrify, "0.9.1", "a26f107c4987477769f272bd0f7e3ac4b7b75b11ba597fd001b877beffa9c068", [:mix], [], "hexpm", "134edf189337d2125c0948bf0c228fdeef975c594317452d536224069a5b7f05"},
"mox": {:hex, :mox, "1.0.0", "4b3c7005173f47ff30641ba044eb0fe67287743eec9bd9545e37f3002b0a9f8b", [:mix], [], "hexpm", "201b0a20b7abdaaab083e9cf97884950f8a30a1350a1da403b3145e213c6f4df"},
"myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
- "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm", "5c040b8469c1ff1b10093d3186e2e10dbe483cd73d79ec017993fb3985b8a9b3"},
+ "nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"},
"nimble_pool": {:hex, :nimble_pool, "0.1.0", "ffa9d5be27eee2b00b0c634eb649aa27f97b39186fec3c493716c2a33e784ec6", [:mix], [], "hexpm", "343a1eaa620ddcf3430a83f39f2af499fe2370390d4f785cd475b4df5acaf3f9"},
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
"oban": {:hex, :oban, "2.3.4", "ec7509b9af2524d55f529cb7aee93d36131ae0bf0f37706f65d2fe707f4d9fd8", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c70ca0434758fd1805422ea4446af5e910ddc697c0c861549c8f0eb0cfbd2fdf"},
"open_api_spex": {:hex, :open_api_spex, "3.10.0", "94e9521ad525b3fcf6dc77da7c45f87fdac24756d4de588cb0816b413e7c1844", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "2dbb2bde3d2b821f06936e8dfaf3284331186556291946d84eeba3750ac28765"},
"p1_utils": {:hex, :p1_utils, "1.0.18", "3fe224de5b2e190d730a3c5da9d6e8540c96484cf4b4692921d1e28f0c32b01c", [:rebar3], [], "hexpm", "1fc8773a71a15553b179c986b22fbeead19b28fe486c332d4929700ffeb71f88"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"},
"phoenix": {:hex, :phoenix, "1.5.9", "a6368d36cfd59d917b37c44386e01315bc89f7609a10a45a22f47c007edf2597", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7e4bce20a67c012f1fbb0af90e5da49fa7bf0d34e3a067795703b74aef75427d"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.2.1", "13f124cf0a3ce0f1948cf24654c7b9f2347169ff75c1123f44674afee6af3b03", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "478a1bae899cac0a6e02be1deec7e2944b7754c04e7d4107fc5a517f877743c0"},
"phoenix_html": {:hex, :phoenix_html, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "0.3.3", "039435dd975f7e55953525b88f1d596f26c6141412584c16f4db109708a8ee68", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.0", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4a540cea32e05356541737033d666ee7fea7700eb2101bf76783adbfe06601cd"},
"plug": {:hex, :plug, "1.10.4", "41eba7d1a2d671faaf531fa867645bd5a3dce0957d8e2a3f398ccff7d2ef017f", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad1e233fe73d2eec56616568d260777b67f53148a999dc2d048f4eb9778fe4a0"},
"plug_cowboy": {:hex, :plug_cowboy, "2.5.0", "51c998f788c4e68fc9f947a5eba8c215fbb1d63a520f7604134cab0270ea6513", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5b2c8925a5e2587446f33810a58c01e66b3c345652eeec809b76ba007acde71a"},
"plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"postgrex": {:hex, :postgrex, "0.15.9", "46f8fe6f25711aeb861c4d0ae09780facfdf3adbd2fb5594ead61504dd489bda", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "610719103e4cb2223d4ab78f9f0f3e720320eeca6011415ab4137ddef730adee"},
"pot": {:hex, :pot, "1.0.1", "81b511b1fa7c3123171c265cb7065a1528cebd7277b0cbc94257c50a8b2e4c17", [:rebar3], [], "hexpm", "ed87f5976531d91528452faa1138a5328db7f9f20d8feaae15f5051f79bcfb6d"},
"prometheus": {:hex, :prometheus, "4.8.0", "1ce1e1002b173c336d61f186b56263346536e76814edd9a142e12aeb2d6c1ad2", [:mix, :rebar3], [], "hexpm", "0fc2e17103073edb3758a46a5d44b006191bf25b73cbaa2b779109de396afcb5"},
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"},
"prometheus_ex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/prometheus.ex.git", "a4e9beb3c1c479d14b352fd9d6dd7b1f6d7deee5", [ref: "a4e9beb3c1c479d14b352fd9d6dd7b1f6d7deee5"]},
"prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "c4d1404ac4e9d3d963da601db2a7d8ea31194f0017057fabf0cfb9bf5a6c8c75"},
"prometheus_phx": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/prometheus-phx.git", "9cd8f248c9381ffedc799905050abce194a97514", [branch: "no-logging"]},
"prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm", "0273a6483ccb936d79ca19b0ab629aef0dba958697c94782bb728b920dfc6a79"},
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "d736bfa7444112eb840027bb887832a0e403a4a3437f48028c3b29a2dbbd2543"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"recon": {:hex, :recon, "2.5.1", "430ffa60685ac1efdfb1fe4c97b8767c92d0d92e6e7c3e8621559ba77598678a", [:mix, :rebar3], [], "hexpm", "5721c6b6d50122d8f68cccac712caa1231f97894bab779eff5ff0f886cb44648"},
"remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]},
+ "retry": {:hex, :retry, "0.15.0", "ba6aaeba92905a396c18c299a07e638947b2ba781e914f803202bc1b9ae867c3", [:mix], [], "hexpm", "93d3310bce78c0a30cc94610684340a14adfc9136856a3f662e4d9ce6013c784"},
+ "search_parser": {:git, "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", "08971a81e68686f9ac465cfb6661d51c5e4e1e7f", [ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"]},
"sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
"swoosh": {:hex, :swoosh, "1.3.11", "34f79c57f19892b43bd2168de9ff5de478a721a26328ef59567aad4243e7a77b", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "f1e2a048db454f9982b9cf840f75e7399dd48be31ecc2a7dc10012a803b913af"},
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
"telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
"tesla": {:hex, :tesla, "1.4.1", "ff855f1cac121e0d16281b49e8f066c4a0d89965f98864515713878cca849ac8", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "95f5de35922c8c4b3945bee7406f66eb680b0955232f78f5fb7e853aa1ce201a"},
"timex": {:hex, :timex, "3.7.5", "3eca56e23bfa4e0848f0b0a29a92fa20af251a975116c6d504966e8a90516dfd", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "a15608dca680f2ef663d71c95842c67f0af08a0f3b1d00e17bbd22872e2874e4"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
"tzdata": {:hex, :tzdata, "1.0.5", "69f1ee029a49afa04ad77801febaf69385f3d3e3d1e4b56b9469025677b89a28", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "55519aa2a99e5d2095c1e61cc74c9be69688f8ab75c27da724eb8279ff402a5a"},
"ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
+ "vex": {:hex, :vex, "0.9.0", "613ea5eb3055662e7178b83e25b2df0975f68c3d8bb67c1645f0573e1a78d606", [:mix], [], "hexpm", "c69fff44d5c8aa3f1faee71bba1dcab05dd36364c5a629df8bb11751240c857f"},
"web_push_encryption": {:git, "https://github.com/lanodan/elixir-web-push-encryption.git", "026a043037a89db4da8f07560bc8f9c68bcf0cc0", [branch: "bugfix/otp-24"]},
"websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []},
}
diff --git a/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs
index 0e49fda99..9150b8d41 100644
--- a/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs
+++ b/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs
@@ -1,109 +1,141 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidatorTest do
use Pleroma.DataCase, async: true
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
import Pleroma.Factory
describe "attachments" do
test "works with honkerific attachments" do
attachment = %{
"mediaType" => "",
"name" => "",
"summary" => "298p3RG7j27tfsZ9RQ.jpg",
"type" => "Document",
"url" => "https://honk.tedunangst.com/d/298p3RG7j27tfsZ9RQ.jpg"
}
assert {:ok, attachment} =
AttachmentValidator.cast_and_validate(attachment)
|> Ecto.Changeset.apply_action(:insert)
assert attachment.mediaType == "application/octet-stream"
end
test "it turns mastodon attachments into our attachments" do
attachment = %{
"url" =>
"http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
"type" => "Document",
"name" => nil,
"mediaType" => "image/jpeg",
"blurhash" => "UD9jJz~VSbR#xT$~%KtQX9R,WAs9RjWBs:of"
}
{:ok, attachment} =
AttachmentValidator.cast_and_validate(attachment)
|> Ecto.Changeset.apply_action(:insert)
assert [
%{
href:
"http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
type: "Link",
mediaType: "image/jpeg"
}
] = attachment.url
assert attachment.mediaType == "image/jpeg"
assert attachment.blurhash == "UD9jJz~VSbR#xT$~%KtQX9R,WAs9RjWBs:of"
end
test "it handles our own uploads" do
user = insert(:user)
file = %Plug.Upload{
content_type: "image/jpeg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
{:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
{:ok, attachment} =
attachment.data
|> AttachmentValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert)
assert attachment.mediaType == "image/jpeg"
end
test "it handles image dimensions" do
attachment = %{
"url" => [
%{
"type" => "Link",
"mediaType" => "image/jpeg",
"href" => "https://example.com/images/1.jpg",
"width" => 200,
"height" => 100
}
],
"type" => "Document",
"name" => nil,
"mediaType" => "image/jpeg"
}
{:ok, attachment} =
AttachmentValidator.cast_and_validate(attachment)
|> Ecto.Changeset.apply_action(:insert)
assert [
%{
href: "https://example.com/images/1.jpg",
type: "Link",
mediaType: "image/jpeg",
width: 200,
height: 100
}
] = attachment.url
assert attachment.mediaType == "image/jpeg"
end
+
+ test "it transforms image dimentions to our internal format" do
+ attachment = %{
+ "type" => "Document",
+ "name" => "Hello world",
+ "url" => "https://media.example.tld/1.jpg",
+ "width" => 880,
+ "height" => 960,
+ "mediaType" => "image/jpeg",
+ "blurhash" => "eTKL26+HDjcEIBVl;ds+K6t301W.t7nit7y1E,R:v}ai4nXSt7V@of"
+ }
+
+ expected = %AttachmentValidator{
+ type: "Document",
+ name: "Hello world",
+ mediaType: "image/jpeg",
+ blurhash: "eTKL26+HDjcEIBVl;ds+K6t301W.t7nit7y1E,R:v}ai4nXSt7V@of",
+ url: [
+ %AttachmentValidator.UrlObjectValidator{
+ type: "Link",
+ mediaType: "image/jpeg",
+ href: "https://media.example.tld/1.jpg",
+ width: 880,
+ height: 960
+ }
+ ]
+ }
+
+ {:ok, ^expected} =
+ AttachmentValidator.cast_and_validate(attachment)
+ |> Ecto.Changeset.apply_action(:insert)
+ end
end
end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/video_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/video_handling_test.exs
index fc3ec7450..87c53ebf4 100644
--- a/test/pleroma/web/activity_pub/transmogrifier/video_handling_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier/video_handling_test.exs
@@ -1,119 +1,122 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Transmogrifier.VideoHandlingTest do
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.DataCase
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Object.Fetcher
alias Pleroma.Web.ActivityPub.Transmogrifier
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
test "skip converting the content when it is nil" do
data =
File.read!("test/fixtures/tesla_mock/framatube.org-video.json")
|> Jason.decode!()
|> Kernel.put_in(["object", "content"], nil)
{:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
assert object = Object.normalize(activity, fetch: false)
assert object.data["content"] == nil
end
test "it converts content of object to html" do
data = File.read!("test/fixtures/tesla_mock/framatube.org-video.json") |> Jason.decode!()
{:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
assert object = Object.normalize(activity, fetch: false)
assert object.data["content"] ==
"<p>Après avoir mené avec un certain succès la campagne « Dégooglisons Internet » en 2014, l’association Framasoft annonce fin 2019 arrêter progressivement un certain nombre de ses services alternatifs aux GAFAM. Pourquoi ?</p><p>Transcription par @aprilorg ici : <a href=\"https://www.april.org/deframasoftisons-internet-pierre-yves-gosset-framasoft\">https://www.april.org/deframasoftisons-internet-pierre-yves-gosset-framasoft</a></p>"
end
test "it remaps video URLs as attachments if necessary" do
{:ok, object} =
Fetcher.fetch_object_from_id(
"https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
)
assert object.data["url"] ==
"https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
assert object.data["attachment"] == [
%{
"type" => "Link",
"mediaType" => "video/mp4",
"url" => [
%{
"href" =>
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
"mediaType" => "video/mp4",
- "type" => "Link"
+ "type" => "Link",
+ "width" => 480
}
]
}
]
data = File.read!("test/fixtures/tesla_mock/framatube.org-video.json") |> Jason.decode!()
{:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
assert object = Object.normalize(activity, fetch: false)
assert object.data["attachment"] == [
%{
"type" => "Link",
"mediaType" => "video/mp4",
"url" => [
%{
"href" =>
"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4",
"mediaType" => "video/mp4",
- "type" => "Link"
+ "type" => "Link",
+ "height" => 1080
}
]
}
]
assert object.data["url"] ==
"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206"
end
test "it works for peertube videos with only their mpegURL map" do
data =
File.read!("test/fixtures/peertube/video-object-mpegURL-only.json")
|> Jason.decode!()
{:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
assert object = Object.normalize(activity, fetch: false)
assert object.data["attachment"] == [
%{
"type" => "Link",
"mediaType" => "video/mp4",
"url" => [
%{
"href" =>
"https://peertube.stream/static/streaming-playlists/hls/abece3c3-b9c6-47f4-8040-f3eed8c602e6/abece3c3-b9c6-47f4-8040-f3eed8c602e6-1080-fragmented.mp4",
"mediaType" => "video/mp4",
- "type" => "Link"
+ "type" => "Link",
+ "height" => 1080
}
]
}
]
assert object.data["url"] ==
"https://peertube.stream/videos/watch/abece3c3-b9c6-47f4-8040-f3eed8c602e6"
end
end
diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs
index 5a3b57acb..06daf6a9f 100644
--- a/test/pleroma/web/activity_pub/transmogrifier_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs
@@ -1,527 +1,567 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.DataCase
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.CommonAPI
import Mock
import Pleroma.Factory
import ExUnit.CaptureLog
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
setup do: clear_config([:instance, :max_remote_account_fields])
describe "handle_incoming" do
test "it works for incoming unfollows with an existing follow" do
user = insert(:user)
follow_data =
File.read!("test/fixtures/mastodon-follow-activity.json")
|> Jason.decode!()
|> Map.put("object", user.ap_id)
{:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
data =
File.read!("test/fixtures/mastodon-unfollow-activity.json")
|> Jason.decode!()
|> Map.put("object", follow_data)
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
assert data["type"] == "Undo"
assert data["object"]["type"] == "Follow"
assert data["object"]["object"] == user.ap_id
assert data["actor"] == "http://mastodon.example.org/users/admin"
refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
end
test "it accepts Flag activities" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "test post"})
object = Object.normalize(activity, fetch: false)
note_obj = %{
"type" => "Note",
"id" => activity.data["id"],
"content" => "test post",
"published" => object.data["published"],
"actor" => AccountView.render("show.json", %{user: user, skip_visibility_check: true})
}
message = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"cc" => [user.ap_id],
"object" => [user.ap_id, activity.data["id"]],
"type" => "Flag",
"content" => "blocked AND reported!!!",
"actor" => other_user.ap_id
}
assert {:ok, activity} = Transmogrifier.handle_incoming(message)
assert activity.data["object"] == [user.ap_id, note_obj]
assert activity.data["content"] == "blocked AND reported!!!"
assert activity.data["actor"] == other_user.ap_id
assert activity.data["cc"] == [user.ap_id]
end
test "it accepts Move activities" do
old_user = insert(:user)
new_user = insert(:user)
message = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Move",
"actor" => old_user.ap_id,
"object" => old_user.ap_id,
"target" => new_user.ap_id
}
assert :error = Transmogrifier.handle_incoming(message)
{:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
assert activity.actor == old_user.ap_id
assert activity.data["actor"] == old_user.ap_id
assert activity.data["object"] == old_user.ap_id
assert activity.data["target"] == new_user.ap_id
assert activity.data["type"] == "Move"
end
end
describe "prepare outgoing" do
test "it inlines private announced objects" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "hey", visibility: "private"})
{:ok, announce_activity} = CommonAPI.repeat(activity.id, user)
{:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data)
assert modified["object"]["content"] == "hey"
assert modified["object"]["actor"] == modified["object"]["attributedTo"]
end
test "it turns mentions into tags" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{status: "hey, @#{other_user.nickname}, how are ya? #2hu"})
with_mock Pleroma.Notification,
get_notified_from_activity: fn _, _ -> [] end do
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
object = modified["object"]
expected_mention = %{
"href" => other_user.ap_id,
"name" => "@#{other_user.nickname}",
"type" => "Mention"
}
expected_tag = %{
"href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
"type" => "Hashtag",
"name" => "#2hu"
}
refute called(Pleroma.Notification.get_notified_from_activity(:_, :_))
assert Enum.member?(object["tag"], expected_tag)
assert Enum.member?(object["tag"], expected_mention)
end
end
test "it adds the json-ld context and the conversation property" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "hey"})
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
assert modified["@context"] == Utils.make_json_ld_header()["@context"]
assert modified["object"]["conversation"] == modified["context"]
end
test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "hey"})
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
assert modified["object"]["actor"] == modified["object"]["attributedTo"]
end
test "it strips internal hashtag data" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "#2hu"})
expected_tag = %{
"href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
"type" => "Hashtag",
"name" => "#2hu"
}
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
assert modified["object"]["tag"] == [expected_tag]
end
test "it strips internal fields" do
user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{
status: "#2hu :firefox:",
generator: %{type: "Application", name: "TestClient", url: "https://pleroma.social"}
})
# Ensure injected application data made it into the activity
# as we don't have a Token to derive it from, otherwise it will
# be nil and the test will pass
assert %{
type: "Application",
name: "TestClient",
url: "https://pleroma.social"
} == activity.object.data["generator"]
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
assert length(modified["object"]["tag"]) == 2
assert is_nil(modified["object"]["emoji"])
assert is_nil(modified["object"]["like_count"])
assert is_nil(modified["object"]["announcements"])
assert is_nil(modified["object"]["announcement_count"])
assert is_nil(modified["object"]["context_id"])
assert is_nil(modified["object"]["generator"])
end
test "it strips internal fields of article" do
activity = insert(:article_activity)
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
assert length(modified["object"]["tag"]) == 2
assert is_nil(modified["object"]["emoji"])
assert is_nil(modified["object"]["like_count"])
assert is_nil(modified["object"]["announcements"])
assert is_nil(modified["object"]["announcement_count"])
assert is_nil(modified["object"]["context_id"])
assert is_nil(modified["object"]["likes"])
end
test "the directMessage flag is present" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "2hu :moominmamma:"})
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
assert modified["directMessage"] == false
{:ok, activity} = CommonAPI.post(user, %{status: "@#{other_user.nickname} :moominmamma:"})
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
assert modified["directMessage"] == false
{:ok, activity} =
CommonAPI.post(user, %{
status: "@#{other_user.nickname} :moominmamma:",
visibility: "direct"
})
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
assert modified["directMessage"] == true
end
test "it strips BCC field" do
user = insert(:user)
{:ok, list} = Pleroma.List.create("foo", user)
{:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
assert is_nil(modified["bcc"])
end
test "it can handle Listen activities" do
listen_activity = insert(:listen)
{:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
assert modified["type"] == "Listen"
user = insert(:user)
{:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
{:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
end
test "custom emoji urls are URI encoded" do
# :dinosaur: filename has a space -> dino walking.gif
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "everybody do the dinosaur :dinosaur:"})
{:ok, prepared} = Transmogrifier.prepare_outgoing(activity.data)
assert length(prepared["object"]["tag"]) == 1
url = prepared["object"]["tag"] |> List.first() |> Map.get("icon") |> Map.get("url")
assert url == "http://localhost:4001/emoji/dino%20walking.gif"
end
end
describe "user upgrade" do
test "it upgrades a user to activitypub" do
user =
insert(:user, %{
nickname: "rye@niu.moe",
local: false,
ap_id: "https://niu.moe/users/rye",
follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
})
user_two = insert(:user)
Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
{:ok, activity} = CommonAPI.post(user, %{status: "test"})
{:ok, unrelated_activity} = CommonAPI.post(user_two, %{status: "test"})
assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
user = User.get_cached_by_id(user.id)
assert user.note_count == 1
{:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
ObanHelpers.perform_all()
assert user.ap_enabled
assert user.note_count == 1
assert user.follower_address == "https://niu.moe/users/rye/followers"
assert user.following_address == "https://niu.moe/users/rye/following"
user = User.get_cached_by_id(user.id)
assert user.note_count == 1
activity = Activity.get_by_id(activity.id)
assert user.follower_address in activity.recipients
assert %{
"url" => [
%{
"href" =>
"https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
}
]
} = user.avatar
assert %{
"url" => [
%{
"href" =>
"https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
}
]
} = user.banner
refute "..." in activity.recipients
unrelated_activity = Activity.get_by_id(unrelated_activity.id)
refute user.follower_address in unrelated_activity.recipients
user_two = User.get_cached_by_id(user_two.id)
assert User.following?(user_two, user)
refute "..." in User.following(user_two)
end
end
describe "actor rewriting" do
test "it fixes the actor URL property to be a proper URI" do
data = %{
"url" => %{"href" => "http://example.com"}
}
rewritten = Transmogrifier.maybe_fix_user_object(data)
assert rewritten["url"] == "http://example.com"
end
end
describe "actor origin containment" do
test "it rejects activities which reference objects with bogus origins" do
data = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "http://mastodon.example.org/users/admin/activities/1234",
"actor" => "http://mastodon.example.org/users/admin",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"object" => "https://info.pleroma.site/activity.json",
"type" => "Announce"
}
assert capture_log(fn ->
{:error, _} = Transmogrifier.handle_incoming(data)
end) =~ "Object containment failed"
end
test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
data = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "http://mastodon.example.org/users/admin/activities/1234",
"actor" => "http://mastodon.example.org/users/admin",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"object" => "https://info.pleroma.site/activity2.json",
"type" => "Announce"
}
assert capture_log(fn ->
{:error, _} = Transmogrifier.handle_incoming(data)
end) =~ "Object containment failed"
end
test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
data = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "http://mastodon.example.org/users/admin/activities/1234",
"actor" => "http://mastodon.example.org/users/admin",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"object" => "https://info.pleroma.site/activity3.json",
"type" => "Announce"
}
assert capture_log(fn ->
{:error, _} = Transmogrifier.handle_incoming(data)
end) =~ "Object containment failed"
end
end
describe "fix_explicit_addressing" do
setup do
user = insert(:user)
[user: user]
end
test "moves non-explicitly mentioned actors to cc", %{user: user} do
explicitly_mentioned_actors = [
"https://pleroma.gold/users/user1",
"https://pleroma.gold/user2"
]
object = %{
"actor" => user.ap_id,
"to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
"cc" => [],
"tag" =>
Enum.map(explicitly_mentioned_actors, fn href ->
%{"type" => "Mention", "href" => href}
end)
}
fixed_object = Transmogrifier.fix_explicit_addressing(object, user.follower_address)
assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
end
test "does not move actor's follower collection to cc", %{user: user} do
object = %{
"actor" => user.ap_id,
"to" => [user.follower_address],
"cc" => []
}
fixed_object = Transmogrifier.fix_explicit_addressing(object, user.follower_address)
assert user.follower_address in fixed_object["to"]
refute user.follower_address in fixed_object["cc"]
end
test "removes recipient's follower collection from cc", %{user: user} do
recipient = insert(:user)
object = %{
"actor" => user.ap_id,
"to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
"cc" => [user.follower_address, recipient.follower_address]
}
fixed_object = Transmogrifier.fix_explicit_addressing(object, user.follower_address)
assert user.follower_address in fixed_object["cc"]
refute recipient.follower_address in fixed_object["cc"]
refute recipient.follower_address in fixed_object["to"]
end
end
describe "fix_summary/1" do
test "returns fixed object" do
assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
end
end
describe "fix_url/1" do
test "fixes data for object when url is map" do
object = %{
"url" => %{
"type" => "Link",
"mimeType" => "video/mp4",
"href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
}
}
assert Transmogrifier.fix_url(object) == %{
"url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
}
end
test "returns non-modified object" do
assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
end
end
describe "get_obj_helper/2" do
test "returns nil when cannot normalize object" do
assert capture_log(fn ->
refute Transmogrifier.get_obj_helper("test-obj-id")
end) =~ "Unsupported URI scheme"
end
@tag capture_log: true
test "returns {:ok, %Object{}} for success case" do
assert {:ok, %Object{}} =
Transmogrifier.get_obj_helper(
"https://mstdn.io/users/mayuutann/statuses/99568293732299394"
)
end
end
+
+ describe "fix_attachments/1" do
+ test "puts dimensions into attachment url field" do
+ object = %{
+ "attachment" => [
+ %{
+ "type" => "Document",
+ "name" => "Hello world",
+ "url" => "https://media.example.tld/1.jpg",
+ "width" => 880,
+ "height" => 960,
+ "mediaType" => "image/jpeg",
+ "blurhash" => "eTKL26+HDjcEIBVl;ds+K6t301W.t7nit7y1E,R:v}ai4nXSt7V@of"
+ }
+ ]
+ }
+
+ expected = %{
+ "attachment" => [
+ %{
+ "type" => "Document",
+ "name" => "Hello world",
+ "url" => [
+ %{
+ "type" => "Link",
+ "mediaType" => "image/jpeg",
+ "href" => "https://media.example.tld/1.jpg",
+ "width" => 880,
+ "height" => 960
+ }
+ ],
+ "mediaType" => "image/jpeg",
+ "blurhash" => "eTKL26+HDjcEIBVl;ds+K6t301W.t7nit7y1E,R:v}ai4nXSt7V@of"
+ }
+ ]
+ }
+
+ assert Transmogrifier.fix_attachments(object) == expected
+ end
+ end
end
diff --git a/test/pleroma/web/rich_media/parser_test.exs b/test/pleroma/web/rich_media/parser_test.exs
index 2f363b012..2fe7f1b0b 100644
--- a/test/pleroma/web/rich_media/parser_test.exs
+++ b/test/pleroma/web/rich_media/parser_test.exs
@@ -1,176 +1,176 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.ParserTest do
use ExUnit.Case, async: true
alias Pleroma.Web.RichMedia.Parser
setup do
Tesla.Mock.mock(fn
%{
method: :get,
url: "http://example.com/ogp"
} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}
%{
method: :get,
url: "http://example.com/non-ogp"
} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/non_ogp_embed.html")}
%{
method: :get,
url: "http://example.com/ogp-missing-title"
} ->
%Tesla.Env{
status: 200,
body: File.read!("test/fixtures/rich_media/ogp-missing-title.html")
}
%{
method: :get,
url: "http://example.com/twitter-card"
} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")}
%{
method: :get,
url: "http://example.com/oembed"
} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.html")}
%{
method: :get,
url: "http://example.com/oembed.json"
} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.json")}
%{method: :get, url: "http://example.com/empty"} ->
%Tesla.Env{status: 200, body: "hello"}
%{method: :get, url: "http://example.com/malformed"} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/malformed-data.html")}
%{method: :get, url: "http://example.com/error"} ->
{:error, :overload}
%{
method: :head,
url: "http://example.com/huge-page"
} ->
%Tesla.Env{
status: 200,
headers: [{"content-length", "2000001"}, {"content-type", "text/html"}]
}
%{
method: :head,
url: "http://example.com/pdf-file"
} ->
%Tesla.Env{
status: 200,
headers: [{"content-length", "1000000"}, {"content-type", "application/pdf"}]
}
%{method: :head} ->
%Tesla.Env{status: 404, body: "", headers: []}
end)
:ok
end
test "returns error when no metadata present" do
assert {:error, _} = Parser.parse("http://example.com/empty")
end
test "doesn't just add a title" do
assert {:error, {:invalid_metadata, _}} = Parser.parse("http://example.com/non-ogp")
end
test "parses ogp" do
assert Parser.parse("http://example.com/ogp") ==
{:ok,
%{
"image" => "http://ia.media-imdb.com/images/rock.jpg",
"title" => "The Rock",
"description" =>
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
"type" => "video.movie",
"url" => "http://example.com/ogp"
}}
end
test "falls back to <title> when ogp:title is missing" do
assert Parser.parse("http://example.com/ogp-missing-title") ==
{:ok,
%{
"image" => "http://ia.media-imdb.com/images/rock.jpg",
"title" => "The Rock (1996)",
"description" =>
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
"type" => "video.movie",
"url" => "http://example.com/ogp-missing-title"
}}
end
test "parses twitter card" do
assert Parser.parse("http://example.com/twitter-card") ==
{:ok,
%{
"card" => "summary",
"site" => "@flickr",
"image" => "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg",
"title" => "Small Island Developing States Photo Submission",
"description" => "View the album on Flickr.",
"url" => "http://example.com/twitter-card"
}}
end
test "parses OEmbed" do
assert Parser.parse("http://example.com/oembed") ==
{:ok,
%{
- "author_name" => "‮‭‬bees‬",
+ "author_name" => "\u202E\u202D\u202Cbees\u202C",
"author_url" => "https://www.flickr.com/photos/bees/",
"cache_age" => 3600,
"flickr_type" => "photo",
"height" => "768",
"html" =>
- "<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/bees/2362225867/\" title=\"Bacon Lollys by ‮‭‬bees‬, on Flickr\"><img src=\"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg\" width=\"1024\" height=\"768\" alt=\"Bacon Lollys\"></a><script async src=\"https://embedr.flickr.com/assets/client-code.js\" charset=\"utf-8\"></script>",
+ "<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/bees/2362225867/\" title=\"Bacon Lollys by \u202E\u202D\u202Cbees\u202C, on Flickr\"><img src=\"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg\" width=\"1024\" height=\"768\" alt=\"Bacon Lollys\"></a><script async src=\"https://embedr.flickr.com/assets/client-code.js\" charset=\"utf-8\"></script>",
"license" => "All Rights Reserved",
"license_id" => 0,
"provider_name" => "Flickr",
"provider_url" => "https://www.flickr.com/",
"thumbnail_height" => 150,
"thumbnail_url" =>
"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_q.jpg",
"thumbnail_width" => 150,
"title" => "Bacon Lollys",
"type" => "photo",
"url" => "http://example.com/oembed",
"version" => "1.0",
"web_page" => "https://www.flickr.com/photos/bees/2362225867/",
"web_page_short_url" => "https://flic.kr/p/4AK2sc",
"width" => "1024"
}}
end
test "rejects invalid OGP data" do
assert {:error, _} = Parser.parse("http://example.com/malformed")
end
test "returns error if getting page was not successful" do
assert {:error, :overload} = Parser.parse("http://example.com/error")
end
test "does a HEAD request to check if the body is too large" do
assert {:error, :body_too_large} = Parser.parse("http://example.com/huge-page")
end
test "does a HEAD request to check if the body is html" do
assert {:error, {:content_type, _}} = Parser.parse("http://example.com/pdf-file")
end
end

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 25, 5:06 AM (1 d, 9 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39596
Default Alt Text
(185 KB)

Event Timeline