From e9e17e5df34051bce60232890ea042582af31f8c Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 13 Oct 2020 00:27:51 -0500
Subject: [PATCH 001/339] Upgrade Earmark to v1.4.10

---
 lib/pleroma/earmark_renderer.ex               | 256 ------------------
 lib/pleroma/formatter.ex                      |   8 +
 .../audio_video_validator.ex                  |   3 +-
 lib/pleroma/web/common_api/utils.ex           |   3 +-
 mix.exs                                       |   2 +-
 mix.lock                                      |   2 +-
 test/pleroma/formatter_test.exs               |   7 +
 test/pleroma/web/common_api/utils_test.exs    |  75 +++++
 8 files changed, 95 insertions(+), 261 deletions(-)
 delete mode 100644 lib/pleroma/earmark_renderer.ex

diff --git a/lib/pleroma/earmark_renderer.ex b/lib/pleroma/earmark_renderer.ex
deleted file mode 100644
index 6211a3b4a..000000000
--- a/lib/pleroma/earmark_renderer.ex
+++ /dev/null
@@ -1,256 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-#
-# This file is derived from Earmark, under the following copyright:
-# Copyright © 2014 Dave Thomas, The Pragmatic Programmers
-# SPDX-License-Identifier: Apache-2.0
-# Upstream: https://github.com/pragdave/earmark/blob/master/lib/earmark/html_renderer.ex
-defmodule Pleroma.EarmarkRenderer do
-  @moduledoc false
-
-  alias Earmark.Block
-  alias Earmark.Context
-  alias Earmark.HtmlRenderer
-  alias Earmark.Options
-
-  import Earmark.Inline, only: [convert: 3]
-  import Earmark.Helpers.HtmlHelpers
-  import Earmark.Message, only: [add_messages_from: 2, get_messages: 1, set_messages: 2]
-  import Earmark.Context, only: [append: 2, set_value: 2]
-  import Earmark.Options, only: [get_mapper: 1]
-
-  @doc false
-  def render(blocks, %Context{options: %Options{}} = context) do
-    messages = get_messages(context)
-
-    {contexts, html} =
-      get_mapper(context.options).(
-        blocks,
-        &render_block(&1, put_in(context.options.messages, []))
-      )
-      |> Enum.unzip()
-
-    all_messages =
-      contexts
-      |> Enum.reduce(messages, fn ctx, messages1 -> messages1 ++ get_messages(ctx) end)
-
-    {put_in(context.options.messages, all_messages), html |> IO.iodata_to_binary()}
-  end
-
-  #############
-  # Paragraph #
-  #############
-  defp render_block(%Block.Para{lnb: lnb, lines: lines, attrs: attrs}, context) do
-    lines = convert(lines, lnb, context)
-    add_attrs(lines, "<p>#{lines.value}</p>", attrs, [], lnb)
-  end
-
-  ########
-  # Html #
-  ########
-  defp render_block(%Block.Html{html: html}, context) do
-    {context, html}
-  end
-
-  defp render_block(%Block.HtmlComment{lines: lines}, context) do
-    {context, lines}
-  end
-
-  defp render_block(%Block.HtmlOneline{html: html}, context) do
-    {context, html}
-  end
-
-  #########
-  # Ruler #
-  #########
-  defp render_block(%Block.Ruler{lnb: lnb, attrs: attrs}, context) do
-    add_attrs(context, "<hr />", attrs, [], lnb)
-  end
-
-  ###########
-  # Heading #
-  ###########
-  defp render_block(
-         %Block.Heading{lnb: lnb, level: level, content: content, attrs: attrs},
-         context
-       ) do
-    converted = convert(content, lnb, context)
-    html = "<h#{level}>#{converted.value}</h#{level}>"
-    add_attrs(converted, html, attrs, [], lnb)
-  end
-
-  ##############
-  # Blockquote #
-  ##############
-
-  defp render_block(%Block.BlockQuote{lnb: lnb, blocks: blocks, attrs: attrs}, context) do
-    {context1, body} = render(blocks, context)
-    html = "<blockquote>#{body}</blockquote>"
-    add_attrs(context1, html, attrs, [], lnb)
-  end
-
-  #########
-  # Table #
-  #########
-
-  defp render_block(
-         %Block.Table{lnb: lnb, header: header, rows: rows, alignments: aligns, attrs: attrs},
-         context
-       ) do
-    {context1, html} = add_attrs(context, "<table>", attrs, [], lnb)
-    context2 = set_value(context1, html)
-
-    context3 =
-      if header do
-        append(add_trs(append(context2, "<thead>"), [header], "th", aligns, lnb), "</thead>")
-      else
-        # Maybe an error, needed append(context, html)
-        context2
-      end
-
-    context4 = append(add_trs(append(context3, "<tbody>"), rows, "td", aligns, lnb), "</tbody>")
-
-    {context4, [context4.value, "</table>"]}
-  end
-
-  ########
-  # Code #
-  ########
-
-  defp render_block(
-         %Block.Code{lnb: lnb, language: language, attrs: attrs} = block,
-         %Context{options: options} = context
-       ) do
-    class =
-      if language, do: ~s{ class="#{code_classes(language, options.code_class_prefix)}"}, else: ""
-
-    tag = ~s[<pre><code#{class}>]
-    lines = options.render_code.(block)
-    html = ~s[#{tag}#{lines}</code></pre>]
-    add_attrs(context, html, attrs, [], lnb)
-  end
-
-  #########
-  # Lists #
-  #########
-
-  defp render_block(
-         %Block.List{lnb: lnb, type: type, blocks: items, attrs: attrs, start: start},
-         context
-       ) do
-    {context1, content} = render(items, context)
-    html = "<#{type}#{start}>#{content}</#{type}>"
-    add_attrs(context1, html, attrs, [], lnb)
-  end
-
-  # format a single paragraph list item, and remove the para tags
-  defp render_block(
-         %Block.ListItem{lnb: lnb, blocks: blocks, spaced: false, attrs: attrs},
-         context
-       )
-       when length(blocks) == 1 do
-    {context1, content} = render(blocks, context)
-    content = Regex.replace(~r{</?p>}, content, "")
-    html = "<li>#{content}</li>"
-    add_attrs(context1, html, attrs, [], lnb)
-  end
-
-  # format a spaced list item
-  defp render_block(%Block.ListItem{lnb: lnb, blocks: blocks, attrs: attrs}, context) do
-    {context1, content} = render(blocks, context)
-    html = "<li>#{content}</li>"
-    add_attrs(context1, html, attrs, [], lnb)
-  end
-
-  ##################
-  # Footnote Block #
-  ##################
-
-  defp render_block(%Block.FnList{blocks: footnotes}, context) do
-    items =
-      Enum.map(footnotes, fn note ->
-        blocks = append_footnote_link(note)
-        %Block.ListItem{attrs: "#fn:#{note.number}", type: :ol, blocks: blocks}
-      end)
-
-    {context1, html} = render_block(%Block.List{type: :ol, blocks: items}, context)
-    {context1, Enum.join([~s[<div class="footnotes">], "<hr />", html, "</div>"])}
-  end
-
-  #######################################
-  # Isolated IALs are rendered as paras #
-  #######################################
-
-  defp render_block(%Block.Ial{verbatim: verbatim}, context) do
-    {context, "<p>{:#{verbatim}}</p>"}
-  end
-
-  ####################
-  # IDDef is ignored #
-  ####################
-
-  defp render_block(%Block.IdDef{}, context), do: {context, ""}
-
-  #####################################
-  # And here are the inline renderers #
-  #####################################
-
-  defdelegate br, to: HtmlRenderer
-  defdelegate codespan(text), to: HtmlRenderer
-  defdelegate em(text), to: HtmlRenderer
-  defdelegate strong(text), to: HtmlRenderer
-  defdelegate strikethrough(text), to: HtmlRenderer
-
-  defdelegate link(url, text), to: HtmlRenderer
-  defdelegate link(url, text, title), to: HtmlRenderer
-
-  defdelegate image(path, alt, title), to: HtmlRenderer
-
-  defdelegate footnote_link(ref, backref, number), to: HtmlRenderer
-
-  # Table rows
-  defp add_trs(context, rows, tag, aligns, lnb) do
-    numbered_rows =
-      rows
-      |> Enum.zip(Stream.iterate(lnb, &(&1 + 1)))
-
-    numbered_rows
-    |> Enum.reduce(context, fn {row, lnb}, ctx ->
-      append(add_tds(append(ctx, "<tr>"), row, tag, aligns, lnb), "</tr>")
-    end)
-  end
-
-  defp add_tds(context, row, tag, aligns, lnb) do
-    Enum.reduce(1..length(row), context, add_td_fn(row, tag, aligns, lnb))
-  end
-
-  defp add_td_fn(row, tag, aligns, lnb) do
-    fn n, ctx ->
-      style =
-        case Enum.at(aligns, n - 1, :default) do
-          :default -> ""
-          align -> " style=\"text-align: #{align}\""
-        end
-
-      col = Enum.at(row, n - 1)
-      converted = convert(col, lnb, set_messages(ctx, []))
-      append(add_messages_from(ctx, converted), "<#{tag}#{style}>#{converted.value}</#{tag}>")
-    end
-  end
-
-  ###############################
-  # Append Footnote Return Link #
-  ###############################
-
-  defdelegate append_footnote_link(note), to: HtmlRenderer
-  defdelegate append_footnote_link(note, fnlink), to: HtmlRenderer
-
-  defdelegate render_code(lines), to: HtmlRenderer
-
-  defp code_classes(language, prefix) do
-    ["" | String.split(prefix || "")]
-    |> Enum.map(fn pfx -> "#{pfx}#{language}" end)
-    |> Enum.join(" ")
-  end
-end
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index 0c450eae4..b0e4a84ae 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -138,6 +138,14 @@ def html_escape(text, "text/plain") do
     |> Enum.join("")
   end
 
+  def minify({text, mentions, hashtags}, type) do
+    {minify(text, type), mentions, hashtags}
+  end
+
+  def minify(text, "text/html") do
+    String.replace(text, "\n", "")
+  end
+
   def truncate(text, max_length \\ 200, omission \\ "...") do
     # Remove trailing whitespace
     text = Regex.replace(~r/([^ \t\r\n])([ \t]+$)/u, text, "\\g{1}")
diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
index 16973e5db..eaf94797a 100644
--- a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
@@ -5,7 +5,6 @@
 defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
   use Ecto.Schema
 
-  alias Pleroma.EarmarkRenderer
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
   alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
@@ -96,7 +95,7 @@ defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = data)
        when is_binary(content) do
     content =
       content
-      |> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer})
+      |> Earmark.as_html!()
       |> Pleroma.HTML.filter_tags()
 
     Map.put(data, "content", content)
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 1c74ea787..b434a069e 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -294,8 +294,9 @@ def format_input(text, "text/html", options) do
   def format_input(text, "text/markdown", options) do
     text
     |> Formatter.mentions_escape(options)
-    |> Earmark.as_html!(%Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+    |> Earmark.as_html!()
     |> Formatter.linkify(options)
+    |> Formatter.minify("text/html")
     |> Formatter.html_escape("text/html")
   end
 
diff --git a/mix.exs b/mix.exs
index 72a6346b5..feb7eefa3 100644
--- a/mix.exs
+++ b/mix.exs
@@ -144,7 +144,7 @@ defp deps do
       {:ex_aws, "~> 2.1.6"},
       {:ex_aws_s3, "~> 2.0"},
       {:sweet_xml, "~> 0.6.6"},
-      {:earmark, "1.4.3"},
+      {:earmark, "1.4.10"},
       {:bbcode_pleroma, "~> 0.2.0"},
       {:crypt,
        git: "https://github.com/msantos/crypt.git",
diff --git a/mix.lock b/mix.lock
index 6b551a012..29439a438 100644
--- a/mix.lock
+++ b/mix.lock
@@ -27,7 +27,7 @@
   "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
   "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.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
+  "earmark": {:hex, :earmark, "1.4.10", "bddce5e8ea37712a5bfb01541be8ba57d3b171d3fa4f80a0be9bcf1db417bcaf", [:mix], [{:earmark_parser, ">= 1.4.10", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "12dbfa80810478e521d3ffb941ad9fbfcbbd7debe94e1341b4c4a1b2411c1c27"},
   "earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
   "ecto": {:hex, :ecto, "3.4.6", "08f7afad3257d6eb8613309af31037e16c36808dfda5a3cd0cb4e9738db030e4", [: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", "6f13a9e2a62e75c2dcfc7207bfc65645ab387af8360db4c89fee8b5a4bf3f70b"},
   "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"},
diff --git a/test/pleroma/formatter_test.exs b/test/pleroma/formatter_test.exs
index 5781a3f01..ceedd1b6d 100644
--- a/test/pleroma/formatter_test.exs
+++ b/test/pleroma/formatter_test.exs
@@ -307,4 +307,11 @@ test "it escapes HTML in plain text" do
 
     assert Formatter.html_escape(text, "text/plain") == expected
   end
+
+  test "it minifies html" do
+    text = "<p>\nhello</p>\n<p>\nworld</p>\n"
+    expected = "<p>hello</p><p>world</p>"
+
+    assert Formatter.minify(text, "text/html") == expected
+  end
 end
diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs
index 4d6c9ea26..39ea08ca8 100644
--- a/test/pleroma/web/common_api/utils_test.exs
+++ b/test/pleroma/web/common_api/utils_test.exs
@@ -168,6 +168,81 @@ test "works for text/markdown with mentions" do
     end
   end
 
+  describe "format_input/3 with markdown" do
+    test "Paragraph" do
+      code = ~s[Hello\n\nWorld!]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == "<p>Hello</p><p>World!</p>"
+    end
+
+    test "raw HTML" do
+      code = ~s[<a href="http://example.org/">OwO</a><!-- what's this?-->]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == "<p>#{code}</p>"
+    end
+
+    test "rulers" do
+      code = ~s[before\n\n-----\n\nafter]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == "<p>before</p><hr /><p>after</p>"
+    end
+
+    test "headings" do
+      code = ~s[# h1\n## h2\n### h3\n]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<h1>h1</h1><h2>h2</h2><h3>h3</h3>]
+    end
+
+    test "blockquote" do
+      code = ~s[> whoms't are you quoting?]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == "<blockquote><p>whoms’t are you quoting?</p></blockquote>"
+    end
+
+    test "code" do
+      code = ~s[`mix`]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<p><code class="inline">mix</code></p>]
+
+      code = ~s[``mix``]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<p><code class="inline">mix</code></p>]
+
+      code = ~s[```\nputs "Hello World"\n```]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<pre><code class="">puts &quot;Hello World&quot;</code></pre>]
+    end
+
+    test "lists" do
+      code = ~s[- one\n- two\n- three\n- four]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == "<ul><li>one</li><li>two</li><li>three</li><li>four</li></ul>"
+
+      code = ~s[1. one\n2. two\n3. three\n4. four\n]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == "<ol><li>one</li><li>two</li><li>three</li><li>four</li></ol>"
+    end
+
+    test "delegated renderers" do
+      code = ~s[a<br/>b]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == "<p>#{code}</p>"
+
+      code = ~s[*aaaa~*]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<p><em>aaaa~</em></p>]
+
+      code = ~s[**aaaa~**]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<p><strong>aaaa~</strong></p>]
+
+      # strikethrought
+      code = ~s[<del>aaaa~</del>]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<p><del>aaaa~</del></p>]
+    end
+  end
+
   describe "context_to_conversation_id" do
     test "creates a mapping object" do
       conversation_id = Utils.context_to_conversation_id("random context")

From ba71bbf6101847292346ba3b1fbe78ce4c385919 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 13 Oct 2020 01:53:25 -0500
Subject: [PATCH 002/339] Improve Formatter.minify/2

---
 lib/pleroma/formatter.ex | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index b0e4a84ae..61906dda6 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -143,7 +143,10 @@ def minify({text, mentions, hashtags}, type) do
   end
 
   def minify(text, "text/html") do
-    String.replace(text, "\n", "")
+    text
+    |> String.replace(">\n", ">")
+    |> String.replace(">  ", ">")
+    |> String.replace("  <", "<")
   end
 
   def truncate(text, max_length \\ 200, omission \\ "...") do

From c4f4e48e574362d1ec86eaf11a382e81ca97cb35 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 13 Oct 2020 02:08:41 -0500
Subject: [PATCH 003/339] Remove some N/A tests

---
 test/pleroma/web/common_api/utils_test.exs | 12 +-----------
 1 file changed, 1 insertion(+), 11 deletions(-)

diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs
index 39ea08ca8..c6abbbe84 100644
--- a/test/pleroma/web/common_api/utils_test.exs
+++ b/test/pleroma/web/common_api/utils_test.exs
@@ -187,12 +187,6 @@ test "rulers" do
       assert result == "<p>before</p><hr /><p>after</p>"
     end
 
-    test "headings" do
-      code = ~s[# h1\n## h2\n### h3\n]
-      {result, [], []} = Utils.format_input(code, "text/markdown")
-      assert result == ~s[<h1>h1</h1><h2>h2</h2><h3>h3</h3>]
-    end
-
     test "blockquote" do
       code = ~s[> whoms't are you quoting?]
       {result, [], []} = Utils.format_input(code, "text/markdown")
@@ -224,10 +218,6 @@ test "lists" do
     end
 
     test "delegated renderers" do
-      code = ~s[a<br/>b]
-      {result, [], []} = Utils.format_input(code, "text/markdown")
-      assert result == "<p>#{code}</p>"
-
       code = ~s[*aaaa~*]
       {result, [], []} = Utils.format_input(code, "text/markdown")
       assert result == ~s[<p><em>aaaa~</em></p>]
@@ -236,7 +226,7 @@ test "delegated renderers" do
       {result, [], []} = Utils.format_input(code, "text/markdown")
       assert result == ~s[<p><strong>aaaa~</strong></p>]
 
-      # strikethrought
+      # strikethrough
       code = ~s[<del>aaaa~</del>]
       {result, [], []} = Utils.format_input(code, "text/markdown")
       assert result == ~s[<p><del>aaaa~</del></p>]

From b2548cfcdabdcb90bfcc9f4022c0b1cff9157a4a Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 13 Oct 2020 13:54:53 -0500
Subject: [PATCH 004/339] Sanitizer: allow <hr> tags

---
 priv/scrubbers/default.ex | 1 +
 1 file changed, 1 insertion(+)

diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex
index 7b06994de..0893b17e5 100644
--- a/priv/scrubbers/default.ex
+++ b/priv/scrubbers/default.ex
@@ -39,6 +39,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
   Meta.allow_tag_with_these_attributes(:code, [])
   Meta.allow_tag_with_these_attributes(:del, [])
   Meta.allow_tag_with_these_attributes(:em, [])
+  Meta.allow_tag_with_these_attributes(:hr, [])
   Meta.allow_tag_with_these_attributes(:i, [])
   Meta.allow_tag_with_these_attributes(:li, [])
   Meta.allow_tag_with_these_attributes(:ol, [])

From f8c93246d69a193ead81248879ba260e98673b3d Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 13 Oct 2020 14:27:50 -0500
Subject: [PATCH 005/339] Refactor Earmark code, fix tests

---
 lib/pleroma/formatter.ex                               |  4 ++++
 .../object_validators/audio_video_validator.ex         |  2 +-
 lib/pleroma/web/common_api/utils.ex                    |  2 +-
 priv/scrubbers/default.ex                              |  2 ++
 test/pleroma/web/common_api/utils_test.exs             | 10 +++++-----
 test/pleroma/web/common_api_test.exs                   |  2 +-
 6 files changed, 14 insertions(+), 8 deletions(-)

diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index 61906dda6..1be12055f 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -121,6 +121,10 @@ def mentions_escape(text, options \\ []) do
     end
   end
 
+  def markdown_to_html(text) do
+    Earmark.as_html!(text)
+  end
+
   def html_escape({text, mentions, hashtags}, type) do
     {html_escape(text, type), mentions, hashtags}
   end
diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
index eaf94797a..9b38aa4c2 100644
--- a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
@@ -95,7 +95,7 @@ defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = data)
        when is_binary(content) do
     content =
       content
-      |> Earmark.as_html!()
+      |> Pleroma.Formatter.markdown_to_html()
       |> Pleroma.HTML.filter_tags()
 
     Map.put(data, "content", content)
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index b434a069e..be86009af 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -294,7 +294,7 @@ def format_input(text, "text/html", options) do
   def format_input(text, "text/markdown", options) do
     text
     |> Formatter.mentions_escape(options)
-    |> Earmark.as_html!()
+    |> Formatter.markdown_to_html()
     |> Formatter.linkify(options)
     |> Formatter.minify("text/html")
     |> Formatter.html_escape("text/html")
diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex
index 0893b17e5..4694a92a5 100644
--- a/priv/scrubbers/default.ex
+++ b/priv/scrubbers/default.ex
@@ -59,6 +59,8 @@ defmodule Pleroma.HTML.Scrubber.Default do
   Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
   Meta.allow_tag_with_these_attributes(:span, [])
 
+  Meta.allow_tag_with_this_attribute_values(:code, "class", ["inline"])
+
   @allow_inline_images Pleroma.Config.get([:markup, :allow_inline_images])
 
   if @allow_inline_images do
diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs
index c6abbbe84..ab6392b1f 100644
--- a/test/pleroma/web/common_api/utils_test.exs
+++ b/test/pleroma/web/common_api/utils_test.exs
@@ -178,13 +178,13 @@ test "Paragraph" do
     test "raw HTML" do
       code = ~s[<a href="http://example.org/">OwO</a><!-- what's this?-->]
       {result, [], []} = Utils.format_input(code, "text/markdown")
-      assert result == "<p>#{code}</p>"
+      assert result == ~s[<a href="http://example.org/">OwO</a>]
     end
 
     test "rulers" do
       code = ~s[before\n\n-----\n\nafter]
       {result, [], []} = Utils.format_input(code, "text/markdown")
-      assert result == "<p>before</p><hr /><p>after</p>"
+      assert result == "<p>before</p><hr/><p>after</p>"
     end
 
     test "blockquote" do
@@ -204,7 +204,7 @@ test "code" do
 
       code = ~s[```\nputs "Hello World"\n```]
       {result, [], []} = Utils.format_input(code, "text/markdown")
-      assert result == ~s[<pre><code class="">puts &quot;Hello World&quot;</code></pre>]
+      assert result == ~s[<pre><code>puts &quot;Hello World&quot;</code></pre>]
     end
 
     test "lists" do
@@ -227,9 +227,9 @@ test "delegated renderers" do
       assert result == ~s[<p><strong>aaaa~</strong></p>]
 
       # strikethrough
-      code = ~s[<del>aaaa~</del>]
+      code = ~s[~~aaaa~~~]
       {result, [], []} = Utils.format_input(code, "text/markdown")
-      assert result == ~s[<p><del>aaaa~</del></p>]
+      assert result == ~s[<p><del>aaaa</del>~</p>]
     end
   end
 
diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs
index 585b2c174..c1b1af073 100644
--- a/test/pleroma/web/common_api_test.exs
+++ b/test/pleroma/web/common_api_test.exs
@@ -558,7 +558,7 @@ test "it filters out obviously bad tags when accepting a post as Markdown" do
 
       object = Object.normalize(activity)
 
-      assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
+      assert object.data["content"] == "<p><b>2hu</b></p>"
       assert object.data["source"] == post
     end
 

From f1c67115d89ddcc7b10b963579dd621fca2094db Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 13 Oct 2020 18:09:49 -0500
Subject: [PATCH 006/339] Upgrade linkify, test URL issues, fixes #2026 #1942

---
 test/pleroma/web/common_api/utils_test.exs | 52 ++++++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs
index ab6392b1f..28b05ed91 100644
--- a/test/pleroma/web/common_api/utils_test.exs
+++ b/test/pleroma/web/common_api/utils_test.exs
@@ -175,6 +175,54 @@ test "Paragraph" do
       assert result == "<p>Hello</p><p>World!</p>"
     end
 
+    test "links" do
+      code = "https://en.wikipedia.org/wiki/Animal_Crossing_(video_game)"
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<p><a href="#{code}">#{code}</a></p>]
+
+      code = "https://github.com/pragdave/earmark/"
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<p><a href="#{code}">#{code}</a></p>]
+    end
+
+    test "link with local mention" do
+      insert(:user, %{nickname: "lain"})
+
+      code = "https://example.com/@lain"
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<p><a href="#{code}">#{code}</a></p>]
+    end
+
+    test "local mentions" do
+      mario = insert(:user, %{nickname: "mario"})
+      luigi = insert(:user, %{nickname: "luigi"})
+
+      code = "@mario @luigi yo what's up?"
+      {result, _, []} = Utils.format_input(code, "text/markdown")
+
+      assert result ==
+               ~s[<p><span class="h-card"><a class="u-url mention" data-user="#{mario.id}" href="#{
+                 mario.ap_id
+               }" rel="ugc">@<span>mario</span></a></span> <span class="h-card"><a class="u-url mention" data-user="#{
+                 luigi.id
+               }" href="#{luigi.ap_id}" rel="ugc">@<span>luigi</span></a></span> yo what’s up?</p>]
+    end
+
+    test "remote mentions" do
+      mario = insert(:user, %{nickname: "mario@mushroom.kingdom", local: false})
+      luigi = insert(:user, %{nickname: "luigi@mushroom.kingdom", local: false})
+
+      code = "@mario@mushroom.kingdom @luigi@mushroom.kingdom yo what's up?"
+      {result, _, []} = Utils.format_input(code, "text/markdown")
+
+      assert result ==
+               ~s[<p><span class="h-card"><a class="u-url mention" data-user="#{mario.id}" href="#{
+                 mario.ap_id
+               }" rel="ugc">@<span>mario</span></a></span> <span class="h-card"><a class="u-url mention" data-user="#{
+                 luigi.id
+               }" href="#{luigi.ap_id}" rel="ugc">@<span>luigi</span></a></span> yo what’s up?</p>]
+    end
+
     test "raw HTML" do
       code = ~s[<a href="http://example.org/">OwO</a><!-- what's this?-->]
       {result, [], []} = Utils.format_input(code, "text/markdown")
@@ -205,6 +253,10 @@ test "code" do
       code = ~s[```\nputs "Hello World"\n```]
       {result, [], []} = Utils.format_input(code, "text/markdown")
       assert result == ~s[<pre><code>puts &quot;Hello World&quot;</code></pre>]
+
+      code = ~s[    <div>\n    </div>]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<pre><code>&lt;div&gt;\n&lt;/div&gt;</code></pre>]
     end
 
     test "lists" do

From 642729b49fca41fb142c6121fedf35c96c03b018 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 13 Oct 2020 19:16:57 -0500
Subject: [PATCH 007/339] Fix AudioVideoValidator markdown

---
 .../web/activity_pub/object_validators/audio_video_validator.ex  | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
index 9b38aa4c2..fa3e2c026 100644
--- a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
@@ -96,6 +96,7 @@ defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = data)
     content =
       content
       |> Pleroma.Formatter.markdown_to_html()
+      |> Pleroma.Formatter.minify("text/html")
       |> Pleroma.HTML.filter_tags()
 
     Map.put(data, "content", content)

From 6520599b7deac56780e1496c969cc45ff2e9f5da Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Fri, 11 Dec 2020 13:43:40 -0600
Subject: [PATCH 008/339] Update Earmark to 1.4.13, use the new compact_output
 mode

---
 lib/pleroma/formatter.ex | 2 +-
 mix.exs                  | 2 +-
 mix.lock                 | 4 ++--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index 1be12055f..2aa236ca9 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -122,7 +122,7 @@ def mentions_escape(text, options \\ []) do
   end
 
   def markdown_to_html(text) do
-    Earmark.as_html!(text)
+    Earmark.as_html!(text, %Earmark.Options{compact_output: true})
   end
 
   def html_escape({text, mentions, hashtags}, type) do
diff --git a/mix.exs b/mix.exs
index feb7eefa3..06d77edb7 100644
--- a/mix.exs
+++ b/mix.exs
@@ -144,7 +144,7 @@ defp deps do
       {:ex_aws, "~> 2.1.6"},
       {:ex_aws_s3, "~> 2.0"},
       {:sweet_xml, "~> 0.6.6"},
-      {:earmark, "1.4.10"},
+      {:earmark, "1.4.13"},
       {:bbcode_pleroma, "~> 0.2.0"},
       {:crypt,
        git: "https://github.com/msantos/crypt.git",
diff --git a/mix.lock b/mix.lock
index 29439a438..e4dd32c83 100644
--- a/mix.lock
+++ b/mix.lock
@@ -27,8 +27,8 @@
   "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
   "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.10", "bddce5e8ea37712a5bfb01541be8ba57d3b171d3fa4f80a0be9bcf1db417bcaf", [:mix], [{:earmark_parser, ">= 1.4.10", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "12dbfa80810478e521d3ffb941ad9fbfcbbd7debe94e1341b4c4a1b2411c1c27"},
-  "earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
+  "earmark": {:hex, :earmark, "1.4.13", "2c6ce9768fc9fdbf4046f457e207df6360ee6c91ee1ecb8e9a139f96a4289d91", [:mix], [{:earmark_parser, ">= 1.4.12", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "a0cf3ed88ef2b1964df408889b5ecb886d1a048edde53497fc935ccd15af3403"},
+  "earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"},
   "ecto": {:hex, :ecto, "3.4.6", "08f7afad3257d6eb8613309af31037e16c36808dfda5a3cd0cb4e9738db030e4", [: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", "6f13a9e2a62e75c2dcfc7207bfc65645ab387af8360db4c89fee8b5a4bf3f70b"},
   "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.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"},

From f318d8e56df1e30f41c7ddf2e306b3552034921f Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Fri, 11 Dec 2020 17:28:00 -0600
Subject: [PATCH 009/339] Use Pleroma.Formatter.markdown_to_html/1 in the tests

---
 test/pleroma/earmark_renderer_test.exs | 28 +++++++++++++-------------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/test/pleroma/earmark_renderer_test.exs b/test/pleroma/earmark_renderer_test.exs
index 220d97d16..3adbefc1e 100644
--- a/test/pleroma/earmark_renderer_test.exs
+++ b/test/pleroma/earmark_renderer_test.exs
@@ -6,74 +6,74 @@ defmodule Pleroma.EarmarkRendererTest do
 
   test "Paragraph" do
     code = ~s[Hello\n\nWorld!]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+    result = Pleroma.Formatter.markdown_to_html(code)
     assert result == "<p>Hello</p><p>World!</p>"
   end
 
   test "raw HTML" do
     code = ~s[<a href="http://example.org/">OwO</a><!-- what's this?-->]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+    result = Pleroma.Formatter.markdown_to_html(code)
     assert result == "<p>#{code}</p>"
   end
 
   test "rulers" do
     code = ~s[before\n\n-----\n\nafter]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+    result = Pleroma.Formatter.markdown_to_html(code)
     assert result == "<p>before</p><hr /><p>after</p>"
   end
 
   test "headings" do
     code = ~s[# h1\n## h2\n### h3\n]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+    result = Pleroma.Formatter.markdown_to_html(code)
     assert result == ~s[<h1>h1</h1><h2>h2</h2><h3>h3</h3>]
   end
 
   test "blockquote" do
     code = ~s[> whoms't are you quoting?]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+    result = Pleroma.Formatter.markdown_to_html(code)
     assert result == "<blockquote><p>whoms’t are you quoting?</p></blockquote>"
   end
 
   test "code" do
     code = ~s[`mix`]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+    result = Pleroma.Formatter.markdown_to_html(code)
     assert result == ~s[<p><code class="inline">mix</code></p>]
 
     code = ~s[``mix``]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+    result = Pleroma.Formatter.markdown_to_html(code)
     assert result == ~s[<p><code class="inline">mix</code></p>]
 
     code = ~s[```\nputs "Hello World"\n```]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+    result = Pleroma.Formatter.markdown_to_html(code)
     assert result == ~s[<pre><code class="">puts &quot;Hello World&quot;</code></pre>]
   end
 
   test "lists" do
     code = ~s[- one\n- two\n- three\n- four]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+    result = Pleroma.Formatter.markdown_to_html(code)
     assert result == "<ul><li>one</li><li>two</li><li>three</li><li>four</li></ul>"
 
     code = ~s[1. one\n2. two\n3. three\n4. four\n]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+    result = Pleroma.Formatter.markdown_to_html(code)
     assert result == "<ol><li>one</li><li>two</li><li>three</li><li>four</li></ol>"
   end
 
   test "delegated renderers" do
     code = ~s[a<br/>b]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+    result = Pleroma.Formatter.markdown_to_html(code)
     assert result == "<p>#{code}</p>"
 
     code = ~s[*aaaa~*]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+    result = Pleroma.Formatter.markdown_to_html(code)
     assert result == ~s[<p><em>aaaa~</em></p>]
 
     code = ~s[**aaaa~**]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+    result = Pleroma.Formatter.markdown_to_html(code)
     assert result == ~s[<p><strong>aaaa~</strong></p>]
 
     # strikethrought
     code = ~s[<del>aaaa~</del>]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+    result = Pleroma.Formatter.markdown_to_html(code)
     assert result == ~s[<p><del>aaaa~</del></p>]
   end
 end

From ee221277b05d2f682c340c1e1b81fbce4931735a Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Mon, 21 Dec 2020 22:54:26 +0300
Subject: [PATCH 010/339] Encapsulation of tags / hashtags fetching from
 objects.

---
 lib/pleroma/activity/ir/topics.ex             | 10 ++---
 lib/pleroma/object.ex                         | 45 ++++++++++++++++---
 .../web/activity_pub/mrf/simple_policy.ex     |  8 ++--
 .../web/activity_pub/transmogrifier.ex        | 29 ++++++------
 lib/pleroma/web/feed/feed_view.ex             |  1 +
 .../web/mastodon_api/views/status_view.ex     |  6 ++-
 .../templates/feed/feed/_activity.atom.eex    |  2 +-
 .../web/templates/feed/feed/_activity.rss.eex |  2 +-
 .../feed/feed/_tag_activity.atom.eex          |  2 +-
 .../transmogrifier/note_handling_test.exs     |  6 ++-
 test/pleroma/web/common_api_test.exs          |  2 +-
 .../mastodon_api/views/status_view_test.exs   |  4 +-
 12 files changed, 78 insertions(+), 39 deletions(-)

diff --git a/lib/pleroma/activity/ir/topics.ex b/lib/pleroma/activity/ir/topics.ex
index fe2e8cb5c..2c74ac2bf 100644
--- a/lib/pleroma/activity/ir/topics.ex
+++ b/lib/pleroma/activity/ir/topics.ex
@@ -48,14 +48,12 @@ defp item_creation_tags(tags, _, _) do
     tags
   end
 
-  defp hashtags_to_topics(%{data: %{"tag" => tags}}) do
-    tags
-    |> Enum.filter(&is_bitstring(&1))
-    |> Enum.map(fn tag -> "hashtag:" <> tag end)
+  defp hashtags_to_topics(object) do
+    object
+    |> Object.hashtags()
+    |> Enum.map(fn hashtag -> "hashtag:" <> hashtag end)
   end
 
-  defp hashtags_to_topics(_), do: []
-
   defp remote_topics(%{local: true}), do: []
 
   defp remote_topics(%{actor: actor}) when is_binary(actor),
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 052ad413b..2088c7656 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -47,17 +47,33 @@ def with_joined_activity(query, activity_type \\ "Create", join_type \\ :inner)
   end
 
   def create(data) do
-    Object.change(%Object{}, %{data: data})
+    %Object{}
+    |> Object.change(%{data: data})
     |> Repo.insert()
   end
 
   def change(struct, params \\ %{}) do
-    struct
-    |> cast(params, [:data])
-    |> validate_required([:data])
-    |> unique_constraint(:ap_id, name: :objects_unique_apid_index)
+    changeset =
+      struct
+      |> cast(params, [:data])
+      |> validate_required([:data])
+      |> unique_constraint(:ap_id, name: :objects_unique_apid_index)
+
+    if hashtags_changed?(struct, get_change(changeset, :data)) do
+      # TODO: modify assoc once it's introduced
+      changeset
+    else
+      changeset
+    end
   end
 
+  defp hashtags_changed?(%Object{} = struct, %{"tag" => _} = data) do
+    Enum.sort(embedded_hashtags(struct)) !=
+      Enum.sort(object_data_hashtags(data))
+  end
+
+  defp hashtags_changed?(_, _), do: false
+
   def get_by_id(nil), do: nil
   def get_by_id(id), do: Repo.get(Object, id)
 
@@ -344,4 +360,23 @@ def replies(object, opts \\ []) do
 
   def self_replies(object, opts \\ []),
     do: replies(object, Keyword.put(opts, :self_only, true))
+
+  def tags(%Object{data: %{"tag" => tags}}) when is_list(tags), do: tags
+
+  def tags(_), do: []
+
+  def hashtags(object), do: embedded_hashtags(object)
+
+  defp embedded_hashtags(%Object{data: data}) do
+    object_data_hashtags(data)
+  end
+
+  defp embedded_hashtags(_), do: []
+
+  defp object_data_hashtags(%{"tag" => tags}) when is_list(tags) do
+    # Note: AS2 map-type elements are ignored
+    Enum.filter(tags, &is_bitstring(&1))
+  end
+
+  defp object_data_hashtags(_), do: []
 end
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index 6cd91826d..e92091d66 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -74,9 +74,11 @@ defp check_media_nsfw(
 
     object =
       if MRF.subdomain_match?(media_nsfw, actor_host) do
-        tags = (child_object["tag"] || []) ++ ["nsfw"]
-        child_object = Map.put(child_object, "tag", tags)
-        child_object = Map.put(child_object, "sensitive", true)
+        child_object =
+          child_object
+          |> Map.put("tag", (child_object["tag"] || []) ++ ["nsfw"])
+          |> Map.put("sensitive", true)
+
         Map.put(object, "object", child_object)
       else
         object
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 565d32433..fd17793d0 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -32,18 +32,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   """
   def fix_object(object, options \\ []) do
     object
-    |> strip_internal_fields
-    |> fix_actor
-    |> fix_url
-    |> fix_attachments
-    |> fix_context
+    |> strip_internal_fields()
+    |> fix_actor()
+    |> fix_url()
+    |> fix_attachments()
+    |> fix_context()
     |> fix_in_reply_to(options)
-    |> fix_emoji
-    |> fix_tag
-    |> set_sensitive
-    |> fix_content_map
-    |> fix_addressing
-    |> fix_summary
+    |> fix_emoji()
+    |> fix_tag()
+    |> set_sensitive()
+    |> fix_content_map()
+    |> fix_addressing()
+    |> fix_summary()
     |> fix_type(options)
   end
 
@@ -315,10 +315,9 @@ def fix_tag(%{"tag" => tag} = object) when is_list(tag) do
     tags =
       tag
       |> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end)
-      |> Enum.map(fn %{"name" => name} ->
-        name
-        |> String.slice(1..-1)
-        |> String.downcase()
+      |> Enum.map(fn
+        %{"name" => "#" <> hashtag} -> String.downcase(hashtag)
+        %{"name" => hashtag} -> String.downcase(hashtag)
       end)
 
     Map.put(object, "tag", tag ++ tags)
diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex
index 30e0a2a55..1155c6a39 100644
--- a/lib/pleroma/web/feed/feed_view.ex
+++ b/lib/pleroma/web/feed/feed_view.ex
@@ -32,6 +32,7 @@ def prepare_activity(activity, opts \\ []) do
 
     %{
       activity: activity,
+      object: object,
       data: Map.get(object, :data),
       actor: actor
     }
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 2301e21cf..bd08aa203 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -201,8 +201,10 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
     like_count = object.data["like_count"] || 0
     announcement_count = object.data["announcement_count"] || 0
 
-    tags = object.data["tag"] || []
-    sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw")
+    hashtags = Object.hashtags(object)
+    sensitive = object.data["sensitive"] || Enum.member?(hashtags, "nsfw")
+
+    tags = Object.tags(object)
 
     tag_mentions =
       tags
diff --git a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
index 3fd150c4e..6688830ba 100644
--- a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
@@ -22,7 +22,7 @@
     <link type="text/html" href='<%= @data["external_url"] %>' rel="alternate"/>
   <% end %>
 
-  <%= for tag <- @data["tag"] || [] do %>
+  <%= for tag <- Pleroma.Object.hashtags(@object) do %>
     <category term="<%= tag %>"></category>
   <% end %>
 
diff --git a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
index 42960de7d..fc6d74b42 100644
--- a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
+++ b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
@@ -21,7 +21,7 @@
     <link><%= @data["external_url"] %></link>
   <% end %>
 
-  <%= for tag <- @data["tag"] || [] do %>
+  <%= for tag <- Pleroma.Object.hashtags(@object) do %>
     <category term="<%= tag %>"></category>
   <% end %>
 
diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex
index cf5874a91..c2de28fe4 100644
--- a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex
@@ -41,7 +41,7 @@
       <% end %>
     <% end %>
 
-    <%= for tag <- @data["tag"] || [] do %>
+    <%= for tag <- Pleroma.Object.hashtags(@object) do %>
       <category term="<%= tag %>"></category>
     <% end %>
 
diff --git a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
index b4a006aec..a33959d9f 100644
--- a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
@@ -39,7 +39,8 @@ test "it works for incoming notices with tag not being an array (kroeg)" do
       {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
       object = Object.normalize(data["object"])
 
-      assert "test" in object.data["tag"]
+      assert "test" in Object.tags(object)
+      assert Object.hashtags(object) == ["test"]
     end
 
     test "it cleans up incoming notices which are not really DMs" do
@@ -220,7 +221,8 @@ test "it works for incoming notices with hashtags" do
       {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
       object = Object.normalize(data["object"])
 
-      assert Enum.at(object.data["tag"], 2) == "moo"
+      assert Enum.at(Object.tags(object), 2) == "moo"
+      assert Object.hashtags(object) == ["moo"]
     end
 
     test "it works for incoming notices with contentMap" do
diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs
index 585b2c174..1e98208fb 100644
--- a/test/pleroma/web/common_api_test.exs
+++ b/test/pleroma/web/common_api_test.exs
@@ -493,7 +493,7 @@ test "it de-duplicates tags" do
 
     object = Object.normalize(activity)
 
-    assert object.data["tag"] == ["2hu"]
+    assert Object.tags(object) == ["2hu"]
   end
 
   test "it adds emoji in the object" do
diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs
index f2a7469ed..6b8afc960 100644
--- a/test/pleroma/web/mastodon_api/views/status_view_test.exs
+++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs
@@ -262,8 +262,8 @@ test "a note activity" do
       mentions: [],
       tags: [
         %{
-          name: "#{object_data["tag"]}",
-          url: "/tag/#{object_data["tag"]}"
+          name: "#{hd(object_data["tag"])}",
+          url: "/tag/#{hd(object_data["tag"])}"
         }
       ],
       application: %{

From e369b1306b2f8b9732c21333b9957f7e4e408f90 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Tue, 22 Dec 2020 22:04:33 +0300
Subject: [PATCH 011/339] Added Hashtag entity and objects-hashtags association
 with auto-sync with `data.tag` on Object update.

---
 lib/pleroma/hashtag.ex                        | 58 +++++++++++++++++++
 lib/pleroma/object.ex                         | 35 ++++++++---
 .../20201221202251_create_hashtags.exs        | 14 +++++
 ...20201221203824_create_hashtags_objects.exs | 13 +++++
 test/pleroma/object_test.exs                  | 27 +++++++++
 .../web/activity_pub/activity_pub_test.exs    |  5 ++
 6 files changed, 143 insertions(+), 9 deletions(-)
 create mode 100644 lib/pleroma/hashtag.ex
 create mode 100644 priv/repo/migrations/20201221202251_create_hashtags.exs
 create mode 100644 priv/repo/migrations/20201221203824_create_hashtags_objects.exs

diff --git a/lib/pleroma/hashtag.ex b/lib/pleroma/hashtag.ex
new file mode 100644
index 000000000..b05927563
--- /dev/null
+++ b/lib/pleroma/hashtag.ex
@@ -0,0 +1,58 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Hashtag do
+  use Ecto.Schema
+
+  import Ecto.Changeset
+
+  alias Pleroma.Hashtag
+  alias Pleroma.Repo
+
+  @derive {Jason.Encoder, only: [:data]}
+
+  schema "hashtags" do
+    field(:name, :string)
+    field(:data, :map, default: %{})
+
+    many_to_many(:objects, Pleroma.Object, join_through: "hashtags_objects", on_replace: :delete)
+
+    timestamps()
+  end
+
+  def get_by_name(name) do
+    Repo.get_by(Hashtag, name: name)
+  end
+
+  def get_or_create_by_name(name) when is_bitstring(name) do
+    with %Hashtag{} = hashtag <- get_by_name(name) do
+      {:ok, hashtag}
+    else
+      _ ->
+        %Hashtag{}
+        |> changeset(%{name: name})
+        |> Repo.insert()
+    end
+  end
+
+  def get_or_create_by_names(names) when is_list(names) do
+    Enum.reduce_while(names, {:ok, []}, fn name, {:ok, list} ->
+      case get_or_create_by_name(name) do
+        {:ok, %Hashtag{} = hashtag} ->
+          {:cont, {:ok, list ++ [hashtag]}}
+
+        error ->
+          {:halt, error}
+      end
+    end)
+  end
+
+  def changeset(%Hashtag{} = struct, params) do
+    struct
+    |> cast(params, [:name, :data])
+    |> update_change(:name, &String.downcase/1)
+    |> validate_required([:name])
+    |> unique_constraint(:name)
+  end
+end
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 2088c7656..357a3b504 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -10,6 +10,7 @@ defmodule Pleroma.Object do
 
   alias Pleroma.Activity
   alias Pleroma.Config
+  alias Pleroma.Hashtag
   alias Pleroma.Object
   alias Pleroma.Object.Fetcher
   alias Pleroma.ObjectTombstone
@@ -26,6 +27,8 @@ defmodule Pleroma.Object do
   schema "objects" do
     field(:data, :map)
 
+    many_to_many(:hashtags, Hashtag, join_through: "hashtags_objects", on_replace: :delete)
+
     timestamps()
   end
 
@@ -53,17 +56,31 @@ def create(data) do
   end
 
   def change(struct, params \\ %{}) do
-    changeset =
-      struct
-      |> cast(params, [:data])
-      |> validate_required([:data])
-      |> unique_constraint(:ap_id, name: :objects_unique_apid_index)
+    struct
+    |> cast(params, [:data])
+    |> validate_required([:data])
+    |> unique_constraint(:ap_id, name: :objects_unique_apid_index)
+    |> maybe_handle_hashtags_change(struct)
+  end
 
-    if hashtags_changed?(struct, get_change(changeset, :data)) do
-      # TODO: modify assoc once it's introduced
-      changeset
+  defp maybe_handle_hashtags_change(changeset, struct) do
+    with data_hashtags_change = get_change(changeset, :data),
+         true <- hashtags_changed?(struct, data_hashtags_change),
+         {:ok, hashtag_records} <-
+           data_hashtags_change
+           |> object_data_hashtags()
+           |> Hashtag.get_or_create_by_names() do
+      put_assoc(changeset, :hashtags, hashtag_records)
     else
-      changeset
+      false ->
+        changeset
+
+      {:error, hashtag_changeset} ->
+        failed_hashtag = get_field(hashtag_changeset, :name)
+
+        validate_change(changeset, :data, fn _, _ ->
+          [data: "error referencing hashtag: #{failed_hashtag}"]
+        end)
     end
   end
 
diff --git a/priv/repo/migrations/20201221202251_create_hashtags.exs b/priv/repo/migrations/20201221202251_create_hashtags.exs
new file mode 100644
index 000000000..afc522002
--- /dev/null
+++ b/priv/repo/migrations/20201221202251_create_hashtags.exs
@@ -0,0 +1,14 @@
+defmodule Pleroma.Repo.Migrations.CreateHashtags do
+  use Ecto.Migration
+
+  def change do
+    create_if_not_exists table(:hashtags) do
+      add(:name, :citext, null: false)
+      add(:data, :map, default: %{})
+
+      timestamps()
+    end
+
+    create_if_not_exists(unique_index(:hashtags, [:name]))
+  end
+end
diff --git a/priv/repo/migrations/20201221203824_create_hashtags_objects.exs b/priv/repo/migrations/20201221203824_create_hashtags_objects.exs
new file mode 100644
index 000000000..b2649b4fb
--- /dev/null
+++ b/priv/repo/migrations/20201221203824_create_hashtags_objects.exs
@@ -0,0 +1,13 @@
+defmodule Pleroma.Repo.Migrations.CreateHashtagsObjects do
+  use Ecto.Migration
+
+  def change do
+    create_if_not_exists table(:hashtags_objects) do
+      add(:hashtag_id, references(:hashtags), null: false)
+      add(:object_id, references(:objects), null: false)
+    end
+
+    create_if_not_exists(unique_index(:hashtags_objects, [:hashtag_id, :object_id]))
+    create_if_not_exists(index(:hashtags_objects, [:object_id]))
+  end
+end
diff --git a/test/pleroma/object_test.exs b/test/pleroma/object_test.exs
index 5d4e6fb84..819ecd210 100644
--- a/test/pleroma/object_test.exs
+++ b/test/pleroma/object_test.exs
@@ -5,10 +5,13 @@
 defmodule Pleroma.ObjectTest do
   use Pleroma.DataCase
   use Oban.Testing, repo: Pleroma.Repo
+
   import ExUnit.CaptureLog
   import Pleroma.Factory
   import Tesla.Mock
+
   alias Pleroma.Activity
+  alias Pleroma.Hashtag
   alias Pleroma.Object
   alias Pleroma.Repo
   alias Pleroma.Tests.ObanHelpers
@@ -406,4 +409,28 @@ test "preserves internal fields on refetch", %{mock_modified: mock_modified} do
       assert updated_object.data["like_count"] == 1
     end
   end
+
+  describe ":hashtags association" do
+    test "Hashtag records are created with Object record and updated on its change" do
+      user = insert(:user)
+
+      {:ok, %{object: object}} =
+        CommonAPI.post(user, %{status: "some text #hashtag1 #hashtag2 ..."})
+
+      assert [%Hashtag{name: "hashtag1"}, %Hashtag{name: "hashtag2"}] =
+               Enum.sort_by(object.hashtags, & &1.name)
+
+      {:ok, object} = Object.update_data(object, %{"tag" => []})
+
+      assert [] = object.hashtags
+
+      object = Object.get_by_id(object.id) |> Repo.preload(:hashtags)
+      assert [] = object.hashtags
+
+      {:ok, object} = Object.update_data(object, %{"tag" => ["abc", "def"]})
+
+      assert [%Hashtag{name: "abc"}, %Hashtag{name: "def"}] =
+               Enum.sort_by(object.hashtags, & &1.name)
+    end
+  end
 end
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index 9eb7ae86b..bfec32042 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -217,6 +217,11 @@ test "it fetches the appropriate tag-restricted posts" do
         tag_all: ["test", "reject"]
       })
 
+    [fetch_one, fetch_two, fetch_three, fetch_four] =
+      Enum.map([fetch_one, fetch_two, fetch_three, fetch_four], fn statuses ->
+        Enum.map(statuses, fn s -> Repo.preload(s, object: :hashtags) end)
+      end)
+
     assert fetch_one == [status_one, status_three]
     assert fetch_two == [status_one, status_two, status_three]
     assert fetch_three == [status_one, status_two]

From cbb19d0e1882f5ce641f30b51d7156336f81aba9 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Sat, 26 Dec 2020 22:20:55 +0300
Subject: [PATCH 012/339] [#3213] Hashtag-filtering functions in ActivityPub.
 Mix task for migrating hashtags to `hashtags` table.

---
 lib/mix/tasks/pleroma/database.ex             |  64 +++++++
 lib/pleroma/web/activity_pub/activity_pub.ex  | 171 +++++++++++++-----
 .../web/activity_pub/activity_pub_test.exs    |  48 ++---
 3 files changed, 218 insertions(+), 65 deletions(-)

diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index 22151ce08..093c7dd30 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -4,14 +4,18 @@
 
 defmodule Mix.Tasks.Pleroma.Database do
   alias Pleroma.Conversation
+  alias Pleroma.Hashtag
   alias Pleroma.Maintenance
   alias Pleroma.Object
   alias Pleroma.Repo
   alias Pleroma.User
+
   require Logger
   require Pleroma.Constants
+
   import Ecto.Query
   import Mix.Pleroma
+
   use Mix.Task
 
   @shortdoc "A collection of database related tasks"
@@ -128,6 +132,66 @@ def run(["fix_likes_collections"]) do
     |> Stream.run()
   end
 
+  def run(["transfer_hashtags"]) do
+    import Ecto.Query
+
+    start_pleroma()
+
+    from(
+      object in Object,
+      left_join: hashtag in assoc(object, :hashtags),
+      where: is_nil(hashtag.id),
+      where: fragment("(?)->>'tag' != '[]'", object.data),
+      select: %{
+        id: object.id,
+        inserted_at: object.inserted_at,
+        tag: fragment("(?)->>'tag'", object.data)
+      },
+      order_by: [desc: object.id]
+    )
+    |> Pleroma.Repo.chunk_stream(100, :batches)
+    |> Stream.each(fn objects ->
+      chunk_start = List.first(objects)
+      chunk_end = List.last(objects)
+
+      Logger.info(
+        "transfer_hashtags: " <>
+          "#{chunk_start.id} (#{chunk_start.inserted_at}) -- " <>
+          "#{chunk_end.id} (#{chunk_end.inserted_at})"
+      )
+
+      Enum.map(
+        objects,
+        fn object ->
+          hashtags =
+            object.tag
+            |> Jason.decode!()
+            |> Enum.filter(&is_bitstring(&1))
+
+          with {:ok, hashtag_records} <- Hashtag.get_or_create_by_names(hashtags) do
+            Repo.transaction(fn ->
+              for hashtag_record <- hashtag_records do
+                with {:error, _} <-
+                       Ecto.Adapters.SQL.query(
+                         Repo,
+                         "insert into hashtags_objects(hashtag_id, object_id) values " <>
+                           "(#{hashtag_record.id}, #{object.id});"
+                       ) do
+                  Logger.warn(
+                    "ERROR: could not link object #{object.id} and hashtag #{hashtag_record.id}"
+                  )
+                end
+              end
+            end)
+          else
+            e -> Logger.warn("ERROR: could not process object #{object.id}: #{inspect(e)}")
+          end
+        end
+      )
+    end)
+    |> Stream.run()
+  end
+
   def run(["vacuum", args]) do
     start_pleroma()
 
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 1c91bc074..2e25412c6 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -660,33 +660,41 @@ defp restrict_since(query, %{since_id: since_id}) do
   defp restrict_since(query, _), do: query
 
   defp restrict_tag_reject(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
-    raise "Can't use the child object without preloading!"
+    raise_on_missing_preload()
   end
 
-  defp restrict_tag_reject(query, %{tag_reject: [_ | _] = tag_reject}) do
+  defp restrict_tag_reject(query, %{tag_reject: tag_reject}) when is_list(tag_reject) do
     from(
       [_activity, object] in query,
       where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
     )
   end
 
+  defp restrict_tag_reject(query, %{tag_reject: tag_reject}) when is_binary(tag_reject) do
+    restrict_tag_reject(query, %{tag_reject: [tag_reject]})
+  end
+
   defp restrict_tag_reject(query, _), do: query
 
   defp restrict_tag_all(_query, %{tag_all: _tag_all, skip_preload: true}) do
-    raise "Can't use the child object without preloading!"
+    raise_on_missing_preload()
   end
 
-  defp restrict_tag_all(query, %{tag_all: [_ | _] = tag_all}) do
+  defp restrict_tag_all(query, %{tag_all: tag_all}) when is_list(tag_all) do
     from(
       [_activity, object] in query,
       where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)
     )
   end
 
+  defp restrict_tag_all(query, %{tag_all: tag}) when is_binary(tag) do
+    restrict_tag(query, %{tag: tag})
+  end
+
   defp restrict_tag_all(query, _), do: query
 
   defp restrict_tag(_query, %{tag: _tag, skip_preload: true}) do
-    raise "Can't use the child object without preloading!"
+    raise_on_missing_preload()
   end
 
   defp restrict_tag(query, %{tag: tag}) when is_list(tag) do
@@ -697,14 +705,80 @@ defp restrict_tag(query, %{tag: tag}) when is_list(tag) do
   end
 
   defp restrict_tag(query, %{tag: tag}) when is_binary(tag) do
-    from(
-      [_activity, object] in query,
-      where: fragment("(?)->'tag' \\? (?)", object.data, ^tag)
-    )
+    restrict_tag(query, %{tag: [tag]})
   end
 
   defp restrict_tag(query, _), do: query
 
+  defp restrict_hashtag_reject_any(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
+    raise_on_missing_preload()
+  end
+
+  defp restrict_hashtag_reject_any(query, %{tag_reject: tags_reject}) when is_list(tags_reject) do
+    if has_named_binding?(query, :thread_mute) do
+      from(
+        [activity, object, thread_mute] in query,
+        group_by: [activity.id, object.id, thread_mute.id]
+      )
+    else
+      from(
+        [activity, object] in query,
+        group_by: [activity.id, object.id]
+      )
+    end
+    |> join(:left, [_activity, object], hashtag in assoc(object, :hashtags), as: :hashtag)
+    |> having(
+      [hashtag: hashtag],
+      fragment("not(array_agg(?) && (?))", hashtag.name, ^tags_reject)
+    )
+  end
+
+  defp restrict_hashtag_reject_any(query, %{tag_reject: tag_reject}) when is_binary(tag_reject) do
+    restrict_hashtag_reject_any(query, %{tag_reject: [tag_reject]})
+  end
+
+  defp restrict_hashtag_reject_any(query, _), do: query
+
+  defp restrict_hashtag_all(_query, %{tag_all: _tag, skip_preload: true}) do
+    raise_on_missing_preload()
+  end
+
+  defp restrict_hashtag_all(query, %{tag_all: tags}) when is_list(tags) do
+    Enum.reduce(
+      tags,
+      query,
+      fn tag, acc -> restrict_hashtag_any(acc, %{tag: tag}) end
+    )
+  end
+
+  defp restrict_hashtag_all(query, %{tag_all: tag}) when is_binary(tag) do
+    restrict_hashtag_any(query, %{tag: tag})
+  end
+
+  defp restrict_hashtag_all(query, _), do: query
+
+  defp restrict_hashtag_any(_query, %{tag: _tag, skip_preload: true}) do
+    raise_on_missing_preload()
+  end
+
+  defp restrict_hashtag_any(query, %{tag: tags}) when is_list(tags) do
+    from(
+      [_activity, object] in query,
+      join: hashtag in assoc(object, :hashtags),
+      where: hashtag.name in ^tags
+    )
+  end
+
+  defp restrict_hashtag_any(query, %{tag: tag}) when is_binary(tag) do
+    restrict_hashtag_any(query, %{tag: [tag]})
+  end
+
+  defp restrict_hashtag_any(query, _), do: query
+
+  defp raise_on_missing_preload do
+    raise "Can't use the child object without preloading!"
+  end
+
   defp restrict_recipients(query, [], _user), do: query
 
   defp restrict_recipients(query, recipients, nil) do
@@ -1088,40 +1162,51 @@ def fetch_activities_query(recipients, opts \\ %{}) do
       skip_thread_containment: Config.get([:instance, :skip_thread_containment])
     }
 
-    Activity
-    |> maybe_preload_objects(opts)
-    |> maybe_preload_bookmarks(opts)
-    |> maybe_preload_report_notes(opts)
-    |> maybe_set_thread_muted_field(opts)
-    |> maybe_order(opts)
-    |> restrict_recipients(recipients, opts[:user])
-    |> restrict_replies(opts)
-    |> restrict_tag(opts)
-    |> restrict_tag_reject(opts)
-    |> restrict_tag_all(opts)
-    |> restrict_since(opts)
-    |> restrict_local(opts)
-    |> restrict_actor(opts)
-    |> restrict_type(opts)
-    |> restrict_state(opts)
-    |> restrict_favorited_by(opts)
-    |> restrict_blocked(restrict_blocked_opts)
-    |> restrict_muted(restrict_muted_opts)
-    |> restrict_filtered(opts)
-    |> restrict_media(opts)
-    |> restrict_visibility(opts)
-    |> restrict_thread_visibility(opts, config)
-    |> restrict_reblogs(opts)
-    |> restrict_pinned(opts)
-    |> restrict_muted_reblogs(restrict_muted_reblogs_opts)
-    |> restrict_instance(opts)
-    |> restrict_announce_object_actor(opts)
-    |> restrict_filtered(opts)
-    |> Activity.restrict_deactivated_users()
-    |> exclude_poll_votes(opts)
-    |> exclude_chat_messages(opts)
-    |> exclude_invisible_actors(opts)
-    |> exclude_visibility(opts)
+    query =
+      Activity
+      |> distinct([a], true)
+      |> maybe_preload_objects(opts)
+      |> maybe_preload_bookmarks(opts)
+      |> maybe_preload_report_notes(opts)
+      |> maybe_set_thread_muted_field(opts)
+      |> maybe_order(opts)
+      |> restrict_recipients(recipients, opts[:user])
+      |> restrict_replies(opts)
+      |> restrict_since(opts)
+      |> restrict_local(opts)
+      |> restrict_actor(opts)
+      |> restrict_type(opts)
+      |> restrict_state(opts)
+      |> restrict_favorited_by(opts)
+      |> restrict_blocked(restrict_blocked_opts)
+      |> restrict_muted(restrict_muted_opts)
+      |> restrict_filtered(opts)
+      |> restrict_media(opts)
+      |> restrict_visibility(opts)
+      |> restrict_thread_visibility(opts, config)
+      |> restrict_reblogs(opts)
+      |> restrict_pinned(opts)
+      |> restrict_muted_reblogs(restrict_muted_reblogs_opts)
+      |> restrict_instance(opts)
+      |> restrict_announce_object_actor(opts)
+      |> restrict_filtered(opts)
+      |> Activity.restrict_deactivated_users()
+      |> exclude_poll_votes(opts)
+      |> exclude_chat_messages(opts)
+      |> exclude_invisible_actors(opts)
+      |> exclude_visibility(opts)
+
+    if Config.get([:instance, :improved_hashtag_timeline]) do
+      query
+      |> restrict_hashtag_any(opts)
+      |> restrict_hashtag_all(opts)
+      |> restrict_hashtag_reject_any(opts)
+    else
+      query
+      |> restrict_tag(opts)
+      |> restrict_tag_reject(opts)
+      |> restrict_tag_all(opts)
+    end
   end
 
   def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index bfec32042..573b26d66 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -199,33 +199,37 @@ test "it fetches the appropriate tag-restricted posts" do
     {:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"})
     {:ok, status_three} = CommonAPI.post(user, %{status: ". #test #reject"})
 
-    fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})
+    for new_timeline_enabled <- [true, false] do
+      clear_config([:instance, :improved_hashtag_timeline], new_timeline_enabled)
 
-    fetch_two = ActivityPub.fetch_activities([], %{type: "Create", tag: ["test", "essais"]})
+      fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})
 
-    fetch_three =
-      ActivityPub.fetch_activities([], %{
-        type: "Create",
-        tag: ["test", "essais"],
-        tag_reject: ["reject"]
-      })
+      fetch_two = ActivityPub.fetch_activities([], %{type: "Create", tag: ["test", "essais"]})
 
-    fetch_four =
-      ActivityPub.fetch_activities([], %{
-        type: "Create",
-        tag: ["test"],
-        tag_all: ["test", "reject"]
-      })
+      fetch_three =
+        ActivityPub.fetch_activities([], %{
+          type: "Create",
+          tag: ["test", "essais"],
+          tag_reject: ["reject"]
+        })
 
-    [fetch_one, fetch_two, fetch_three, fetch_four] =
-      Enum.map([fetch_one, fetch_two, fetch_three, fetch_four], fn statuses ->
-        Enum.map(statuses, fn s -> Repo.preload(s, object: :hashtags) end)
-      end)
+      fetch_four =
+        ActivityPub.fetch_activities([], %{
+          type: "Create",
+          tag: ["test"],
+          tag_all: ["test", "reject"]
+        })
 
-    assert fetch_one == [status_one, status_three]
-    assert fetch_two == [status_one, status_two, status_three]
-    assert fetch_three == [status_one, status_two]
-    assert fetch_four == [status_three]
+      [fetch_one, fetch_two, fetch_three, fetch_four] =
+        Enum.map([fetch_one, fetch_two, fetch_three, fetch_four], fn statuses ->
+          Enum.map(statuses, fn s -> Repo.preload(s, object: :hashtags) end)
+        end)
+
+      assert fetch_one == [status_one, status_three]
+      assert fetch_two == [status_one, status_two, status_three]
+      assert fetch_three == [status_one, status_two]
+      assert fetch_four == [status_three]
+    end
   end
 
   describe "insertion" do

From 14fae94c0e4b04123c7af148260d0a4a51042570 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Mon, 28 Dec 2020 00:08:09 +0300
Subject: [PATCH 013/339] [#3213] Made Object.hashtags/1 work with :hashtags
 assoc. Adjusted tests.

---
 lib/pleroma/config.ex                        |  2 ++
 lib/pleroma/object.ex                        | 14 +++++++++++++-
 lib/pleroma/web/activity_pub/activity_pub.ex | 12 ++++++------
 test/pleroma/activity/ir/topics_test.exs     | 15 ++++++++-------
 4 files changed, 29 insertions(+), 14 deletions(-)

diff --git a/lib/pleroma/config.ex b/lib/pleroma/config.ex
index 86d4f6b72..ee0167f4e 100644
--- a/lib/pleroma/config.ex
+++ b/lib/pleroma/config.ex
@@ -96,6 +96,8 @@ def restrict_unauthenticated_access?(resource, kind) do
     end
   end
 
+  def object_embedded_hashtags?, do: !get([:instance, :improved_hashtag_timeline])
+
   def oauth_consumer_strategies, do: get([:auth, :oauth_consumer_strategies], [])
 
   def oauth_consumer_enabled?, do: oauth_consumer_strategies() != []
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 1d756bcd1..08114d4f2 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -384,7 +384,19 @@ def tags(%Object{data: %{"tag" => tags}}) when is_list(tags), do: tags
 
   def tags(_), do: []
 
-  def hashtags(object), do: embedded_hashtags(object)
+  def hashtags(%Object{} = object) do
+    cond do
+      Config.object_embedded_hashtags?() ->
+        embedded_hashtags(object)
+
+      object.id == "pleroma:fake_object_id" ->
+        []
+
+      true ->
+        hashtag_records = Repo.preload(object, :hashtags).hashtags
+        Enum.map(hashtag_records, & &1.name)
+    end
+  end
 
   defp embedded_hashtags(%Object{data: data}) do
     object_data_hashtags(data)
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 54d1a2350..626cad336 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1199,16 +1199,16 @@ def fetch_activities_query(recipients, opts \\ %{}) do
       |> exclude_invisible_actors(opts)
       |> exclude_visibility(opts)
 
-    if Config.get([:instance, :improved_hashtag_timeline]) do
-      query
-      |> restrict_hashtag_any(opts)
-      |> restrict_hashtag_all(opts)
-      |> restrict_hashtag_reject_any(opts)
-    else
+    if Config.object_embedded_hashtags?() do
       query
       |> restrict_tag(opts)
       |> restrict_tag_reject(opts)
       |> restrict_tag_all(opts)
+    else
+      query
+      |> restrict_hashtag_any(opts)
+      |> restrict_hashtag_all(opts)
+      |> restrict_hashtag_reject_any(opts)
     end
   end
 
diff --git a/test/pleroma/activity/ir/topics_test.exs b/test/pleroma/activity/ir/topics_test.exs
index b464822d9..984777bda 100644
--- a/test/pleroma/activity/ir/topics_test.exs
+++ b/test/pleroma/activity/ir/topics_test.exs
@@ -11,6 +11,8 @@ defmodule Pleroma.Activity.Ir.TopicsTest do
 
   require Pleroma.Constants
 
+  import Mock
+
   describe "poll answer" do
     test "produce no topics" do
       activity = %Activity{object: %Object{data: %{"type" => "Answer"}}}
@@ -77,14 +79,13 @@ test "with no attachments doesn't produce public:media topics", %{activity: acti
       refute Enum.member?(topics, "public:local:media")
     end
 
-    test "converts tags to hash tags", %{activity: %{object: %{data: data} = object} = activity} do
-      tagged_data = Map.put(data, "tag", ["foo", "bar"])
-      activity = %{activity | object: %{object | data: tagged_data}}
+    test "converts tags to hash tags", %{activity: activity} do
+      with_mock(Object, [:passthrough], hashtags: fn _ -> ["foo", "bar"] end) do
+        topics = Topics.get_activity_topics(activity)
 
-      topics = Topics.get_activity_topics(activity)
-
-      assert Enum.member?(topics, "hashtag:foo")
-      assert Enum.member?(topics, "hashtag:bar")
+        assert Enum.member?(topics, "hashtag:foo")
+        assert Enum.member?(topics, "hashtag:bar")
+      end
     end
 
     test "only converts strings to hash tags", %{

From a25c1e8ec0b6f4ef2e9f68c4ad5e48e18f5f01a7 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Wed, 30 Dec 2020 14:35:19 +0300
Subject: [PATCH 014/339] [#3213] Improved `database.transfer_hashtags` mix
 task: proper rollback, speedup.

---
 lib/mix/tasks/pleroma/database.ex | 46 +++++++++++++++++--------------
 1 file changed, 25 insertions(+), 21 deletions(-)

diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index 093c7dd30..d44bd3478 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -137,6 +137,8 @@ def run(["transfer_hashtags"]) do
 
     start_pleroma()
 
+    Logger.info("Starting transferring object embedded hashtags to `hashtags` table...")
+
     from(
       object in Object,
       left_join: hashtag in assoc(object, :hashtags),
@@ -144,21 +146,12 @@ def run(["transfer_hashtags"]) do
       where: fragment("(?)->>'tag' != '[]'", object.data),
       select: %{
         id: object.id,
-        inserted_at: object.inserted_at,
         tag: fragment("(?)->>'tag'", object.data)
-      },
-      order_by: [desc: object.id]
+      }
     )
     |> Pleroma.Repo.chunk_stream(100, :batches)
     |> Stream.each(fn objects ->
-      chunk_start = List.first(objects)
-      chunk_end = List.last(objects)
-
-      Logger.info(
-        "transfer_hashtags: " <>
-          "#{chunk_start.id} (#{chunk_start.inserted_at}) -- " <>
-          "#{chunk_end.id} (#{chunk_end.inserted_at})"
-      )
+      Logger.info("Processing #{length(objects)} objects...")
 
       Enum.map(
         objects,
@@ -168,28 +161,39 @@ def run(["transfer_hashtags"]) do
             |> Jason.decode!()
             |> Enum.filter(&is_bitstring(&1))
 
-          with {:ok, hashtag_records} <- Hashtag.get_or_create_by_names(hashtags) do
-            Repo.transaction(fn ->
+          Repo.transaction(fn ->
+            with {:ok, hashtag_records} <- Hashtag.get_or_create_by_names(hashtags) do
               for hashtag_record <- hashtag_records do
-                with {:error, _} <-
+                with {:ok, _} <-
                        Ecto.Adapters.SQL.query(
                          Repo,
                          "insert into hashtags_objects(hashtag_id, object_id) values " <>
                            "(#{hashtag_record.id}, #{object.id});"
                        ) do
-                  Logger.warn(
-                    "ERROR: could not link object #{object.id} and hashtag #{hashtag_record.id}"
-                  )
+                  :noop
+                else
+                  {:error, e} ->
+                    error =
+                      "ERROR: could not link object #{object.id} and hashtag " <>
+                        "#{hashtag_record.id}: #{inspect(e)}"
+
+                    Logger.error(error)
+                    Repo.rollback(error)
                 end
               end
-            end)
-          else
-            e -> Logger.warn("ERROR: could not process object #{object.id}: #{inspect(e)}")
-          end
+            else
+              e ->
+                error = "ERROR: could not create hashtags for object #{object.id}: #{inspect(e)}"
+                Logger.error(error)
+                Repo.rollback(error)
+            end
+          end)
         end
       )
     end)
     |> Stream.run()
+
+    Logger.info("Done transferring hashtags. Please check logs to ensure no errors.")
   end
 
   def run(["vacuum", args]) do

From e0b5edb6d5a423bfd247e0774d2f5bc642b2fb80 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Wed, 30 Dec 2020 14:42:35 +0300
Subject: [PATCH 015/339] [#3213] Fixed Object.object_data_hashtags/1 to
 process only AS2 elements of `data.tag` (basing on #2984).

---
 lib/pleroma/object.ex | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 08114d4f2..dad572f2b 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -405,8 +405,16 @@ defp embedded_hashtags(%Object{data: data}) do
   defp embedded_hashtags(_), do: []
 
   defp object_data_hashtags(%{"tag" => tags}) when is_list(tags) do
-    # Note: AS2 map-type elements are ignored
-    Enum.filter(tags, &is_bitstring(&1))
+    # Note: Old format with copy of hashtags as strings is ignored, using AS2
+    tags
+    |> Enum.filter(fn
+      %{"type" => "Hashtag"} = data -> Map.has_key?(data, "name")
+      _ -> false
+    end)
+    |> Enum.map(fn
+      %{"name" => "#" <> hashtag} -> String.downcase(hashtag)
+      %{"name" => hashtag} -> String.downcase(hashtag)
+    end)
   end
 
   defp object_data_hashtags(_), do: []

From 8d1a0c1afd46f8683e9022523cecffb9b60c9f8c Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Wed, 30 Dec 2020 15:22:49 +0300
Subject: [PATCH 016/339] [#3213] Made Object.object_data_hashtags/1 handle
 both AS2 and plain text hashtags.

---
 lib/pleroma/object.ex | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index dad572f2b..7e79e15ee 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -405,16 +405,18 @@ defp embedded_hashtags(%Object{data: data}) do
   defp embedded_hashtags(_), do: []
 
   defp object_data_hashtags(%{"tag" => tags}) when is_list(tags) do
-    # Note: Old format with copy of hashtags as strings is ignored, using AS2
     tags
     |> Enum.filter(fn
       %{"type" => "Hashtag"} = data -> Map.has_key?(data, "name")
+      plain_text when is_bitstring(plain_text) -> true
       _ -> false
     end)
     |> Enum.map(fn
       %{"name" => "#" <> hashtag} -> String.downcase(hashtag)
       %{"name" => hashtag} -> String.downcase(hashtag)
+      hashtag when is_bitstring(hashtag) -> String.downcase(hashtag)
     end)
+    |> Enum.uniq()
   end
 
   defp object_data_hashtags(_), do: []

From 367f0c31c3c15f75aed1d3ba66914e4197c19596 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Thu, 31 Dec 2020 09:36:26 +0300
Subject: [PATCH 017/339] [#3213] Added query options support for
 Repo.chunk_stream/4. Used infinite timeout in transfer_hashtags select query.

---
 lib/mix/tasks/pleroma/database.ex | 11 +++++------
 lib/pleroma/repo.ex               |  6 +++---
 2 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index d44bd3478..f903cf75b 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -149,9 +149,9 @@ def run(["transfer_hashtags"]) do
         tag: fragment("(?)->>'tag'", object.data)
       }
     )
-    |> Pleroma.Repo.chunk_stream(100, :batches)
+    |> Repo.chunk_stream(100, :batches, timeout: :infinity)
     |> Stream.each(fn objects ->
-      Logger.info("Processing #{length(objects)} objects...")
+      Logger.info("Processing #{length(objects)} objects starting from id #{hd(objects).id}...")
 
       Enum.map(
         objects,
@@ -165,10 +165,9 @@ def run(["transfer_hashtags"]) do
             with {:ok, hashtag_records} <- Hashtag.get_or_create_by_names(hashtags) do
               for hashtag_record <- hashtag_records do
                 with {:ok, _} <-
-                       Ecto.Adapters.SQL.query(
-                         Repo,
-                         "insert into hashtags_objects(hashtag_id, object_id) values " <>
-                           "(#{hashtag_record.id}, #{object.id});"
+                       Repo.query(
+                         "insert into hashtags_objects(hashtag_id, object_id) values ($1, $2);",
+                         [hashtag_record.id, object.id]
                        ) do
                   :noop
                 else
diff --git a/lib/pleroma/repo.ex b/lib/pleroma/repo.ex
index 4524bd5e2..78711e6ac 100644
--- a/lib/pleroma/repo.ex
+++ b/lib/pleroma/repo.ex
@@ -63,8 +63,8 @@ def get_assoc(resource, association) do
   iex> Pleroma.Repo.chunk_stream(Pleroma.Activity.Queries.by_actor(ap_id), 500, :batches)
   """
   @spec chunk_stream(Ecto.Query.t(), integer(), atom()) :: Enumerable.t()
-  def chunk_stream(query, chunk_size, returns_as \\ :one) do
-    # We don't actually need start and end funcitons of resource streaming,
+  def chunk_stream(query, chunk_size, returns_as \\ :one, query_options \\ []) do
+    # We don't actually need start and end functions of resource streaming,
     # but it seems to be the only way to not fetch records one-by-one and
     # have individual records be the elements of the stream, instead of
     # lists of records
@@ -76,7 +76,7 @@ def chunk_stream(query, chunk_size, returns_as \\ :one) do
           |> order_by(asc: :id)
           |> where([r], r.id > ^last_id)
           |> limit(^chunk_size)
-          |> all()
+          |> all(query_options)
           |> case do
             [] ->
               {:halt, last_id}

From 303055456f19152821ec5ec1df88d60c03f60905 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Thu, 31 Dec 2020 12:45:23 +0300
Subject: [PATCH 018/339] Alternative implementation of hashtag-filtering
 queries in ActivityPub. Fixed GROUP BY clause for aggregation on hashtags.

---
 lib/pleroma/activity.ex                      |   2 +
 lib/pleroma/web/activity_pub/activity_pub.ex | 120 +++++++++++++++----
 2 files changed, 100 insertions(+), 22 deletions(-)

diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index 9d970a808..df216e4de 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -113,6 +113,7 @@ def with_preloaded_bookmark(query, %User{} = user) do
     from([a] in query,
       left_join: b in Bookmark,
       on: b.user_id == ^user.id and b.activity_id == a.id,
+      as: :bookmark,
       preload: [bookmark: b]
     )
   end
@@ -123,6 +124,7 @@ def with_preloaded_report_notes(query) do
     from([a] in query,
       left_join: r in ReportNote,
       on: a.id == r.activity_id,
+      as: :report_note,
       preload: [report_notes: r]
     )
   end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 626cad336..339843330 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -713,22 +713,92 @@ defp restrict_tag(query, %{tag: tag}) when is_binary(tag) do
 
   defp restrict_tag(query, _), do: query
 
+  defp restrict_hashtag(query, opts) do
+    [tag_any, tag_all, tag_reject] =
+      [:tag, :tag_all, :tag_reject]
+      |> Enum.map(&opts[&1])
+      |> Enum.map(&List.wrap(&1))
+
+    has_conditions = Enum.any?([tag_any, tag_all, tag_reject], &Enum.any?(&1))
+
+    cond do
+      !has_conditions ->
+        query
+
+      opts[:skip_preload] ->
+        raise_on_missing_preload()
+
+      true ->
+        query
+        |> group_by_all_bindings()
+        |> join(:left, [_activity, object], hashtag in assoc(object, :hashtags), as: :hashtag)
+        |> maybe_restrict_hashtag_any(tag_any)
+        |> maybe_restrict_hashtag_all(tag_all)
+        |> maybe_restrict_hashtag_reject_any(tag_reject)
+    end
+  end
+
+  # Groups by all bindings to allow aggregation on hashtags
+  defp group_by_all_bindings(query) do
+    # Expecting named bindings: :object, :bookmark, :thread_mute, :report_note
+    cond do
+      Enum.count(query.aliases) == 4 ->
+        from([a, o, b3, b4, b5] in query, group_by: [a.id, o.id, b3.id, b4.id, b5.id])
+
+      Enum.count(query.aliases) == 3 ->
+        from([a, o, b3, b4] in query, group_by: [a.id, o.id, b3.id, b4.id])
+
+      Enum.count(query.aliases) == 2 ->
+        from([a, o, b3] in query, group_by: [a.id, o.id, b3.id])
+
+      true ->
+        from([a, o] in query, group_by: [a.id, o.id])
+    end
+  end
+
+  defp maybe_restrict_hashtag_any(query, []) do
+    query
+  end
+
+  defp maybe_restrict_hashtag_any(query, tags) do
+    having(
+      query,
+      [hashtag: hashtag],
+      fragment("array_agg(?) && (?)", hashtag.name, ^tags)
+    )
+  end
+
+  defp maybe_restrict_hashtag_all(query, []) do
+    query
+  end
+
+  defp maybe_restrict_hashtag_all(query, tags) do
+    having(
+      query,
+      [hashtag: hashtag],
+      fragment("array_agg(?) @> (?)", hashtag.name, ^tags)
+    )
+  end
+
+  defp maybe_restrict_hashtag_reject_any(query, []) do
+    query
+  end
+
+  defp maybe_restrict_hashtag_reject_any(query, tags) do
+    having(
+      query,
+      [hashtag: hashtag],
+      fragment("not(array_agg(?) && (?))", hashtag.name, ^tags)
+    )
+  end
+
   defp restrict_hashtag_reject_any(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
     raise_on_missing_preload()
   end
 
   defp restrict_hashtag_reject_any(query, %{tag_reject: tags_reject}) when is_list(tags_reject) do
-    if has_named_binding?(query, :thread_mute) do
-      from(
-        [activity, object, thread_mute] in query,
-        group_by: [activity.id, object.id, thread_mute.id]
-      )
-    else
-      from(
-        [activity, object] in query,
-        group_by: [activity.id, object.id]
-      )
-    end
+    query
+    |> group_by_all_bindings()
     |> join(:left, [_activity, object], hashtag in assoc(object, :hashtags), as: :hashtag)
     |> having(
       [hashtag: hashtag],
@@ -1167,7 +1237,6 @@ def fetch_activities_query(recipients, opts \\ %{}) do
 
     query =
       Activity
-      |> distinct([a], true)
       |> maybe_preload_objects(opts)
       |> maybe_preload_bookmarks(opts)
       |> maybe_preload_report_notes(opts)
@@ -1199,16 +1268,23 @@ def fetch_activities_query(recipients, opts \\ %{}) do
       |> exclude_invisible_actors(opts)
       |> exclude_visibility(opts)
 
-    if Config.object_embedded_hashtags?() do
-      query
-      |> restrict_tag(opts)
-      |> restrict_tag_reject(opts)
-      |> restrict_tag_all(opts)
-    else
-      query
-      |> restrict_hashtag_any(opts)
-      |> restrict_hashtag_all(opts)
-      |> restrict_hashtag_reject_any(opts)
+    cond do
+      Config.object_embedded_hashtags?() ->
+        query
+        |> restrict_tag(opts)
+        |> restrict_tag_reject(opts)
+        |> restrict_tag_all(opts)
+
+      # TODO: benchmark (initial approach preferring non-aggregate ops when possible)
+      Config.get([:instance, :improved_hashtag_timeline]) == :join ->
+        query
+        |> distinct([activity], true)
+        |> restrict_hashtag_any(opts)
+        |> restrict_hashtag_all(opts)
+        |> restrict_hashtag_reject_any(opts)
+
+      true ->
+        restrict_hashtag(query, opts)
     end
   end
 

From 0d521022fe6157ce9a346c6915ce38292e653bb3 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Thu, 7 Jan 2021 12:20:29 +0300
Subject: [PATCH 019/339] [#3213] Removed PK from hashtags_objects table.
 Improved hashtags_transfer mix task (logging of failed ids).

---
 lib/mix/tasks/pleroma/database.ex             | 29 +++++++++++--------
 lib/pleroma/object.ex                         |  4 +--
 ...20201221203824_create_hashtags_objects.exs |  2 +-
 3 files changed, 20 insertions(+), 15 deletions(-)

diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index f903cf75b..918752dc2 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -139,6 +139,7 @@ def run(["transfer_hashtags"]) do
 
     Logger.info("Starting transferring object embedded hashtags to `hashtags` table...")
 
+    # Note: most objects have Mention-type AS2 tags and no hashtags (but we can't filter them out)
     from(
       object in Object,
       left_join: hashtag in assoc(object, :hashtags),
@@ -153,13 +154,10 @@ def run(["transfer_hashtags"]) do
     |> Stream.each(fn objects ->
       Logger.info("Processing #{length(objects)} objects starting from id #{hd(objects).id}...")
 
-      Enum.map(
-        objects,
-        fn object ->
-          hashtags =
-            object.tag
-            |> Jason.decode!()
-            |> Enum.filter(&is_bitstring(&1))
+      failed_ids =
+        objects
+        |> Enum.map(fn object ->
+          hashtags = Object.object_data_hashtags(%{"tag" => Jason.decode!(object.tag)})
 
           Repo.transaction(fn ->
             with {:ok, hashtag_records} <- Hashtag.get_or_create_by_names(hashtags) do
@@ -169,7 +167,7 @@ def run(["transfer_hashtags"]) do
                          "insert into hashtags_objects(hashtag_id, object_id) values ($1, $2);",
                          [hashtag_record.id, object.id]
                        ) do
-                  :noop
+                  nil
                 else
                   {:error, e} ->
                     error =
@@ -177,18 +175,25 @@ def run(["transfer_hashtags"]) do
                         "#{hashtag_record.id}: #{inspect(e)}"
 
                     Logger.error(error)
-                    Repo.rollback(error)
+                    Repo.rollback(object.id)
                 end
               end
+
+              object.id
             else
               e ->
                 error = "ERROR: could not create hashtags for object #{object.id}: #{inspect(e)}"
                 Logger.error(error)
-                Repo.rollback(error)
+                Repo.rollback(object.id)
             end
           end)
-        end
-      )
+        end)
+        |> Enum.filter(&(elem(&1, 0) == :error))
+        |> Enum.map(&elem(&1, 1))
+
+      if Enum.any?(failed_ids) do
+        Logger.error("ERROR: transfer_hashtags iteration failed for ids: #{inspect(failed_ids)}")
+      end
     end)
     |> Stream.run()
 
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 7e79e15ee..61f2ffa19 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -404,7 +404,7 @@ defp embedded_hashtags(%Object{data: data}) do
 
   defp embedded_hashtags(_), do: []
 
-  defp object_data_hashtags(%{"tag" => tags}) when is_list(tags) do
+  def object_data_hashtags(%{"tag" => tags}) when is_list(tags) do
     tags
     |> Enum.filter(fn
       %{"type" => "Hashtag"} = data -> Map.has_key?(data, "name")
@@ -419,5 +419,5 @@ defp object_data_hashtags(%{"tag" => tags}) when is_list(tags) do
     |> Enum.uniq()
   end
 
-  defp object_data_hashtags(_), do: []
+  def object_data_hashtags(_), do: []
 end
diff --git a/priv/repo/migrations/20201221203824_create_hashtags_objects.exs b/priv/repo/migrations/20201221203824_create_hashtags_objects.exs
index b2649b4fb..214ea81c3 100644
--- a/priv/repo/migrations/20201221203824_create_hashtags_objects.exs
+++ b/priv/repo/migrations/20201221203824_create_hashtags_objects.exs
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateHashtagsObjects do
   use Ecto.Migration
 
   def change do
-    create_if_not_exists table(:hashtags_objects) do
+    create_if_not_exists table(:hashtags_objects, primary_key: false) do
       add(:hashtag_id, references(:hashtags), null: false)
       add(:object_id, references(:objects), null: false)
     end

From 8c972de0457199098c5f3378313d08a9dd2d64ce Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Sun, 10 Jan 2021 11:44:39 +0300
Subject: [PATCH 020/339] [#3213] transfer_hashtags mix task refactoring.

---
 lib/mix/tasks/pleroma/database.ex | 127 ++++++++++++++----------------
 1 file changed, 59 insertions(+), 68 deletions(-)

diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index 918752dc2..e9686fc1b 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -132,74 +132,6 @@ def run(["fix_likes_collections"]) do
     |> Stream.run()
   end
 
-  def run(["transfer_hashtags"]) do
-    import Ecto.Query
-
-    start_pleroma()
-
-    Logger.info("Starting transferring object embedded hashtags to `hashtags` table...")
-
-    # Note: most objects have Mention-type AS2 tags and no hashtags (but we can't filter them out)
-    from(
-      object in Object,
-      left_join: hashtag in assoc(object, :hashtags),
-      where: is_nil(hashtag.id),
-      where: fragment("(?)->>'tag' != '[]'", object.data),
-      select: %{
-        id: object.id,
-        tag: fragment("(?)->>'tag'", object.data)
-      }
-    )
-    |> Repo.chunk_stream(100, :batches, timeout: :infinity)
-    |> Stream.each(fn objects ->
-      Logger.info("Processing #{length(objects)} objects starting from id #{hd(objects).id}...")
-
-      failed_ids =
-        objects
-        |> Enum.map(fn object ->
-          hashtags = Object.object_data_hashtags(%{"tag" => Jason.decode!(object.tag)})
-
-          Repo.transaction(fn ->
-            with {:ok, hashtag_records} <- Hashtag.get_or_create_by_names(hashtags) do
-              for hashtag_record <- hashtag_records do
-                with {:ok, _} <-
-                       Repo.query(
-                         "insert into hashtags_objects(hashtag_id, object_id) values ($1, $2);",
-                         [hashtag_record.id, object.id]
-                       ) do
-                  nil
-                else
-                  {:error, e} ->
-                    error =
-                      "ERROR: could not link object #{object.id} and hashtag " <>
-                        "#{hashtag_record.id}: #{inspect(e)}"
-
-                    Logger.error(error)
-                    Repo.rollback(object.id)
-                end
-              end
-
-              object.id
-            else
-              e ->
-                error = "ERROR: could not create hashtags for object #{object.id}: #{inspect(e)}"
-                Logger.error(error)
-                Repo.rollback(object.id)
-            end
-          end)
-        end)
-        |> Enum.filter(&(elem(&1, 0) == :error))
-        |> Enum.map(&elem(&1, 1))
-
-      if Enum.any?(failed_ids) do
-        Logger.error("ERROR: transfer_hashtags iteration failed for ids: #{inspect(failed_ids)}")
-      end
-    end)
-    |> Stream.run()
-
-    Logger.info("Done transferring hashtags. Please check logs to ensure no errors.")
-  end
-
   def run(["vacuum", args]) do
     start_pleroma()
 
@@ -239,4 +171,63 @@ def run(["ensure_expiration"]) do
     end)
     |> Stream.run()
   end
+
+  def run(["transfer_hashtags"]) do
+    import Ecto.Query
+
+    start_pleroma()
+
+    Logger.info("Starting transferring object embedded hashtags to `hashtags` table...")
+
+    # Note: most objects have Mention-type AS2 tags and no hashtags (but we can't filter them out)
+    from(
+      object in Object,
+      left_join: hashtag in assoc(object, :hashtags),
+      where: is_nil(hashtag.id),
+      where:
+        fragment("(?)->'tag' IS NOT NULL AND (?)->'tag' != '[]'::jsonb", object.data, object.data),
+      select: %{
+        id: object.id,
+        tag: fragment("(?)->'tag'", object.data)
+      }
+    )
+    |> Repo.chunk_stream(100, :one, timeout: :infinity)
+    |> Stream.each(&transfer_object_hashtags(&1))
+    |> Stream.run()
+
+    Logger.info("Done transferring hashtags. Please check logs to ensure no errors.")
+  end
+
+  defp transfer_object_hashtags(object) do
+    hashtags = Object.object_data_hashtags(%{"tag" => object.tag})
+
+    Repo.transaction(fn ->
+      with {:ok, hashtag_records} <- Hashtag.get_or_create_by_names(hashtags) do
+        for hashtag_record <- hashtag_records do
+          with {:ok, _} <-
+                 Repo.query(
+                   "insert into hashtags_objects(hashtag_id, object_id) values ($1, $2);",
+                   [hashtag_record.id, object.id]
+                 ) do
+            nil
+          else
+            {:error, e} ->
+              error =
+                "ERROR: could not link object #{object.id} and hashtag " <>
+                  "#{hashtag_record.id}: #{inspect(e)}"
+
+              Logger.error(error)
+              Repo.rollback(object.id)
+          end
+        end
+
+        object.id
+      else
+        e ->
+          error = "ERROR: could not create hashtags for object #{object.id}: #{inspect(e)}"
+          Logger.error(error)
+          Repo.rollback(object.id)
+      end
+    end)
+  end
 end

From 3e4d84729a4ca8d9779d439a9aa2c8c23b3acd1d Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Wed, 13 Jan 2021 22:07:38 +0300
Subject: [PATCH 021/339] [#3213] Prototype of data migrations functionality /
 HashtagsTableMigrator.

---
 lib/mix/tasks/pleroma/database.ex             |  60 -----
 lib/pleroma/application.ex                    |   3 +-
 lib/pleroma/config.ex                         |   4 +-
 lib/pleroma/data_migration.ex                 |  46 ++++
 lib/pleroma/delivery.ex                       |   1 -
 lib/pleroma/ecto_enums.ex                     |   8 +
 .../migrators/hashtags_table_migrator.ex      | 211 ++++++++++++++++++
 lib/pleroma/web/activity_pub/activity_pub.ex  |   2 +-
 .../20210105195018_create_data_migrations.exs |  17 ++
 ...gration_create_populate_hashtags_table.exs |  14 ++
 ...72254_create_data_migration_failed_ids.exs |  14 ++
 11 files changed, 316 insertions(+), 64 deletions(-)
 create mode 100644 lib/pleroma/data_migration.ex
 create mode 100644 lib/pleroma/migrators/hashtags_table_migrator.ex
 create mode 100644 priv/repo/migrations/20210105195018_create_data_migrations.exs
 create mode 100644 priv/repo/migrations/20210106183301_data_migration_create_populate_hashtags_table.exs
 create mode 100644 priv/repo/migrations/20210111172254_create_data_migration_failed_ids.exs

diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index e9686fc1b..08ede9eef 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -4,7 +4,6 @@
 
 defmodule Mix.Tasks.Pleroma.Database do
   alias Pleroma.Conversation
-  alias Pleroma.Hashtag
   alias Pleroma.Maintenance
   alias Pleroma.Object
   alias Pleroma.Repo
@@ -171,63 +170,4 @@ def run(["ensure_expiration"]) do
     end)
     |> Stream.run()
   end
-
-  def run(["transfer_hashtags"]) do
-    import Ecto.Query
-
-    start_pleroma()
-
-    Logger.info("Starting transferring object embedded hashtags to `hashtags` table...")
-
-    # Note: most objects have Mention-type AS2 tags and no hashtags (but we can't filter them out)
-    from(
-      object in Object,
-      left_join: hashtag in assoc(object, :hashtags),
-      where: is_nil(hashtag.id),
-      where:
-        fragment("(?)->'tag' IS NOT NULL AND (?)->'tag' != '[]'::jsonb", object.data, object.data),
-      select: %{
-        id: object.id,
-        tag: fragment("(?)->'tag'", object.data)
-      }
-    )
-    |> Repo.chunk_stream(100, :one, timeout: :infinity)
-    |> Stream.each(&transfer_object_hashtags(&1))
-    |> Stream.run()
-
-    Logger.info("Done transferring hashtags. Please check logs to ensure no errors.")
-  end
-
-  defp transfer_object_hashtags(object) do
-    hashtags = Object.object_data_hashtags(%{"tag" => object.tag})
-
-    Repo.transaction(fn ->
-      with {:ok, hashtag_records} <- Hashtag.get_or_create_by_names(hashtags) do
-        for hashtag_record <- hashtag_records do
-          with {:ok, _} <-
-                 Repo.query(
-                   "insert into hashtags_objects(hashtag_id, object_id) values ($1, $2);",
-                   [hashtag_record.id, object.id]
-                 ) do
-            nil
-          else
-            {:error, e} ->
-              error =
-                "ERROR: could not link object #{object.id} and hashtag " <>
-                  "#{hashtag_record.id}: #{inspect(e)}"
-
-              Logger.error(error)
-              Repo.rollback(object.id)
-          end
-        end
-
-        object.id
-      else
-        e ->
-          error = "ERROR: could not create hashtags for object #{object.id}: #{inspect(e)}"
-          Logger.error(error)
-          Repo.rollback(object.id)
-      end
-    end)
-  end
 end
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index bd568d858..962529dfd 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -104,7 +104,8 @@ def start(_type, _args) do
         chat_child(chat_enabled?()) ++
         [
           Pleroma.Web.Endpoint,
-          Pleroma.Gopher.Server
+          Pleroma.Gopher.Server,
+          Pleroma.Migrators.HashtagsTableMigrator
         ]
 
     # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
diff --git a/lib/pleroma/config.ex b/lib/pleroma/config.ex
index ee0167f4e..dbfb114d6 100644
--- a/lib/pleroma/config.ex
+++ b/lib/pleroma/config.ex
@@ -96,7 +96,9 @@ def restrict_unauthenticated_access?(resource, kind) do
     end
   end
 
-  def object_embedded_hashtags?, do: !get([:instance, :improved_hashtag_timeline])
+  def improved_hashtag_timeline_path, do: [:instance, :improved_hashtag_timeline]
+  def improved_hashtag_timeline, do: get(improved_hashtag_timeline_path())
+  def object_embedded_hashtags?, do: !improved_hashtag_timeline()
 
   def oauth_consumer_strategies, do: get([:auth, :oauth_consumer_strategies], [])
 
diff --git a/lib/pleroma/data_migration.ex b/lib/pleroma/data_migration.ex
new file mode 100644
index 000000000..64fa155ff
--- /dev/null
+++ b/lib/pleroma/data_migration.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 Pleroma.DataMigration do
+  use Ecto.Schema
+
+  alias Pleroma.DataMigration
+  alias Pleroma.DataMigration.State
+  alias Pleroma.Repo
+
+  import Ecto.Changeset
+
+  schema "data_migrations" do
+    field(:name, :string)
+    field(:state, State, default: :pending)
+    field(:feature_lock, :boolean, default: false)
+    field(:params, :map, default: %{})
+    field(:data, :map, default: %{})
+
+    timestamps()
+  end
+
+  def changeset(data_migration, params \\ %{}) do
+    data_migration
+    |> cast(params, [:name, :state, :feature_lock, :params, :data])
+    |> validate_required([:name])
+    |> unique_constraint(:name)
+  end
+
+  def update(data_migration, params \\ %{}) do
+    data_migration
+    |> changeset(params)
+    |> Repo.update()
+  end
+
+  def update_state(data_migration, new_state) do
+    update(data_migration, %{state: new_state})
+  end
+
+  def get_by_name(name) do
+    Repo.get_by(DataMigration, name: name)
+  end
+
+  def populate_hashtags_table, do: get_by_name("populate_hashtags_table")
+end
diff --git a/lib/pleroma/delivery.ex b/lib/pleroma/delivery.ex
index 0ded2855c..baf79dda7 100644
--- a/lib/pleroma/delivery.ex
+++ b/lib/pleroma/delivery.ex
@@ -9,7 +9,6 @@ defmodule Pleroma.Delivery do
   alias Pleroma.Object
   alias Pleroma.Repo
   alias Pleroma.User
-  alias Pleroma.User
 
   import Ecto.Changeset
   import Ecto.Query
diff --git a/lib/pleroma/ecto_enums.ex b/lib/pleroma/ecto_enums.ex
index 6fc47620c..f0ae658a4 100644
--- a/lib/pleroma/ecto_enums.ex
+++ b/lib/pleroma/ecto_enums.ex
@@ -17,3 +17,11 @@
   follow_accept: 2,
   follow_reject: 3
 )
+
+defenum(Pleroma.DataMigration.State,
+  pending: 1,
+  running: 2,
+  complete: 3,
+  failed: 4,
+  manual: 5
+)
diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
new file mode 100644
index 000000000..a7e3de542
--- /dev/null
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -0,0 +1,211 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Migrators.HashtagsTableMigrator do
+  defmodule State do
+    use Agent
+
+    @init_state %{}
+
+    def start_link(_) do
+      Agent.start_link(fn -> @init_state end, name: __MODULE__)
+    end
+
+    def get do
+      Agent.get(__MODULE__, & &1)
+    end
+
+    def put(key, value) do
+      Agent.update(__MODULE__, fn state ->
+        Map.put(state, key, value)
+      end)
+    end
+
+    def increment(key, increment \\ 1) do
+      Agent.update(__MODULE__, fn state ->
+        updated_value = (state[key] || 0) + increment
+        Map.put(state, key, updated_value)
+      end)
+    end
+  end
+
+  use GenServer
+
+  require Logger
+
+  import Ecto.Query
+
+  alias Pleroma.Config
+  alias Pleroma.DataMigration
+  alias Pleroma.Hashtag
+  alias Pleroma.Object
+  alias Pleroma.Repo
+
+  defdelegate state(), to: State, as: :get
+  defdelegate put_state(key, value), to: State, as: :put
+  defdelegate increment_state(key, increment), to: State, as: :increment
+
+  defdelegate data_migration(), to: DataMigration, as: :populate_hashtags_table
+
+  def start_link(_) do
+    GenServer.start_link(__MODULE__, nil, name: __MODULE__)
+  end
+
+  @impl true
+  def init(_) do
+    {:ok, nil, {:continue, :init_state}}
+  end
+
+  @impl true
+  def handle_continue(:init_state, _state) do
+    {:ok, _} = State.start_link(nil)
+
+    put_state(:status, :init)
+
+    dm = data_migration()
+
+    cond do
+      Config.get(:env) == :test ->
+        put_state(:status, :noop)
+
+      is_nil(dm) ->
+        put_state(:status, :halt)
+        put_state(:message, "Data migration does not exist.")
+
+      dm.state == :manual ->
+        put_state(:status, :noop)
+        put_state(:message, "Data migration is in manual execution state.")
+
+      dm.state == :complete ->
+        handle_success()
+
+      true ->
+        send(self(), :migrate_hashtags)
+    end
+
+    {:noreply, nil}
+  end
+
+  @impl true
+  def handle_info(:migrate_hashtags, state) do
+    data_migration = data_migration()
+
+    {:ok, data_migration} = DataMigration.update_state(data_migration, :running)
+    put_state(:status, :running)
+
+    Logger.info("Starting transferring object embedded hashtags to `hashtags` table...")
+
+    max_processed_id = data_migration.data["max_processed_id"] || 0
+
+    # Note: most objects have Mention-type AS2 tags and no hashtags (but we can't filter them out)
+    from(
+      object in Object,
+      left_join: hashtag in assoc(object, :hashtags),
+      where: object.id > ^max_processed_id,
+      where: is_nil(hashtag.id),
+      where:
+        fragment("(?)->'tag' IS NOT NULL AND (?)->'tag' != '[]'::jsonb", object.data, object.data),
+      select: %{
+        id: object.id,
+        tag: fragment("(?)->'tag'", object.data)
+      }
+    )
+    |> Repo.chunk_stream(100, :batches, timeout: :infinity)
+    |> Stream.each(fn objects ->
+      object_ids = Enum.map(objects, & &1.id)
+
+      failed_ids =
+        objects
+        |> Enum.map(&transfer_object_hashtags(&1))
+        |> Enum.filter(&(elem(&1, 0) == :error))
+        |> Enum.map(&elem(&1, 1))
+
+      for failed_id <- failed_ids do
+        _ =
+          Repo.query(
+            "INSERT INTO data_migration_failed_ids(data_migration_id, record_id) " <>
+              "VALUES ($1, $2) ON CONFLICT DO NOTHING;",
+            [data_migration.id, failed_id]
+          )
+      end
+
+      _ =
+        Repo.query(
+          "DELETE FROM data_migration_failed_ids WHERE id = ANY($1)",
+          [object_ids -- failed_ids]
+        )
+
+      max_object_id = Enum.at(object_ids, -1)
+      _ = DataMigration.update(data_migration, %{data: %{"max_processed_id" => max_object_id}})
+
+      increment_state(:processed_count, length(object_ids))
+      increment_state(:failed_count, length(failed_ids))
+
+      # A quick and dirty approach to controlling the load this background migration imposes
+      sleep_interval = Config.get([:populate_hashtags_table, :sleep_interval_ms], 0)
+      Process.sleep(sleep_interval)
+    end)
+    |> Stream.run()
+
+    with {:ok, %{rows: [[0]]}} <-
+           Repo.query(
+             "SELECT COUNT(record_id) FROM data_migration_failed_ids WHERE data_migration_id = $1;",
+             [data_migration.id]
+           ) do
+      put_state(:status, :complete)
+      _ = DataMigration.update_state(data_migration, :complete)
+
+      handle_success()
+    else
+      _ ->
+        put_state(:status, :failed)
+        put_state(:message, "Please check data_migration_failed_ids records.")
+    end
+
+    {:noreply, state}
+  end
+
+  defp transfer_object_hashtags(object) do
+    hashtags = Object.object_data_hashtags(%{"tag" => object.tag})
+
+    Repo.transaction(fn ->
+      with {:ok, hashtag_records} <- Hashtag.get_or_create_by_names(hashtags) do
+        for hashtag_record <- hashtag_records do
+          with {:ok, _} <-
+                 Repo.query(
+                   "insert into hashtags_objects(hashtag_id, object_id) values ($1, $2);",
+                   [hashtag_record.id, object.id]
+                 ) do
+            nil
+          else
+            {:error, e} ->
+              error =
+                "ERROR: could not link object #{object.id} and hashtag " <>
+                  "#{hashtag_record.id}: #{inspect(e)}"
+
+              Logger.error(error)
+              Repo.rollback(object.id)
+          end
+        end
+
+        object.id
+      else
+        e ->
+          error = "ERROR: could not create hashtags for object #{object.id}: #{inspect(e)}"
+          Logger.error(error)
+          Repo.rollback(object.id)
+      end
+    end)
+  end
+
+  defp handle_success do
+    put_state(:status, :complete)
+
+    unless Config.improved_hashtag_timeline() do
+      Config.put(Config.improved_hashtag_timeline_path(), true)
+    end
+
+    :ok
+  end
+end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 339843330..6131ae85b 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1276,7 +1276,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
         |> restrict_tag_all(opts)
 
       # TODO: benchmark (initial approach preferring non-aggregate ops when possible)
-      Config.get([:instance, :improved_hashtag_timeline]) == :join ->
+      Config.improved_hashtag_timeline() == :join ->
         query
         |> distinct([activity], true)
         |> restrict_hashtag_any(opts)
diff --git a/priv/repo/migrations/20210105195018_create_data_migrations.exs b/priv/repo/migrations/20210105195018_create_data_migrations.exs
new file mode 100644
index 000000000..5f2e8d96c
--- /dev/null
+++ b/priv/repo/migrations/20210105195018_create_data_migrations.exs
@@ -0,0 +1,17 @@
+defmodule Pleroma.Repo.Migrations.CreateDataMigrations do
+  use Ecto.Migration
+
+  def change do
+    create_if_not_exists table(:data_migrations) do
+      add(:name, :string, null: false)
+      add(:state, :integer, default: 1)
+      add(:feature_lock, :boolean, default: false)
+      add(:params, :map, default: %{})
+      add(:data, :map, default: %{})
+
+      timestamps()
+    end
+
+    create_if_not_exists(unique_index(:data_migrations, [:name]))
+  end
+end
diff --git a/priv/repo/migrations/20210106183301_data_migration_create_populate_hashtags_table.exs b/priv/repo/migrations/20210106183301_data_migration_create_populate_hashtags_table.exs
new file mode 100644
index 000000000..2a965f075
--- /dev/null
+++ b/priv/repo/migrations/20210106183301_data_migration_create_populate_hashtags_table.exs
@@ -0,0 +1,14 @@
+defmodule Pleroma.Repo.Migrations.DataMigrationCreatePopulateHashtagsTable do
+  use Ecto.Migration
+
+  def up do
+    dt = NaiveDateTime.utc_now()
+
+    execute(
+      "INSERT INTO data_migrations(name, inserted_at, updated_at) " <>
+        "VALUES ('populate_hashtags_table', '#{dt}', '#{dt}') ON CONFLICT DO NOTHING;"
+    )
+  end
+
+  def down, do: :ok
+end
diff --git a/priv/repo/migrations/20210111172254_create_data_migration_failed_ids.exs b/priv/repo/migrations/20210111172254_create_data_migration_failed_ids.exs
new file mode 100644
index 000000000..ba0be98af
--- /dev/null
+++ b/priv/repo/migrations/20210111172254_create_data_migration_failed_ids.exs
@@ -0,0 +1,14 @@
+defmodule Pleroma.Repo.Migrations.CreateDataMigrationFailedIds do
+  use Ecto.Migration
+
+  def change do
+    create_if_not_exists table(:data_migration_failed_ids, primary_key: false) do
+      add(:data_migration_id, references(:data_migrations), null: false)
+      add(:record_id, :bigint, null: false)
+    end
+
+    create_if_not_exists(
+      unique_index(:data_migration_failed_ids, [:data_migration_id, :record_id])
+    )
+  end
+end

From f5f267fa764f53ef617bc9504c7ecb68b5d3d7ab Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Thu, 14 Jan 2021 22:41:27 +0300
Subject: [PATCH 022/339] [#3213] Refactoring of HashtagsTableMigrator.

---
 .../migrators/hashtags_table_migrator.ex      | 98 ++++++++++---------
 .../hashtags_table_migrator/state.ex          | 26 +++++
 .../20190711042021_create_safe_jsonb_set.exs  |  2 +-
 3 files changed, 79 insertions(+), 47 deletions(-)
 create mode 100644 lib/pleroma/migrators/hashtags_table_migrator/state.ex

diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index a7e3de542..9f1a00f9c 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -3,39 +3,13 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Migrators.HashtagsTableMigrator do
-  defmodule State do
-    use Agent
-
-    @init_state %{}
-
-    def start_link(_) do
-      Agent.start_link(fn -> @init_state end, name: __MODULE__)
-    end
-
-    def get do
-      Agent.get(__MODULE__, & &1)
-    end
-
-    def put(key, value) do
-      Agent.update(__MODULE__, fn state ->
-        Map.put(state, key, value)
-      end)
-    end
-
-    def increment(key, increment \\ 1) do
-      Agent.update(__MODULE__, fn state ->
-        updated_value = (state[key] || 0) + increment
-        Map.put(state, key, updated_value)
-      end)
-    end
-  end
-
   use GenServer
 
   require Logger
 
   import Ecto.Query
 
+  alias __MODULE__.State
   alias Pleroma.Config
   alias Pleroma.DataMigration
   alias Pleroma.Hashtag
@@ -43,13 +17,23 @@ def increment(key, increment \\ 1) do
   alias Pleroma.Repo
 
   defdelegate state(), to: State, as: :get
-  defdelegate put_state(key, value), to: State, as: :put
-  defdelegate increment_state(key, increment), to: State, as: :increment
+  defdelegate put_stat(key, value), to: State, as: :put
+  defdelegate increment_stat(key, increment), to: State, as: :increment
 
   defdelegate data_migration(), to: DataMigration, as: :populate_hashtags_table
 
+  @reg_name {:global, __MODULE__}
+
+  def whereis, do: GenServer.whereis(@reg_name)
+
   def start_link(_) do
-    GenServer.start_link(__MODULE__, nil, name: __MODULE__)
+    case whereis() do
+      nil ->
+        GenServer.start_link(__MODULE__, nil, name: @reg_name)
+
+      pid ->
+        {:ok, pid}
+    end
   end
 
   @impl true
@@ -61,21 +45,22 @@ def init(_) do
   def handle_continue(:init_state, _state) do
     {:ok, _} = State.start_link(nil)
 
-    put_state(:status, :init)
+    put_stat(:status, :init)
 
     dm = data_migration()
+    manual_migrations = Config.get([:instance, :manual_data_migrations], [])
 
     cond do
       Config.get(:env) == :test ->
-        put_state(:status, :noop)
+        put_stat(:status, :noop)
 
       is_nil(dm) ->
-        put_state(:status, :halt)
-        put_state(:message, "Data migration does not exist.")
+        put_stat(:status, :halt)
+        put_stat(:message, "Data migration does not exist.")
 
-      dm.state == :manual ->
-        put_state(:status, :noop)
-        put_state(:message, "Data migration is in manual execution state.")
+      dm.state == :manual or dm.name in manual_migrations ->
+        put_stat(:status, :noop)
+        put_stat(:message, "Data migration is in manual execution state.")
 
       dm.state == :complete ->
         handle_success()
@@ -91,8 +76,12 @@ def handle_continue(:init_state, _state) do
   def handle_info(:migrate_hashtags, state) do
     data_migration = data_migration()
 
-    {:ok, data_migration} = DataMigration.update_state(data_migration, :running)
-    put_state(:status, :running)
+    persistent_data = Map.take(data_migration.data, ["max_processed_id"])
+
+    {:ok, data_migration} =
+      DataMigration.update(data_migration, %{state: :running, data: persistent_data})
+
+    put_stat(:status, :running)
 
     Logger.info("Starting transferring object embedded hashtags to `hashtags` table...")
 
@@ -137,10 +126,12 @@ def handle_info(:migrate_hashtags, state) do
         )
 
       max_object_id = Enum.at(object_ids, -1)
-      _ = DataMigration.update(data_migration, %{data: %{"max_processed_id" => max_object_id}})
 
-      increment_state(:processed_count, length(object_ids))
-      increment_state(:failed_count, length(failed_ids))
+      put_stat(:max_processed_id, max_object_id)
+      increment_stat(:processed_count, length(object_ids))
+      increment_stat(:failed_count, length(failed_ids))
+
+      persist_stats(data_migration)
 
       # A quick and dirty approach to controlling the load this background migration imposes
       sleep_interval = Config.get([:populate_hashtags_table, :sleep_interval_ms], 0)
@@ -153,14 +144,15 @@ def handle_info(:migrate_hashtags, state) do
              "SELECT COUNT(record_id) FROM data_migration_failed_ids WHERE data_migration_id = $1;",
              [data_migration.id]
            ) do
-      put_state(:status, :complete)
       _ = DataMigration.update_state(data_migration, :complete)
 
       handle_success()
     else
       _ ->
-        put_state(:status, :failed)
-        put_state(:message, "Please check data_migration_failed_ids records.")
+        _ = DataMigration.update_state(data_migration, :failed)
+
+        put_stat(:status, :failed)
+        put_stat(:message, "Please check data_migration_failed_ids records.")
     end
 
     {:noreply, state}
@@ -199,8 +191,13 @@ defp transfer_object_hashtags(object) do
     end)
   end
 
+  defp persist_stats(data_migration) do
+    runner_state = Map.drop(state(), [:status])
+    _ = DataMigration.update(data_migration, %{data: runner_state})
+  end
+
   defp handle_success do
-    put_state(:status, :complete)
+    put_stat(:status, :complete)
 
     unless Config.improved_hashtag_timeline() do
       Config.put(Config.improved_hashtag_timeline_path(), true)
@@ -208,4 +205,13 @@ defp handle_success do
 
     :ok
   end
+
+  def force_continue do
+    send(whereis(), :migrate_hashtags)
+  end
+
+  def force_restart do
+    {:ok, _} = DataMigration.update(data_migration(), %{state: :pending, data: %{}})
+    force_continue()
+  end
 end
diff --git a/lib/pleroma/migrators/hashtags_table_migrator/state.ex b/lib/pleroma/migrators/hashtags_table_migrator/state.ex
new file mode 100644
index 000000000..79926892c
--- /dev/null
+++ b/lib/pleroma/migrators/hashtags_table_migrator/state.ex
@@ -0,0 +1,26 @@
+defmodule Pleroma.Migrators.HashtagsTableMigrator.State do
+  use Agent
+
+  @init_state %{}
+
+  def start_link(_) do
+    Agent.start_link(fn -> @init_state end, name: __MODULE__)
+  end
+
+  def get do
+    Agent.get(__MODULE__, & &1)
+  end
+
+  def put(key, value) do
+    Agent.update(__MODULE__, fn state ->
+      Map.put(state, key, value)
+    end)
+  end
+
+  def increment(key, increment \\ 1) do
+    Agent.update(__MODULE__, fn state ->
+      updated_value = (state[key] || 0) + increment
+      Map.put(state, key, updated_value)
+    end)
+  end
+end
diff --git a/priv/repo/migrations/20190711042021_create_safe_jsonb_set.exs b/priv/repo/migrations/20190711042021_create_safe_jsonb_set.exs
index 43d616705..bfac09f9e 100644
--- a/priv/repo/migrations/20190711042021_create_safe_jsonb_set.exs
+++ b/priv/repo/migrations/20190711042021_create_safe_jsonb_set.exs
@@ -9,7 +9,7 @@ def change do
     begin
       result := jsonb_set(target, path, coalesce(new_value, 'null'::jsonb), create_missing);
       if result is NULL then
-        raise 'jsonb_set tried to wipe the object, please report this incindent to Pleroma bug tracker. https://git.pleroma.social/pleroma/pleroma/issues/new';
+        raise 'jsonb_set tried to wipe the object, please report this incident to Pleroma bug tracker. https://git.pleroma.social/pleroma/pleroma/issues/new';
         return target;
       else
         return result;

From 48b399cedb7d46ea0f08181cfbe4df222861f65b Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Sat, 16 Jan 2021 20:22:14 +0300
Subject: [PATCH 023/339] [#3213] Refactoring of HashtagsTableMigrator. Hashtag
 timeline performance optimization (auto switch to non-aggregate join strategy
 when efficient).

---
 CHANGELOG.md                                  |  1 +
 config/description.exs                        |  6 ++
 .../migrators/hashtags_table_migrator.ex      | 47 +++++++-----
 .../hashtags_table_migrator/state.ex          |  9 +--
 lib/pleroma/web/activity_pub/activity_pub.ex  | 72 +++++++++++--------
 .../web/activity_pub/activity_pub_test.exs    |  4 +-
 6 files changed, 86 insertions(+), 53 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 25b24bf07..9a053156f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Search: When using Postgres 11+, Pleroma will use the `websearch_to_tsvector` function to parse search queries.
 - Emoji: Support the full Unicode 13.1 set of Emoji for reactions, plus regional indicators.
 - Admin API: Reports now ordered by newest
+- Extracted object hashtags into separate table in order to improve hashtag timeline performance (via background migration in `Pleroma.Migrators.HashtagsTableMigrator`). 
 
 ### Added
 
diff --git a/config/description.exs b/config/description.exs
index f438a88ab..c73d50f7d 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -941,6 +941,12 @@
         key: :show_reactions,
         type: :boolean,
         description: "Let favourites and emoji reactions be viewed through the API."
+      },
+      %{
+        key: :improved_hashtag_timeline,
+        type: :keyword,
+        description:
+          "If `true` / `:prefer_aggregation` / `:avoid_aggregation`, hashtags table and selected strategy will be used for hashtags timeline. When `false`, object-embedded hashtags will be used (slower). Is auto-set to `true` (unless overridden) when HashtagsTableMigrator completes."
       }
     ]
   },
diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index 9f1a00f9c..b40578d50 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -45,25 +45,23 @@ def init(_) do
   def handle_continue(:init_state, _state) do
     {:ok, _} = State.start_link(nil)
 
-    put_stat(:status, :init)
+    update_status(:init)
 
-    dm = data_migration()
+    data_migration = data_migration()
     manual_migrations = Config.get([:instance, :manual_data_migrations], [])
 
     cond do
       Config.get(:env) == :test ->
-        put_stat(:status, :noop)
+        update_status(:noop)
 
-      is_nil(dm) ->
-        put_stat(:status, :halt)
-        put_stat(:message, "Data migration does not exist.")
+      is_nil(data_migration) ->
+        update_status(:halt, "Data migration does not exist.")
 
-      dm.state == :manual or dm.name in manual_migrations ->
-        put_stat(:status, :noop)
-        put_stat(:message, "Data migration is in manual execution state.")
+      data_migration.state == :manual or data_migration.name in manual_migrations ->
+        update_status(:noop, "Data migration is in manual execution state.")
 
-      dm.state == :complete ->
-        handle_success()
+      data_migration.state == :complete ->
+        handle_success(data_migration)
 
       true ->
         send(self(), :migrate_hashtags)
@@ -81,7 +79,7 @@ def handle_info(:migrate_hashtags, state) do
     {:ok, data_migration} =
       DataMigration.update(data_migration, %{state: :running, data: persistent_data})
 
-    put_stat(:status, :running)
+    update_status(:running)
 
     Logger.info("Starting transferring object embedded hashtags to `hashtags` table...")
 
@@ -146,13 +144,12 @@ def handle_info(:migrate_hashtags, state) do
            ) do
       _ = DataMigration.update_state(data_migration, :complete)
 
-      handle_success()
+      handle_success(data_migration)
     else
       _ ->
         _ = DataMigration.update_state(data_migration, :failed)
 
-        put_stat(:status, :failed)
-        put_stat(:message, "Please check data_migration_failed_ids records.")
+        update_status(:failed, "Please check data_migration_failed_ids records.")
     end
 
     {:noreply, state}
@@ -196,16 +193,25 @@ defp persist_stats(data_migration) do
     _ = DataMigration.update(data_migration, %{data: runner_state})
   end
 
-  defp handle_success do
-    put_stat(:status, :complete)
+  defp handle_success(data_migration) do
+    update_status(:complete)
 
-    unless Config.improved_hashtag_timeline() do
+    unless data_migration.feature_lock || Config.improved_hashtag_timeline() do
       Config.put(Config.improved_hashtag_timeline_path(), true)
     end
 
     :ok
   end
 
+  def failed_objects_query do
+    from(o in Object)
+    |> join(:inner, [o], dmf in fragment("SELECT * FROM data_migration_failed_ids"),
+      on: dmf.record_id == o.id
+    )
+    |> where([_o, dmf], dmf.data_migration_id == ^data_migration().id)
+    |> order_by([o], asc: o.id)
+  end
+
   def force_continue do
     send(whereis(), :migrate_hashtags)
   end
@@ -214,4 +220,9 @@ def force_restart do
     {:ok, _} = DataMigration.update(data_migration(), %{state: :pending, data: %{}})
     force_continue()
   end
+
+  defp update_status(status, message \\ nil) do
+    put_stat(:status, status)
+    put_stat(:message, message)
+  end
 end
diff --git a/lib/pleroma/migrators/hashtags_table_migrator/state.ex b/lib/pleroma/migrators/hashtags_table_migrator/state.ex
index 79926892c..c1a2709fc 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator/state.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator/state.ex
@@ -2,23 +2,24 @@ defmodule Pleroma.Migrators.HashtagsTableMigrator.State do
   use Agent
 
   @init_state %{}
+  @reg_name {:global, __MODULE__}
 
   def start_link(_) do
-    Agent.start_link(fn -> @init_state end, name: __MODULE__)
+    Agent.start_link(fn -> @init_state end, name: @reg_name)
   end
 
   def get do
-    Agent.get(__MODULE__, & &1)
+    Agent.get(@reg_name, & &1)
   end
 
   def put(key, value) do
-    Agent.update(__MODULE__, fn state ->
+    Agent.update(@reg_name, fn state ->
       Map.put(state, key, value)
     end)
   end
 
   def increment(key, increment \\ 1) do
-    Agent.update(__MODULE__, fn state ->
+    Agent.update(@reg_name, fn state ->
       updated_value = (state[key] || 0) + increment
       Map.put(state, key, updated_value)
     end)
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index f5563b0fd..0609827ec 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -669,63 +669,66 @@ defp restrict_since(query, %{since_id: since_id}) do
 
   defp restrict_since(query, _), do: query
 
-  defp restrict_tag_reject(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
+  defp restrict_embedded_tag_reject(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
     raise_on_missing_preload()
   end
 
-  defp restrict_tag_reject(query, %{tag_reject: tag_reject}) when is_list(tag_reject) do
+  defp restrict_embedded_tag_reject(query, %{tag_reject: tag_reject}) when is_list(tag_reject) do
     from(
       [_activity, object] in query,
       where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
     )
   end
 
-  defp restrict_tag_reject(query, %{tag_reject: tag_reject}) when is_binary(tag_reject) do
-    restrict_tag_reject(query, %{tag_reject: [tag_reject]})
+  defp restrict_embedded_tag_reject(query, %{tag_reject: tag_reject})
+       when is_binary(tag_reject) do
+    restrict_embedded_tag_reject(query, %{tag_reject: [tag_reject]})
   end
 
-  defp restrict_tag_reject(query, _), do: query
+  defp restrict_embedded_tag_reject(query, _), do: query
 
-  defp restrict_tag_all(_query, %{tag_all: _tag_all, skip_preload: true}) do
+  defp restrict_embedded_tag_all(_query, %{tag_all: _tag_all, skip_preload: true}) do
     raise_on_missing_preload()
   end
 
-  defp restrict_tag_all(query, %{tag_all: tag_all}) when is_list(tag_all) do
+  defp restrict_embedded_tag_all(query, %{tag_all: tag_all}) when is_list(tag_all) do
     from(
       [_activity, object] in query,
       where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)
     )
   end
 
-  defp restrict_tag_all(query, %{tag_all: tag}) when is_binary(tag) do
-    restrict_tag(query, %{tag: tag})
+  defp restrict_embedded_tag_all(query, %{tag_all: tag}) when is_binary(tag) do
+    restrict_embedded_tag(query, %{tag: tag})
   end
 
-  defp restrict_tag_all(query, _), do: query
+  defp restrict_embedded_tag_all(query, _), do: query
 
-  defp restrict_tag(_query, %{tag: _tag, skip_preload: true}) do
+  defp restrict_embedded_tag(_query, %{tag: _tag, skip_preload: true}) do
     raise_on_missing_preload()
   end
 
-  defp restrict_tag(query, %{tag: tag}) when is_list(tag) do
+  defp restrict_embedded_tag(query, %{tag: tag}) when is_list(tag) do
     from(
       [_activity, object] in query,
       where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag)
     )
   end
 
-  defp restrict_tag(query, %{tag: tag}) when is_binary(tag) do
-    restrict_tag(query, %{tag: [tag]})
+  defp restrict_embedded_tag(query, %{tag: tag}) when is_binary(tag) do
+    restrict_embedded_tag(query, %{tag: [tag]})
   end
 
-  defp restrict_tag(query, _), do: query
+  defp restrict_embedded_tag(query, _), do: query
 
-  defp restrict_hashtag(query, opts) do
-    [tag_any, tag_all, tag_reject] =
-      [:tag, :tag_all, :tag_reject]
-      |> Enum.map(&opts[&1])
-      |> Enum.map(&List.wrap(&1))
+  defp hashtag_conditions(opts) do
+    [:tag, :tag_all, :tag_reject]
+    |> Enum.map(&opts[&1])
+    |> Enum.map(&List.wrap(&1))
+  end
 
+  defp restrict_hashtag_agg(query, opts) do
+    [tag_any, tag_all, tag_reject] = hashtag_conditions(opts)
     has_conditions = Enum.any?([tag_any, tag_all, tag_reject], &Enum.any?(&1))
 
     cond do
@@ -1275,15 +1278,19 @@ def fetch_activities_query(recipients, opts \\ %{}) do
       |> exclude_invisible_actors(opts)
       |> exclude_visibility(opts)
 
-    cond do
-      Config.object_embedded_hashtags?() ->
-        query
-        |> restrict_tag(opts)
-        |> restrict_tag_reject(opts)
-        |> restrict_tag_all(opts)
+    hashtag_timeline_strategy = Config.improved_hashtag_timeline()
 
-      # TODO: benchmark (initial approach preferring non-aggregate ops when possible)
-      Config.improved_hashtag_timeline() == :join ->
+    cond do
+      !hashtag_timeline_strategy ->
+        query
+        |> restrict_embedded_tag(opts)
+        |> restrict_embedded_tag_reject(opts)
+        |> restrict_embedded_tag_all(opts)
+
+      hashtag_timeline_strategy == :prefer_aggregation ->
+        restrict_hashtag_agg(query, opts)
+
+      hashtag_timeline_strategy == :avoid_aggregation or avoid_hashtags_aggregation?(opts) ->
         query
         |> distinct([activity], true)
         |> restrict_hashtag_any(opts)
@@ -1291,10 +1298,17 @@ def fetch_activities_query(recipients, opts \\ %{}) do
         |> restrict_hashtag_reject_any(opts)
 
       true ->
-        restrict_hashtag(query, opts)
+        restrict_hashtag_agg(query, opts)
     end
   end
 
+  defp avoid_hashtags_aggregation?(opts) do
+    [tag_any, tag_all, tag_reject] = hashtag_conditions(opts)
+
+    joins_count = length(tag_all) + if Enum.any?(tag_any), do: 1, else: 0
+    Enum.empty?(tag_reject) and joins_count <= 2
+  end
+
   def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do
     list_memberships = Pleroma.List.memberships(opts[:user])
 
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index f86d0a265..36fd65c76 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -217,8 +217,8 @@ test "it fetches the appropriate tag-restricted posts" do
     {:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"})
     {:ok, status_three} = CommonAPI.post(user, %{status: ". #test #reject"})
 
-    for new_timeline_enabled <- [true, false] do
-      clear_config([:instance, :improved_hashtag_timeline], new_timeline_enabled)
+    for hashtag_timeline_strategy <- [true, :prefer_aggregation, :avoid_aggregation, false] do
+      clear_config([:instance, :improved_hashtag_timeline], hashtag_timeline_strategy)
 
       fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})
 

From 85f7ef4d13adea9d64d279d1395d17c6ebc20678 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Sun, 17 Jan 2021 10:57:06 +0300
Subject: [PATCH 024/339] [#3213] Feature lock adjustment for
 HashtagsTableMigrator.

---
 lib/pleroma/migrators/hashtags_table_migrator.ex | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index b40578d50..47de5e134 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -196,11 +196,17 @@ defp persist_stats(data_migration) do
   defp handle_success(data_migration) do
     update_status(:complete)
 
-    unless data_migration.feature_lock || Config.improved_hashtag_timeline() do
-      Config.put(Config.improved_hashtag_timeline_path(), true)
-    end
+    cond do
+      data_migration.feature_lock ->
+        :noop
 
-    :ok
+      not is_nil(Config.improved_hashtag_timeline()) ->
+        :noop
+
+      true ->
+        Config.put(Config.improved_hashtag_timeline_path(), true)
+        :ok
+    end
   end
 
   def failed_objects_query do

From 9d28a7ebfbc7bd8fb893cf1e2ad555ed71f4c812 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Sun, 17 Jan 2021 21:58:15 +0300
Subject: [PATCH 025/339] [#3213] Missing copyright header for
 HashtagsTableMigrator.State.

---
 lib/pleroma/migrators/hashtags_table_migrator/state.ex | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lib/pleroma/migrators/hashtags_table_migrator/state.ex b/lib/pleroma/migrators/hashtags_table_migrator/state.ex
index c1a2709fc..43f7270e2 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator/state.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator/state.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
 defmodule Pleroma.Migrators.HashtagsTableMigrator.State do
   use Agent
 

From 7f07909a7b56eb368b3f8aab4752def1551c12fe Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Tue, 19 Jan 2021 21:13:32 +0300
Subject: [PATCH 026/339] [#3213] Added `HashtagsTableMigrator.count/1`.

---
 .../migrators/hashtags_table_migrator.ex      | 42 +++++++++++++------
 1 file changed, 29 insertions(+), 13 deletions(-)

diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index 47de5e134..048f3c8ee 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -85,19 +85,8 @@ def handle_info(:migrate_hashtags, state) do
 
     max_processed_id = data_migration.data["max_processed_id"] || 0
 
-    # Note: most objects have Mention-type AS2 tags and no hashtags (but we can't filter them out)
-    from(
-      object in Object,
-      left_join: hashtag in assoc(object, :hashtags),
-      where: object.id > ^max_processed_id,
-      where: is_nil(hashtag.id),
-      where:
-        fragment("(?)->'tag' IS NOT NULL AND (?)->'tag' != '[]'::jsonb", object.data, object.data),
-      select: %{
-        id: object.id,
-        tag: fragment("(?)->'tag'", object.data)
-      }
-    )
+    query()
+    |> where([object], object.id > ^max_processed_id)
     |> Repo.chunk_stream(100, :batches, timeout: :infinity)
     |> Stream.each(fn objects ->
       object_ids = Enum.map(objects, & &1.id)
@@ -155,6 +144,21 @@ def handle_info(:migrate_hashtags, state) do
     {:noreply, state}
   end
 
+  defp query do
+    # Note: most objects have Mention-type AS2 tags and no hashtags (but we can't filter them out)
+    from(
+      object in Object,
+      left_join: hashtag in assoc(object, :hashtags),
+      where: is_nil(hashtag.id),
+      where:
+        fragment("(?)->'tag' IS NOT NULL AND (?)->'tag' != '[]'::jsonb", object.data, object.data),
+      select: %{
+        id: object.id,
+        tag: fragment("(?)->'tag'", object.data)
+      }
+    )
+  end
+
   defp transfer_object_hashtags(object) do
     hashtags = Object.object_data_hashtags(%{"tag" => object.tag})
 
@@ -188,6 +192,18 @@ defp transfer_object_hashtags(object) do
     end)
   end
 
+  def count(force \\ false) do
+    stored_count = state()[:count]
+
+    if stored_count && !force do
+      stored_count
+    else
+      count = Repo.aggregate(query(), :count, :id)
+      put_stat(:count, count)
+      count
+    end
+  end
+
   defp persist_stats(data_migration) do
     runner_state = Map.drop(state(), [:status])
     _ = DataMigration.update(data_migration, %{data: runner_state})

From f0f0f2af00e8b73a7013c1308289795961b23f4b Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Tue, 19 Jan 2021 21:17:06 +0300
Subject: [PATCH 027/339] [#3213] `timeout` option for
 `HashtagsTableMigrator.count/_`.

---
 lib/pleroma/migrators/hashtags_table_migrator.ex | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index 048f3c8ee..6a6a7b5b8 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -192,13 +192,13 @@ defp transfer_object_hashtags(object) do
     end)
   end
 
-  def count(force \\ false) do
+  def count(force \\ false, timeout \\ :infinity) do
     stored_count = state()[:count]
 
     if stored_count && !force do
       stored_count
     else
-      count = Repo.aggregate(query(), :count, :id)
+      count = Repo.aggregate(query(), :count, :id, timeout: timeout)
       put_stat(:count, count)
       count
     end

From b830605577f369d6b1a8730a5b3476ceea4fef5a Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Tue, 19 Jan 2021 22:03:25 +0300
Subject: [PATCH 028/339] [#3213] Performance-related stat in
 HashtagsTableMigrator. Reworked `count/_` to indicate approximate total count
 for current iteration.

---
 lib/pleroma/migrators/hashtags_table_migrator.ex | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index 6a6a7b5b8..e9dd9b70c 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -80,6 +80,7 @@ def handle_info(:migrate_hashtags, state) do
       DataMigration.update(data_migration, %{state: :running, data: persistent_data})
 
     update_status(:running)
+    put_stat(:started_at, NaiveDateTime.utc_now())
 
     Logger.info("Starting transferring object embedded hashtags to `hashtags` table...")
 
@@ -118,6 +119,12 @@ def handle_info(:migrate_hashtags, state) do
       increment_stat(:processed_count, length(object_ids))
       increment_stat(:failed_count, length(failed_ids))
 
+      put_stat(
+        :records_per_second,
+        state()[:processed_count] /
+          Enum.max([NaiveDateTime.diff(NaiveDateTime.utc_now(), state()[:started_at]), 1])
+      )
+
       persist_stats(data_migration)
 
       # A quick and dirty approach to controlling the load this background migration imposes
@@ -192,13 +199,18 @@ defp transfer_object_hashtags(object) do
     end)
   end
 
+  @doc "Approximate count for current iteration (including processed records count)"
   def count(force \\ false, timeout \\ :infinity) do
     stored_count = state()[:count]
 
     if stored_count && !force do
       stored_count
     else
-      count = Repo.aggregate(query(), :count, :id, timeout: timeout)
+      processed_count = state()[:processed_count] || 0
+      max_processed_id = data_migration().data["max_processed_id"] || 0
+      query = where(query(), [object], object.id > ^max_processed_id)
+
+      count = Repo.aggregate(query, :count, :id, timeout: timeout) + processed_count
       put_stat(:count, count)
       count
     end

From c041e9c6300726a40a00146bba04d3ec752219d9 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Thu, 21 Jan 2021 20:19:09 +0300
Subject: [PATCH 029/339] [#3213] HashtagsTableMigrator: failures handling fix,
 retry function. Changed default hashtags filtering strategy to non-aggregate
 approach.

---
 config/description.exs                        |  2 +-
 .../migrators/hashtags_table_migrator.ex      | 52 +++++++++++++++----
 lib/pleroma/web/activity_pub/activity_pub.ex  | 13 +----
 .../web/activity_pub/activity_pub_test.exs    |  2 +-
 4 files changed, 47 insertions(+), 22 deletions(-)

diff --git a/config/description.exs b/config/description.exs
index b48616b22..46f085c70 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -940,7 +940,7 @@
         key: :improved_hashtag_timeline,
         type: :keyword,
         description:
-          "If `true` / `:prefer_aggregation` / `:avoid_aggregation`, hashtags table and selected strategy will be used for hashtags timeline. When `false`, object-embedded hashtags will be used (slower). Is auto-set to `true` (unless overridden) when HashtagsTableMigrator completes."
+          "If `true` / `:prefer_aggregation`, hashtags table and selected strategy will be used for hashtags timeline. When `false`, object-embedded hashtags will be used (slower). Is auto-set to `true` (unless overridden) when HashtagsTableMigrator completes."
       }
     ]
   },
diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index e9dd9b70c..8ad2c8c73 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -109,8 +109,9 @@ def handle_info(:migrate_hashtags, state) do
 
       _ =
         Repo.query(
-          "DELETE FROM data_migration_failed_ids WHERE id = ANY($1)",
-          [object_ids -- failed_ids]
+          "DELETE FROM data_migration_failed_ids " <>
+            "WHERE data_migration_id = $1 AND record_id = ANY($2)",
+          [data_migration.id, object_ids -- failed_ids]
         )
 
       max_object_id = Enum.at(object_ids, -1)
@@ -133,12 +134,8 @@ def handle_info(:migrate_hashtags, state) do
     end)
     |> Stream.run()
 
-    with {:ok, %{rows: [[0]]}} <-
-           Repo.query(
-             "SELECT COUNT(record_id) FROM data_migration_failed_ids WHERE data_migration_id = $1;",
-             [data_migration.id]
-           ) do
-      _ = DataMigration.update_state(data_migration, :complete)
+    with 0 <- failures_count(data_migration.id) do
+      {:ok, data_migration} = DataMigration.update_state(data_migration, :complete)
 
       handle_success(data_migration)
     else
@@ -167,7 +164,8 @@ defp query do
   end
 
   defp transfer_object_hashtags(object) do
-    hashtags = Object.object_data_hashtags(%{"tag" => object.tag})
+    embedded_tags = (Map.has_key?(object, :tag) && object.tag) || object.data["tag"]
+    hashtags = Object.object_data_hashtags(%{"tag" => embedded_tags})
 
     Repo.transaction(fn ->
       with {:ok, hashtag_records} <- Hashtag.get_or_create_by_names(hashtags) do
@@ -246,6 +244,36 @@ def failed_objects_query do
     |> order_by([o], asc: o.id)
   end
 
+  def failures_count(data_migration_id \\ nil) do
+    data_migration_id = data_migration_id || data_migration().id
+
+    with {:ok, %{rows: [[count]]}} <-
+           Repo.query(
+             "SELECT COUNT(record_id) FROM data_migration_failed_ids WHERE data_migration_id = $1;",
+             [data_migration_id]
+           ) do
+      count
+    end
+  end
+
+  def retry_failed do
+    data_migration = data_migration()
+
+    failed_objects_query()
+    |> Repo.chunk_stream(100, :one)
+    |> Stream.each(fn object ->
+      with {:ok, _} <- transfer_object_hashtags(object) do
+        _ =
+          Repo.query(
+            "DELETE FROM data_migration_failed_ids " <>
+              "WHERE data_migration_id = $1 AND record_id = $2",
+            [data_migration.id, object.id]
+          )
+      end
+    end)
+    |> Stream.run()
+  end
+
   def force_continue do
     send(whereis(), :migrate_hashtags)
   end
@@ -255,6 +283,12 @@ def force_restart do
     force_continue()
   end
 
+  def force_complete do
+    {:ok, data_migration} = DataMigration.update_state(data_migration(), :complete)
+
+    handle_success(data_migration)
+  end
+
   defp update_status(status, message \\ nil) do
     put_stat(:status, status)
     put_stat(:message, message)
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 0609827ec..dbfd3839d 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -727,6 +727,7 @@ defp hashtag_conditions(opts) do
     |> Enum.map(&List.wrap(&1))
   end
 
+  # Note: times out on larger instances (with default timeout), intended for complex queries
   defp restrict_hashtag_agg(query, opts) do
     [tag_any, tag_all, tag_reject] = hashtag_conditions(opts)
     has_conditions = Enum.any?([tag_any, tag_all, tag_reject], &Enum.any?(&1))
@@ -1290,25 +1291,15 @@ def fetch_activities_query(recipients, opts \\ %{}) do
       hashtag_timeline_strategy == :prefer_aggregation ->
         restrict_hashtag_agg(query, opts)
 
-      hashtag_timeline_strategy == :avoid_aggregation or avoid_hashtags_aggregation?(opts) ->
+      true ->
         query
         |> distinct([activity], true)
         |> restrict_hashtag_any(opts)
         |> restrict_hashtag_all(opts)
         |> restrict_hashtag_reject_any(opts)
-
-      true ->
-        restrict_hashtag_agg(query, opts)
     end
   end
 
-  defp avoid_hashtags_aggregation?(opts) do
-    [tag_any, tag_all, tag_reject] = hashtag_conditions(opts)
-
-    joins_count = length(tag_all) + if Enum.any?(tag_any), do: 1, else: 0
-    Enum.empty?(tag_reject) and joins_count <= 2
-  end
-
   def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do
     list_memberships = Pleroma.List.memberships(opts[:user])
 
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index 36fd65c76..1fcaf74d3 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -217,7 +217,7 @@ test "it fetches the appropriate tag-restricted posts" do
     {:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"})
     {:ok, status_three} = CommonAPI.post(user, %{status: ". #test #reject"})
 
-    for hashtag_timeline_strategy <- [true, :prefer_aggregation, :avoid_aggregation, false] do
+    for hashtag_timeline_strategy <- [true, :prefer_aggregation, false] do
       clear_config([:instance, :improved_hashtag_timeline], hashtag_timeline_strategy)
 
       fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})

From ca7f24064304945587fc232325dce4b834ff6c94 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Thu, 21 Jan 2021 20:50:06 +0300
Subject: [PATCH 030/339] [#3213] Ignoring of blank elements from
 objects.data->tag.

---
 lib/pleroma/object.ex         |  2 ++
 test/pleroma/hashtag_test.exs | 17 +++++++++++++++++
 2 files changed, 19 insertions(+)
 create mode 100644 test/pleroma/hashtag_test.exs

diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 5102be1de..9b5c1bec1 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -420,6 +420,8 @@ def object_data_hashtags(%{"tag" => tags}) when is_list(tags) do
       hashtag when is_bitstring(hashtag) -> String.downcase(hashtag)
     end)
     |> Enum.uniq()
+    # Note: "" elements (plain text) might occur in `data.tag` for incoming objects
+    |> Enum.filter(&(&1 not in [nil, ""]))
   end
 
   def object_data_hashtags(_), do: []
diff --git a/test/pleroma/hashtag_test.exs b/test/pleroma/hashtag_test.exs
new file mode 100644
index 000000000..0264dea0b
--- /dev/null
+++ b/test/pleroma/hashtag_test.exs
@@ -0,0 +1,17 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HashtagTest do
+  use Pleroma.DataCase
+
+  alias Pleroma.Hashtag
+
+  describe "changeset validations" do
+    test "ensure non-blank :name" do
+      changeset = Hashtag.changeset(%Hashtag{}, %{name: ""})
+
+      assert {:name, {"can't be blank", [validation: :required]}} in changeset.errors
+    end
+  end
+end

From f264d930cc00c463d0f506a94f6f6b494aab7022 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Sun, 24 Jan 2021 23:27:02 +0300
Subject: [PATCH 031/339] [#3213] Speedup of HashtagsTableMigrator (query
 optimization). State handling fix.

---
 .../migrators/hashtags_table_migrator.ex       | 18 +++++++++++++++---
 .../migrators/hashtags_table_migrator/state.ex |  4 ++++
 2 files changed, 19 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index 8ad2c8c73..6a1c9592c 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -72,6 +72,8 @@ def handle_continue(:init_state, _state) do
 
   @impl true
   def handle_info(:migrate_hashtags, state) do
+    State.clear()
+
     data_migration = data_migration()
 
     persistent_data = Map.take(data_migration.data, ["max_processed_id"])
@@ -152,8 +154,6 @@ defp query do
     # Note: most objects have Mention-type AS2 tags and no hashtags (but we can't filter them out)
     from(
       object in Object,
-      left_join: hashtag in assoc(object, :hashtags),
-      where: is_nil(hashtag.id),
       where:
         fragment("(?)->'tag' IS NOT NULL AND (?)->'tag' != '[]'::jsonb", object.data, object.data),
       select: %{
@@ -161,12 +161,24 @@ defp query do
         tag: fragment("(?)->'tag'", object.data)
       }
     )
+    |> join(:left, [o], hashtags_objects in fragment("SELECT object_id FROM hashtags_objects"),
+      on: hashtags_objects.object_id == o.id
+    )
+    |> where([_o, hashtags_objects], is_nil(hashtags_objects.object_id))
   end
 
   defp transfer_object_hashtags(object) do
-    embedded_tags = (Map.has_key?(object, :tag) && object.tag) || object.data["tag"]
+    embedded_tags = if Map.has_key?(object, :tag), do: object.tag, else: object.data["tag"]
     hashtags = Object.object_data_hashtags(%{"tag" => embedded_tags})
 
+    if Enum.any?(hashtags) do
+      transfer_object_hashtags(object, hashtags)
+    else
+      {:ok, object.id}
+    end
+  end
+
+  defp transfer_object_hashtags(object, hashtags) do
     Repo.transaction(fn ->
       with {:ok, hashtag_records} <- Hashtag.get_or_create_by_names(hashtags) do
         for hashtag_record <- hashtag_records do
diff --git a/lib/pleroma/migrators/hashtags_table_migrator/state.ex b/lib/pleroma/migrators/hashtags_table_migrator/state.ex
index 43f7270e2..901563426 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator/state.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator/state.ex
@@ -12,6 +12,10 @@ def start_link(_) do
     Agent.start_link(fn -> @init_state end, name: @reg_name)
   end
 
+  def clear do
+    Agent.update(@reg_name, fn _state -> @init_state end)
+  end
+
   def get do
     Agent.get(@reg_name, & &1)
   end

From ea4785213a449f3bcd68bcb4ecb3bb6d794736b1 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Mon, 25 Jan 2021 20:12:09 +0300
Subject: [PATCH 032/339] [#3213] Switched to using embedded hashtags in
 Object.hashtags/1 (to avoid extra joins / preload in timeline queries).

---
 lib/pleroma/config.ex |  1 -
 lib/pleroma/object.ex | 18 +++++-------------
 2 files changed, 5 insertions(+), 14 deletions(-)

diff --git a/lib/pleroma/config.ex b/lib/pleroma/config.ex
index ceb8c8b5a..0a6ac0ad0 100644
--- a/lib/pleroma/config.ex
+++ b/lib/pleroma/config.ex
@@ -98,7 +98,6 @@ def restrict_unauthenticated_access?(resource, kind) do
 
   def improved_hashtag_timeline_path, do: [:instance, :improved_hashtag_timeline]
   def improved_hashtag_timeline, do: get(improved_hashtag_timeline_path())
-  def object_embedded_hashtags?, do: !improved_hashtag_timeline()
 
   def oauth_consumer_strategies, do: get([:auth, :oauth_consumer_strategies], [])
 
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 9b5c1bec1..9edf43e04 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -388,24 +388,16 @@ def tags(%Object{data: %{"tag" => tags}}) when is_list(tags), do: tags
   def tags(_), do: []
 
   def hashtags(%Object{} = object) do
-    cond do
-      Config.object_embedded_hashtags?() ->
-        embedded_hashtags(object)
-
-      object.id == "pleroma:fake_object_id" ->
-        []
-
-      true ->
-        hashtag_records = Repo.preload(object, :hashtags).hashtags
-        Enum.map(hashtag_records, & &1.name)
-    end
+    # Note: always using embedded hashtags regardless whether they are migrated to hashtags table
+    #   (embedded hashtags stay in sync anyways, and we avoid extra joins and preload hassle)
+    embedded_hashtags(object)
   end
 
-  defp embedded_hashtags(%Object{data: data}) do
+  def embedded_hashtags(%Object{data: data}) do
     object_data_hashtags(data)
   end
 
-  defp embedded_hashtags(_), do: []
+  def embedded_hashtags(_), do: []
 
   def object_data_hashtags(%{"tag" => tags}) when is_list(tags) do
     tags

From e7864a32d7c9930e5f6c62bd77cef64c68f1eb21 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Mon, 25 Jan 2021 22:31:23 +0300
Subject: [PATCH 033/339] [#3213] Removed DISTINCT clause from
 ActivityPub.fetch_activities_query/2.

---
 lib/pleroma/web/activity_pub/activity_pub.ex | 1 -
 1 file changed, 1 deletion(-)

diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index fbda89a25..be81e0833 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1293,7 +1293,6 @@ def fetch_activities_query(recipients, opts \\ %{}) do
 
       true ->
         query
-        |> distinct([activity], true)
         |> restrict_hashtag_any(opts)
         |> restrict_hashtag_all(opts)
         |> restrict_hashtag_reject_any(opts)

From 380d0cce6b802baf4d13031a4a39f169dd65bffd Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Fri, 29 Jan 2021 00:17:33 +0300
Subject: [PATCH 034/339] [#3213] Reinstated DISTINCT clause for hashtag "any"
 filtering with 2+ terms. Added test.

---
 lib/pleroma/web/activity_pub/activity_pub.ex    | 17 ++++++++++++-----
 .../controllers/timeline_controller.ex          |  2 +-
 .../web/activity_pub/activity_pub_test.exs      | 17 +++++++++++++++--
 3 files changed, 28 insertions(+), 8 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index be81e0833..0a21ac2f2 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -846,11 +846,18 @@ defp restrict_hashtag_any(_query, %{tag: _tag, skip_preload: true}) do
   end
 
   defp restrict_hashtag_any(query, %{tag: tags}) when is_list(tags) do
-    from(
-      [_activity, object] in query,
-      join: hashtag in assoc(object, :hashtags),
-      where: hashtag.name in ^tags
-    )
+    query =
+      from(
+        [_activity, object] in query,
+        join: hashtag in assoc(object, :hashtags),
+        where: hashtag.name in ^tags
+      )
+
+    if length(tags) > 1 do
+      distinct(query, [activity], true)
+    else
+      query
+    end
   end
 
   defp restrict_hashtag_any(query, %{tag: tag}) when is_binary(tag) do
diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
index 08e6f23b9..1fb954a9b 100644
--- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
@@ -134,9 +134,9 @@ defp hashtag_fetching(params, user, local_only) do
     tags =
       [params[:tag], params[:any]]
       |> List.flatten()
-      |> Enum.uniq()
       |> Enum.reject(&is_nil/1)
       |> Enum.map(&String.downcase/1)
+      |> Enum.uniq()
 
     tag_all =
       params
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index 1fcaf74d3..0b18269cd 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -217,6 +217,9 @@ test "it fetches the appropriate tag-restricted posts" do
     {:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"})
     {:ok, status_three} = CommonAPI.post(user, %{status: ". #test #reject"})
 
+    {:ok, status_four} = CommonAPI.post(user, %{status: ". #any1 #any2"})
+    {:ok, status_five} = CommonAPI.post(user, %{status: ". #any2 #any1"})
+
     for hashtag_timeline_strategy <- [true, :prefer_aggregation, false] do
       clear_config([:instance, :improved_hashtag_timeline], hashtag_timeline_strategy)
 
@@ -238,8 +241,17 @@ test "it fetches the appropriate tag-restricted posts" do
           tag_all: ["test", "reject"]
         })
 
-      [fetch_one, fetch_two, fetch_three, fetch_four] =
-        Enum.map([fetch_one, fetch_two, fetch_three, fetch_four], fn statuses ->
+      # This test would fail if JOIN with 2+ terms in "any" clause is done without DISTINCT.
+      # The :limit is important (w/o DISTINCT 2 records are deduped by Ecto to 1 b/c of preload).
+      fetch_five =
+        ActivityPub.fetch_activities([], %{
+          type: "Create",
+          tag: ["any1", "any2"],
+          limit: 2
+        })
+
+      [fetch_one, fetch_two, fetch_three, fetch_four, fetch_five] =
+        Enum.map([fetch_one, fetch_two, fetch_three, fetch_four, fetch_five], fn statuses ->
           Enum.map(statuses, fn s -> Repo.preload(s, object: :hashtags) end)
         end)
 
@@ -247,6 +259,7 @@ test "it fetches the appropriate tag-restricted posts" do
       assert fetch_two == [status_one, status_two, status_three]
       assert fetch_three == [status_one, status_two]
       assert fetch_four == [status_three]
+      assert fetch_five == [status_four, status_five]
     end
   end
 

From 9948ff3356f9e9e214584207a53eba614c73383c Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Sun, 31 Jan 2021 18:24:19 +0300
Subject: [PATCH 035/339] [#3213] Added HashtagsCleanupWorker periodic job.

---
 config/config.exs                             |  2 +
 config/description.exs                        |  1 +
 .../migrators/hashtags_table_migrator.ex      |  1 +
 lib/pleroma/object.ex                         |  1 +
 .../workers/cron/hashtags_cleanup_worker.ex   | 57 +++++++++++++++++++
 5 files changed, 62 insertions(+)
 create mode 100644 lib/pleroma/workers/cron/hashtags_cleanup_worker.ex

diff --git a/config/config.exs b/config/config.exs
index c4a690799..dfd2fc434 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -553,10 +553,12 @@
     remote_fetcher: 2,
     attachments_cleanup: 1,
     new_users_digest: 1,
+    hashtags_cleanup: 1,
     mute_expire: 5
   ],
   plugins: [Oban.Plugins.Pruner],
   crontab: [
+    {"0 1 * * *", Pleroma.Workers.Cron.HashtagsCleanupWorker},
     {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
     {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
   ]
diff --git a/config/description.exs b/config/description.exs
index 46f085c70..147c1930c 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -1943,6 +1943,7 @@
         type: {:list, :tuple},
         description: "Settings for cron background jobs",
         suggestions: [
+          {"0 1 * * *", Pleroma.Workers.Cron.HashtagsCleanupWorker},
           {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
           {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
         ]
diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index 6a1c9592c..07b42a7f4 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -152,6 +152,7 @@ def handle_info(:migrate_hashtags, state) do
 
   defp query do
     # Note: most objects have Mention-type AS2 tags and no hashtags (but we can't filter them out)
+    # Note: not checking activity type; HashtagsCleanupWorker should clean up unused records later
     from(
       object in Object,
       where:
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 9edf43e04..52b77e41c 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -65,6 +65,7 @@ def change(struct, params \\ %{}) do
     |> maybe_handle_hashtags_change(struct)
   end
 
+  # Note: not checking activity type; HashtagsCleanupWorker should clean up unused records later
   defp maybe_handle_hashtags_change(changeset, struct) do
     with data_hashtags_change = get_change(changeset, :data),
          true <- hashtags_changed?(struct, data_hashtags_change),
diff --git a/lib/pleroma/workers/cron/hashtags_cleanup_worker.ex b/lib/pleroma/workers/cron/hashtags_cleanup_worker.ex
new file mode 100644
index 000000000..b319067ca
--- /dev/null
+++ b/lib/pleroma/workers/cron/hashtags_cleanup_worker.ex
@@ -0,0 +1,57 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.Cron.HashtagsCleanupWorker do
+  @moduledoc """
+  The worker to clean up unused hashtags_objects and hashtags.
+  """
+
+  use Oban.Worker, queue: "hashtags_cleanup"
+
+  alias Pleroma.Repo
+
+  require Logger
+
+  @hashtags_objects_query """
+  DELETE FROM hashtags_objects WHERE object_id IN
+    (SELECT DISTINCT objects.id FROM objects
+      JOIN hashtags_objects ON hashtags_objects.object_id = objects.id LEFT JOIN activities
+        ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') =
+          (objects.data->>'id')
+        AND activities.data->>'type' = 'Create'
+      WHERE activities.id IS NULL);
+  """
+
+  @hashtags_query """
+  DELETE FROM hashtags WHERE id IN
+    (SELECT hashtags.id FROM hashtags
+      LEFT OUTER JOIN hashtags_objects
+        ON hashtags_objects.hashtag_id = hashtags.id
+      WHERE hashtags_objects.hashtag_id IS NULL AND hashtags.inserted_at < $1);
+  """
+
+  @impl Oban.Worker
+  def perform(_job) do
+    Logger.info("Cleaning up unused `hashtags_objects` records...")
+
+    {:ok, %{num_rows: hashtags_objects_count}} =
+      Repo.query(@hashtags_objects_query, [], timeout: :infinity)
+
+    Logger.info("Deleted #{hashtags_objects_count} unused `hashtags_objects` records.")
+
+    Logger.info("Cleaning up unused `hashtags` records...")
+
+    # Note: ignoring recently created hashtags since references are added after hashtag is created
+    {:ok, %{num_rows: hashtags_count}} =
+      Repo.query(@hashtags_query, [NaiveDateTime.add(NaiveDateTime.utc_now(), -3600 * 24)],
+        timeout: :infinity
+      )
+
+    Logger.info("Deleted #{hashtags_count} unused `hashtags` records.")
+
+    Logger.info("HashtagsCleanupWorker complete.")
+
+    :ok
+  end
+end

From 6fd4163ab60be07b1a20ac8911e105ddca8e2095 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Sun, 31 Jan 2021 20:37:33 +0300
Subject: [PATCH 036/339] [#3213] ActivityPub: implemented subqueries-based
 hashtags filtering, removed aggregation-based hashtags filtering.

---
 config/description.exs                        |   2 +-
 lib/pleroma/web/activity_pub/activity_pub.ex  | 228 ++++++------------
 .../web/activity_pub/activity_pub_test.exs    |   5 +-
 3 files changed, 81 insertions(+), 154 deletions(-)

diff --git a/config/description.exs b/config/description.exs
index 147c1930c..ead541724 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -940,7 +940,7 @@
         key: :improved_hashtag_timeline,
         type: :keyword,
         description:
-          "If `true` / `:prefer_aggregation`, hashtags table and selected strategy will be used for hashtags timeline. When `false`, object-embedded hashtags will be used (slower). Is auto-set to `true` (unless overridden) when HashtagsTableMigrator completes."
+          "If `true`, hashtags will be fetched from `hashtags` table for hashtags timeline. When `false`, object-embedded hashtags will be used (slower). Is auto-set to `true` (unless overridden) when HashtagsTableMigrator completes."
       }
     ]
   },
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 0a21ac2f2..cda8d3f54 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -669,24 +669,6 @@ defp restrict_since(query, %{since_id: since_id}) do
 
   defp restrict_since(query, _), do: query
 
-  defp restrict_embedded_tag_reject(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
-    raise_on_missing_preload()
-  end
-
-  defp restrict_embedded_tag_reject(query, %{tag_reject: tag_reject}) when is_list(tag_reject) do
-    from(
-      [_activity, object] in query,
-      where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
-    )
-  end
-
-  defp restrict_embedded_tag_reject(query, %{tag_reject: tag_reject})
-       when is_binary(tag_reject) do
-    restrict_embedded_tag_reject(query, %{tag_reject: [tag_reject]})
-  end
-
-  defp restrict_embedded_tag_reject(query, _), do: query
-
   defp restrict_embedded_tag_all(_query, %{tag_all: _tag_all, skip_preload: true}) do
     raise_on_missing_preload()
   end
@@ -699,139 +681,65 @@ defp restrict_embedded_tag_all(query, %{tag_all: tag_all}) when is_list(tag_all)
   end
 
   defp restrict_embedded_tag_all(query, %{tag_all: tag}) when is_binary(tag) do
-    restrict_embedded_tag(query, %{tag: tag})
+    restrict_embedded_tag_any(query, %{tag: tag})
   end
 
   defp restrict_embedded_tag_all(query, _), do: query
 
-  defp restrict_embedded_tag(_query, %{tag: _tag, skip_preload: true}) do
+  defp restrict_embedded_tag_any(_query, %{tag: _tag, skip_preload: true}) do
     raise_on_missing_preload()
   end
 
-  defp restrict_embedded_tag(query, %{tag: tag}) when is_list(tag) do
+  defp restrict_embedded_tag_any(query, %{tag: tag}) when is_list(tag) do
     from(
       [_activity, object] in query,
       where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag)
     )
   end
 
-  defp restrict_embedded_tag(query, %{tag: tag}) when is_binary(tag) do
-    restrict_embedded_tag(query, %{tag: [tag]})
+  defp restrict_embedded_tag_any(query, %{tag: tag}) when is_binary(tag) do
+    restrict_embedded_tag_any(query, %{tag: [tag]})
   end
 
-  defp restrict_embedded_tag(query, _), do: query
+  defp restrict_embedded_tag_any(query, _), do: query
 
-  defp hashtag_conditions(opts) do
-    [:tag, :tag_all, :tag_reject]
-    |> Enum.map(&opts[&1])
-    |> Enum.map(&List.wrap(&1))
-  end
-
-  # Note: times out on larger instances (with default timeout), intended for complex queries
-  defp restrict_hashtag_agg(query, opts) do
-    [tag_any, tag_all, tag_reject] = hashtag_conditions(opts)
-    has_conditions = Enum.any?([tag_any, tag_all, tag_reject], &Enum.any?(&1))
-
-    cond do
-      !has_conditions ->
-        query
-
-      opts[:skip_preload] ->
-        raise_on_missing_preload()
-
-      true ->
-        query
-        |> group_by_all_bindings()
-        |> join(:left, [_activity, object], hashtag in assoc(object, :hashtags), as: :hashtag)
-        |> maybe_restrict_hashtag_any(tag_any)
-        |> maybe_restrict_hashtag_all(tag_all)
-        |> maybe_restrict_hashtag_reject_any(tag_reject)
-    end
-  end
-
-  # Groups by all bindings to allow aggregation on hashtags
-  defp group_by_all_bindings(query) do
-    # Expecting named bindings: :object, :bookmark, :thread_mute, :report_note
-    cond do
-      Enum.count(query.aliases) == 4 ->
-        from([a, o, b3, b4, b5] in query, group_by: [a.id, o.id, b3.id, b4.id, b5.id])
-
-      Enum.count(query.aliases) == 3 ->
-        from([a, o, b3, b4] in query, group_by: [a.id, o.id, b3.id, b4.id])
-
-      Enum.count(query.aliases) == 2 ->
-        from([a, o, b3] in query, group_by: [a.id, o.id, b3.id])
-
-      true ->
-        from([a, o] in query, group_by: [a.id, o.id])
-    end
-  end
-
-  defp maybe_restrict_hashtag_any(query, []) do
-    query
-  end
-
-  defp maybe_restrict_hashtag_any(query, tags) do
-    having(
-      query,
-      [hashtag: hashtag],
-      fragment("array_agg(?) && (?)", hashtag.name, ^tags)
-    )
-  end
-
-  defp maybe_restrict_hashtag_all(query, []) do
-    query
-  end
-
-  defp maybe_restrict_hashtag_all(query, tags) do
-    having(
-      query,
-      [hashtag: hashtag],
-      fragment("array_agg(?) @> (?)", hashtag.name, ^tags)
-    )
-  end
-
-  defp maybe_restrict_hashtag_reject_any(query, []) do
-    query
-  end
-
-  defp maybe_restrict_hashtag_reject_any(query, tags) do
-    having(
-      query,
-      [hashtag: hashtag],
-      fragment("not(array_agg(?) && (?))", hashtag.name, ^tags)
-    )
-  end
-
-  defp restrict_hashtag_reject_any(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
+  defp restrict_embedded_tag_reject_any(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
     raise_on_missing_preload()
   end
 
-  defp restrict_hashtag_reject_any(query, %{tag_reject: tags_reject}) when is_list(tags_reject) do
-    query
-    |> group_by_all_bindings()
-    |> join(:left, [_activity, object], hashtag in assoc(object, :hashtags), as: :hashtag)
-    |> having(
-      [hashtag: hashtag],
-      fragment("not(array_agg(?) && (?))", hashtag.name, ^tags_reject)
+  defp restrict_embedded_tag_reject_any(query, %{tag_reject: tag_reject})
+       when is_list(tag_reject) do
+    from(
+      [_activity, object] in query,
+      where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
     )
   end
 
-  defp restrict_hashtag_reject_any(query, %{tag_reject: tag_reject}) when is_binary(tag_reject) do
-    restrict_hashtag_reject_any(query, %{tag_reject: [tag_reject]})
+  defp restrict_embedded_tag_reject_any(query, %{tag_reject: tag_reject})
+       when is_binary(tag_reject) do
+    restrict_embedded_tag_reject_any(query, %{tag_reject: [tag_reject]})
   end
 
-  defp restrict_hashtag_reject_any(query, _), do: query
+  defp restrict_embedded_tag_reject_any(query, _), do: query
 
   defp restrict_hashtag_all(_query, %{tag_all: _tag, skip_preload: true}) do
     raise_on_missing_preload()
   end
 
   defp restrict_hashtag_all(query, %{tag_all: tags}) when is_list(tags) do
-    Enum.reduce(
-      tags,
-      query,
-      fn tag, acc -> restrict_hashtag_any(acc, %{tag: tag}) end
+    from(
+      [_activity, object] in query,
+      where:
+        fragment(
+          """
+          (SELECT array_agg(hashtags.name) FROM hashtags JOIN hashtags_objects
+            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
+              AND hashtags_objects.object_id = ?) @> ?
+          """,
+          ^tags,
+          object.id,
+          ^tags
+        )
     )
   end
 
@@ -846,18 +754,19 @@ defp restrict_hashtag_any(_query, %{tag: _tag, skip_preload: true}) do
   end
 
   defp restrict_hashtag_any(query, %{tag: tags}) when is_list(tags) do
-    query =
-      from(
-        [_activity, object] in query,
-        join: hashtag in assoc(object, :hashtags),
-        where: hashtag.name in ^tags
-      )
-
-    if length(tags) > 1 do
-      distinct(query, [activity], true)
-    else
-      query
-    end
+    from(
+      [_activity, object] in query,
+      where:
+        fragment(
+          """
+          EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
+            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
+              AND hashtags_objects.object_id = ? LIMIT 1)
+          """,
+          ^tags,
+          object.id
+        )
+    )
   end
 
   defp restrict_hashtag_any(query, %{tag: tag}) when is_binary(tag) do
@@ -866,6 +775,32 @@ defp restrict_hashtag_any(query, %{tag: tag}) when is_binary(tag) do
 
   defp restrict_hashtag_any(query, _), do: query
 
+  defp restrict_hashtag_reject_any(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
+    raise_on_missing_preload()
+  end
+
+  defp restrict_hashtag_reject_any(query, %{tag_reject: tags_reject}) when is_list(tags_reject) do
+    from(
+      [_activity, object] in query,
+      where:
+        fragment(
+          """
+          NOT EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
+            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
+              AND hashtags_objects.object_id = ? LIMIT 1)
+          """,
+          ^tags_reject,
+          object.id
+        )
+    )
+  end
+
+  defp restrict_hashtag_reject_any(query, %{tag_reject: tag_reject}) when is_binary(tag_reject) do
+    restrict_hashtag_reject_any(query, %{tag_reject: [tag_reject]})
+  end
+
+  defp restrict_hashtag_reject_any(query, _), do: query
+
   defp raise_on_missing_preload do
     raise "Can't use the child object without preloading!"
   end
@@ -1286,23 +1221,16 @@ def fetch_activities_query(recipients, opts \\ %{}) do
       |> exclude_invisible_actors(opts)
       |> exclude_visibility(opts)
 
-    hashtag_timeline_strategy = Config.improved_hashtag_timeline()
-
-    cond do
-      !hashtag_timeline_strategy ->
-        query
-        |> restrict_embedded_tag(opts)
-        |> restrict_embedded_tag_reject(opts)
-        |> restrict_embedded_tag_all(opts)
-
-      hashtag_timeline_strategy == :prefer_aggregation ->
-        restrict_hashtag_agg(query, opts)
-
-      true ->
-        query
-        |> restrict_hashtag_any(opts)
-        |> restrict_hashtag_all(opts)
-        |> restrict_hashtag_reject_any(opts)
+    if Config.improved_hashtag_timeline() do
+      query
+      |> restrict_hashtag_any(opts)
+      |> restrict_hashtag_all(opts)
+      |> restrict_hashtag_reject_any(opts)
+    else
+      query
+      |> restrict_embedded_tag_any(opts)
+      |> restrict_embedded_tag_all(opts)
+      |> restrict_embedded_tag_reject_any(opts)
     end
   end
 
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index 0b18269cd..c2cc5a9af 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -220,7 +220,7 @@ test "it fetches the appropriate tag-restricted posts" do
     {:ok, status_four} = CommonAPI.post(user, %{status: ". #any1 #any2"})
     {:ok, status_five} = CommonAPI.post(user, %{status: ". #any2 #any1"})
 
-    for hashtag_timeline_strategy <- [true, :prefer_aggregation, false] do
+    for hashtag_timeline_strategy <- [true, false] do
       clear_config([:instance, :improved_hashtag_timeline], hashtag_timeline_strategy)
 
       fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})
@@ -241,8 +241,7 @@ test "it fetches the appropriate tag-restricted posts" do
           tag_all: ["test", "reject"]
         })
 
-      # This test would fail if JOIN with 2+ terms in "any" clause is done without DISTINCT.
-      # The :limit is important (w/o DISTINCT 2 records are deduped by Ecto to 1 b/c of preload).
+      # Testing that deduplication (if needed) is done on DB (not Ecto) level; :limit is important
       fetch_five =
         ActivityPub.fetch_activities([], %{
           type: "Create",

From 108e90b18edcfb57b9839e7c5d6d444a63ae2069 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Sun, 31 Jan 2021 22:03:59 +0300
Subject: [PATCH 037/339] [#3213] Explicitly defined PKs in hashtags_objects
 and data_migration_failed_ids. Added "pleroma.database rollback" task to
 revert a single migration.

---
 lib/mix/tasks/pleroma/database.ex             | 24 +++++++++++++++++++
 ...20201221203824_create_hashtags_objects.exs |  4 ++--
 ...gration_create_populate_hashtags_table.exs |  4 +++-
 ...72254_create_data_migration_failed_ids.exs |  4 ++--
 4 files changed, 31 insertions(+), 5 deletions(-)

diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index 4ddace9c9..30c0d2bf1 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -20,6 +20,30 @@ defmodule Mix.Tasks.Pleroma.Database do
   @shortdoc "A collection of database related tasks"
   @moduledoc File.read!("docs/administration/CLI_tasks/database.md")
 
+  # Rolls back a specific migration (leaving subsequent migrations applied)
+  # Based on https://stackoverflow.com/a/53825840
+  def run(["rollback", version]) do
+    start_pleroma()
+
+    version = String.to_integer(version)
+    re = ~r/^#{version}_.*\.exs/
+    path = Application.app_dir(:pleroma, Path.join(["priv", "repo", "migrations"]))
+
+    result =
+      with {:find, "" <> file} <- {:find, Enum.find(File.ls!(path), &String.match?(&1, re))},
+           {:compile, [{mod, _} | _]} <- {:compile, Code.compile_file(Path.join(path, file))},
+           {:rollback, :ok} <- {:rollback, Ecto.Migrator.down(Repo, version, mod)} do
+        {:ok, "Reversed migration: #{file}"}
+      else
+        {:find, _} -> {:error, "No migration found with version prefix: #{version}"}
+        {:compile, e} -> {:error, "Problem compiling migration module: #{inspect(e)}"}
+        {:rollback, e} -> {:error, "Problem reversing migration: #{inspect(e)}"}
+        e -> {:error, "Something unexpected happened: #{inspect(e)}"}
+      end
+
+    IO.inspect(result)
+  end
+
   def run(["remove_embedded_objects" | args]) do
     {options, [], []} =
       OptionParser.parse(
diff --git a/priv/repo/migrations/20201221203824_create_hashtags_objects.exs b/priv/repo/migrations/20201221203824_create_hashtags_objects.exs
index 214ea81c3..efd60369d 100644
--- a/priv/repo/migrations/20201221203824_create_hashtags_objects.exs
+++ b/priv/repo/migrations/20201221203824_create_hashtags_objects.exs
@@ -3,8 +3,8 @@ defmodule Pleroma.Repo.Migrations.CreateHashtagsObjects do
 
   def change do
     create_if_not_exists table(:hashtags_objects, primary_key: false) do
-      add(:hashtag_id, references(:hashtags), null: false)
-      add(:object_id, references(:objects), null: false)
+      add(:hashtag_id, references(:hashtags), null: false, primary_key: true)
+      add(:object_id, references(:objects), null: false, primary_key: true)
     end
 
     create_if_not_exists(unique_index(:hashtags_objects, [:hashtag_id, :object_id]))
diff --git a/priv/repo/migrations/20210106183301_data_migration_create_populate_hashtags_table.exs b/priv/repo/migrations/20210106183301_data_migration_create_populate_hashtags_table.exs
index 2a965f075..cf3cf26a0 100644
--- a/priv/repo/migrations/20210106183301_data_migration_create_populate_hashtags_table.exs
+++ b/priv/repo/migrations/20210106183301_data_migration_create_populate_hashtags_table.exs
@@ -10,5 +10,7 @@ def up do
     )
   end
 
-  def down, do: :ok
+  def down do
+    execute("DELETE FROM data_migrations WHERE name = 'populate_hashtags_table';")
+  end
 end
diff --git a/priv/repo/migrations/20210111172254_create_data_migration_failed_ids.exs b/priv/repo/migrations/20210111172254_create_data_migration_failed_ids.exs
index ba0be98af..18afa74ac 100644
--- a/priv/repo/migrations/20210111172254_create_data_migration_failed_ids.exs
+++ b/priv/repo/migrations/20210111172254_create_data_migration_failed_ids.exs
@@ -3,8 +3,8 @@ defmodule Pleroma.Repo.Migrations.CreateDataMigrationFailedIds do
 
   def change do
     create_if_not_exists table(:data_migration_failed_ids, primary_key: false) do
-      add(:data_migration_id, references(:data_migrations), null: false)
-      add(:record_id, :bigint, null: false)
+      add(:data_migration_id, references(:data_migrations), null: false, primary_key: true)
+      add(:record_id, :bigint, null: false, primary_key: true)
     end
 
     create_if_not_exists(

From 10207f840ce3515dddfde36288575f203c52840f Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Sun, 31 Jan 2021 22:36:46 +0300
Subject: [PATCH 038/339] [#3213] ActivityPub: temporarily reverted to previous
 hashtags filtering implementation due to blank results issue.

---
 lib/pleroma/web/activity_pub/activity_pub.ex | 106 ++++++++++---------
 1 file changed, 54 insertions(+), 52 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index cda8d3f54..fd0144aad 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -722,24 +722,53 @@ defp restrict_embedded_tag_reject_any(query, %{tag_reject: tag_reject})
 
   defp restrict_embedded_tag_reject_any(query, _), do: query
 
+  # Groups by all bindings to allow aggregation on hashtags
+  defp group_by_all_bindings(query) do
+    # Expecting named bindings: :object, :bookmark, :thread_mute, :report_note
+    cond do
+      Enum.count(query.aliases) == 4 ->
+        from([a, o, b3, b4, b5] in query, group_by: [a.id, o.id, b3.id, b4.id, b5.id])
+
+      Enum.count(query.aliases) == 3 ->
+        from([a, o, b3, b4] in query, group_by: [a.id, o.id, b3.id, b4.id])
+
+      Enum.count(query.aliases) == 2 ->
+        from([a, o, b3] in query, group_by: [a.id, o.id, b3.id])
+
+      true ->
+        from([a, o] in query, group_by: [a.id, o.id])
+    end
+  end
+
+  defp restrict_hashtag_reject_any(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
+    raise_on_missing_preload()
+  end
+
+  defp restrict_hashtag_reject_any(query, %{tag_reject: tags_reject}) when is_list(tags_reject) do
+    query
+    |> group_by_all_bindings()
+    |> join(:left, [_activity, object], hashtag in assoc(object, :hashtags), as: :hashtag)
+    |> having(
+      [hashtag: hashtag],
+      fragment("not(array_agg(?) && (?))", hashtag.name, ^tags_reject)
+    )
+  end
+
+  defp restrict_hashtag_reject_any(query, %{tag_reject: tag_reject}) when is_binary(tag_reject) do
+    restrict_hashtag_reject_any(query, %{tag_reject: [tag_reject]})
+  end
+
+  defp restrict_hashtag_reject_any(query, _), do: query
+
   defp restrict_hashtag_all(_query, %{tag_all: _tag, skip_preload: true}) do
     raise_on_missing_preload()
   end
 
   defp restrict_hashtag_all(query, %{tag_all: tags}) when is_list(tags) do
-    from(
-      [_activity, object] in query,
-      where:
-        fragment(
-          """
-          (SELECT array_agg(hashtags.name) FROM hashtags JOIN hashtags_objects
-            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
-              AND hashtags_objects.object_id = ?) @> ?
-          """,
-          ^tags,
-          object.id,
-          ^tags
-        )
+    Enum.reduce(
+      tags,
+      query,
+      fn tag, acc -> restrict_hashtag_any(acc, %{tag: tag}) end
     )
   end
 
@@ -754,19 +783,18 @@ defp restrict_hashtag_any(_query, %{tag: _tag, skip_preload: true}) do
   end
 
   defp restrict_hashtag_any(query, %{tag: tags}) when is_list(tags) do
-    from(
-      [_activity, object] in query,
-      where:
-        fragment(
-          """
-          EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
-            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
-              AND hashtags_objects.object_id = ? LIMIT 1)
-          """,
-          ^tags,
-          object.id
-        )
-    )
+    query =
+      from(
+        [_activity, object] in query,
+        join: hashtag in assoc(object, :hashtags),
+        where: hashtag.name in ^tags
+      )
+
+    if length(tags) > 1 do
+      distinct(query, [activity], true)
+    else
+      query
+    end
   end
 
   defp restrict_hashtag_any(query, %{tag: tag}) when is_binary(tag) do
@@ -775,32 +803,6 @@ defp restrict_hashtag_any(query, %{tag: tag}) when is_binary(tag) do
 
   defp restrict_hashtag_any(query, _), do: query
 
-  defp restrict_hashtag_reject_any(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
-    raise_on_missing_preload()
-  end
-
-  defp restrict_hashtag_reject_any(query, %{tag_reject: tags_reject}) when is_list(tags_reject) do
-    from(
-      [_activity, object] in query,
-      where:
-        fragment(
-          """
-          NOT EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
-            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
-              AND hashtags_objects.object_id = ? LIMIT 1)
-          """,
-          ^tags_reject,
-          object.id
-        )
-    )
-  end
-
-  defp restrict_hashtag_reject_any(query, %{tag_reject: tag_reject}) when is_binary(tag_reject) do
-    restrict_hashtag_reject_any(query, %{tag_reject: [tag_reject]})
-  end
-
-  defp restrict_hashtag_reject_any(query, _), do: query
-
   defp raise_on_missing_preload do
     raise "Can't use the child object without preloading!"
   end

From cf4765af4098098fa4d6996193432bd19c439a75 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Sun, 31 Jan 2021 23:06:38 +0300
Subject: [PATCH 039/339] [#3213] ActivityPub: fixed subquery-based hashtags
 filtering implementation (addressed empty list options issue). Added
 regression test.

---
 lib/pleroma/web/activity_pub/activity_pub.ex  | 117 +++++++++---------
 .../web/activity_pub/activity_pub_test.exs    |  11 ++
 2 files changed, 68 insertions(+), 60 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index fd0144aad..6cf4093fb 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -673,7 +673,7 @@ defp restrict_embedded_tag_all(_query, %{tag_all: _tag_all, skip_preload: true})
     raise_on_missing_preload()
   end
 
-  defp restrict_embedded_tag_all(query, %{tag_all: tag_all}) when is_list(tag_all) do
+  defp restrict_embedded_tag_all(query, %{tag_all: [_ | _] = tag_all}) do
     from(
       [_activity, object] in query,
       where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)
@@ -690,7 +690,7 @@ defp restrict_embedded_tag_any(_query, %{tag: _tag, skip_preload: true}) do
     raise_on_missing_preload()
   end
 
-  defp restrict_embedded_tag_any(query, %{tag: tag}) when is_list(tag) do
+  defp restrict_embedded_tag_any(query, %{tag: [_ | _] = tag}) do
     from(
       [_activity, object] in query,
       where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag)
@@ -707,8 +707,7 @@ defp restrict_embedded_tag_reject_any(_query, %{tag_reject: _tag_reject, skip_pr
     raise_on_missing_preload()
   end
 
-  defp restrict_embedded_tag_reject_any(query, %{tag_reject: tag_reject})
-       when is_list(tag_reject) do
+  defp restrict_embedded_tag_reject_any(query, %{tag_reject: [_ | _] = tag_reject}) do
     from(
       [_activity, object] in query,
       where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
@@ -722,53 +721,24 @@ defp restrict_embedded_tag_reject_any(query, %{tag_reject: tag_reject})
 
   defp restrict_embedded_tag_reject_any(query, _), do: query
 
-  # Groups by all bindings to allow aggregation on hashtags
-  defp group_by_all_bindings(query) do
-    # Expecting named bindings: :object, :bookmark, :thread_mute, :report_note
-    cond do
-      Enum.count(query.aliases) == 4 ->
-        from([a, o, b3, b4, b5] in query, group_by: [a.id, o.id, b3.id, b4.id, b5.id])
-
-      Enum.count(query.aliases) == 3 ->
-        from([a, o, b3, b4] in query, group_by: [a.id, o.id, b3.id, b4.id])
-
-      Enum.count(query.aliases) == 2 ->
-        from([a, o, b3] in query, group_by: [a.id, o.id, b3.id])
-
-      true ->
-        from([a, o] in query, group_by: [a.id, o.id])
-    end
-  end
-
-  defp restrict_hashtag_reject_any(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
-    raise_on_missing_preload()
-  end
-
-  defp restrict_hashtag_reject_any(query, %{tag_reject: tags_reject}) when is_list(tags_reject) do
-    query
-    |> group_by_all_bindings()
-    |> join(:left, [_activity, object], hashtag in assoc(object, :hashtags), as: :hashtag)
-    |> having(
-      [hashtag: hashtag],
-      fragment("not(array_agg(?) && (?))", hashtag.name, ^tags_reject)
-    )
-  end
-
-  defp restrict_hashtag_reject_any(query, %{tag_reject: tag_reject}) when is_binary(tag_reject) do
-    restrict_hashtag_reject_any(query, %{tag_reject: [tag_reject]})
-  end
-
-  defp restrict_hashtag_reject_any(query, _), do: query
-
   defp restrict_hashtag_all(_query, %{tag_all: _tag, skip_preload: true}) do
     raise_on_missing_preload()
   end
 
-  defp restrict_hashtag_all(query, %{tag_all: tags}) when is_list(tags) do
-    Enum.reduce(
-      tags,
-      query,
-      fn tag, acc -> restrict_hashtag_any(acc, %{tag: tag}) end
+  defp restrict_hashtag_all(query, %{tag_all: [_ | _] = tags}) do
+    from(
+      [_activity, object] in query,
+      where:
+        fragment(
+          """
+          (SELECT array_agg(hashtags.name) FROM hashtags JOIN hashtags_objects
+            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
+              AND hashtags_objects.object_id = ?) @> ?
+          """,
+          ^tags,
+          object.id,
+          ^tags
+        )
     )
   end
 
@@ -782,19 +752,20 @@ defp restrict_hashtag_any(_query, %{tag: _tag, skip_preload: true}) do
     raise_on_missing_preload()
   end
 
-  defp restrict_hashtag_any(query, %{tag: tags}) when is_list(tags) do
-    query =
-      from(
-        [_activity, object] in query,
-        join: hashtag in assoc(object, :hashtags),
-        where: hashtag.name in ^tags
-      )
-
-    if length(tags) > 1 do
-      distinct(query, [activity], true)
-    else
-      query
-    end
+  defp restrict_hashtag_any(query, %{tag: [_ | _] = tags}) do
+    from(
+      [_activity, object] in query,
+      where:
+        fragment(
+          """
+          EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
+            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
+              AND hashtags_objects.object_id = ? LIMIT 1)
+          """,
+          ^tags,
+          object.id
+        )
+    )
   end
 
   defp restrict_hashtag_any(query, %{tag: tag}) when is_binary(tag) do
@@ -803,6 +774,32 @@ defp restrict_hashtag_any(query, %{tag: tag}) when is_binary(tag) do
 
   defp restrict_hashtag_any(query, _), do: query
 
+  defp restrict_hashtag_reject_any(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
+    raise_on_missing_preload()
+  end
+
+  defp restrict_hashtag_reject_any(query, %{tag_reject: [_ | _] = tags_reject}) do
+    from(
+      [_activity, object] in query,
+      where:
+        fragment(
+          """
+          NOT EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
+            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
+              AND hashtags_objects.object_id = ? LIMIT 1)
+          """,
+          ^tags_reject,
+          object.id
+        )
+    )
+  end
+
+  defp restrict_hashtag_reject_any(query, %{tag_reject: tag_reject}) when is_binary(tag_reject) do
+    restrict_hashtag_reject_any(query, %{tag_reject: [tag_reject]})
+  end
+
+  defp restrict_hashtag_reject_any(query, _), do: query
+
   defp raise_on_missing_preload do
     raise "Can't use the child object without preloading!"
   end
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index 5b9fc061e..04fd1def3 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -249,6 +249,17 @@ test "it fetches the appropriate tag-restricted posts" do
           limit: 2
         })
 
+      fetch_six =
+        ActivityPub.fetch_activities([], %{
+          type: "Create",
+          tag: ["any1", "any2"],
+          tag_all: [],
+          tag_reject: []
+        })
+
+      # Regression test: passing empty lists as filter options shouldn't affect the results
+      assert fetch_five == fetch_six
+
       [fetch_one, fetch_two, fetch_three, fetch_four, fetch_five] =
         Enum.map([fetch_one, fetch_two, fetch_three, fetch_four, fetch_five], fn statuses ->
           Enum.map(statuses, fn s -> Repo.preload(s, object: :hashtags) end)

From d1c6dd97aa503ca7c897d67d98fe8c924e113a61 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Sun, 7 Feb 2021 22:24:12 +0300
Subject: [PATCH 040/339] [#3213] Partially addressed code review points.
 migration rollback task changes, hashtags-related config handling tweaks,
 `hashtags.data` deletion (unused).

---
 config/description.exs                        | 20 ++++---
 lib/mix/tasks/pleroma/database.ex             | 53 ++++++++++---------
 lib/pleroma/config.ex                         |  3 --
 lib/pleroma/hashtag.ex                        |  5 +-
 .../migrators/hashtags_table_migrator.ex      |  4 +-
 lib/pleroma/web/activity_pub/activity_pub.ex  |  2 +-
 .../20201221202251_create_hashtags.exs        |  1 -
 ...201221202252_remove_data_from_hashtags.exs | 15 ++++++
 .../web/activity_pub/activity_pub_test.exs    |  2 +-
 9 files changed, 63 insertions(+), 42 deletions(-)
 create mode 100644 priv/repo/migrations/20201221202252_remove_data_from_hashtags.exs

diff --git a/config/description.exs b/config/description.exs
index ed3a534a0..02cdf2ff3 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -495,6 +495,20 @@
       }
     ]
   },
+  %{
+    group: :pleroma,
+    key: :database,
+    type: :group,
+    description: "Database-related settings",
+    children: [
+      %{
+        key: :improved_hashtag_timeline,
+        type: :keyword,
+        description:
+          "If `true`, hashtags will be fetched from `hashtags` table for hashtags timeline. When `false`, object-embedded hashtags will be used (slower). Is auto-set to `true` (unless overridden) when HashtagsTableMigrator completes."
+      }
+    ]
+  },
   %{
     group: :pleroma,
     key: :instance,
@@ -941,12 +955,6 @@
         key: :show_reactions,
         type: :boolean,
         description: "Let favourites and emoji reactions be viewed through the API."
-      },
-      %{
-        key: :improved_hashtag_timeline,
-        type: :keyword,
-        description:
-          "If `true`, hashtags will be fetched from `hashtags` table for hashtags timeline. When `false`, object-embedded hashtags will be used (slower). Is auto-set to `true` (unless overridden) when HashtagsTableMigrator completes."
       }
     ]
   },
diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index 30c0d2bf1..7c4f54141 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -20,30 +20,6 @@ defmodule Mix.Tasks.Pleroma.Database do
   @shortdoc "A collection of database related tasks"
   @moduledoc File.read!("docs/administration/CLI_tasks/database.md")
 
-  # Rolls back a specific migration (leaving subsequent migrations applied)
-  # Based on https://stackoverflow.com/a/53825840
-  def run(["rollback", version]) do
-    start_pleroma()
-
-    version = String.to_integer(version)
-    re = ~r/^#{version}_.*\.exs/
-    path = Application.app_dir(:pleroma, Path.join(["priv", "repo", "migrations"]))
-
-    result =
-      with {:find, "" <> file} <- {:find, Enum.find(File.ls!(path), &String.match?(&1, re))},
-           {:compile, [{mod, _} | _]} <- {:compile, Code.compile_file(Path.join(path, file))},
-           {:rollback, :ok} <- {:rollback, Ecto.Migrator.down(Repo, version, mod)} do
-        {:ok, "Reversed migration: #{file}"}
-      else
-        {:find, _} -> {:error, "No migration found with version prefix: #{version}"}
-        {:compile, e} -> {:error, "Problem compiling migration module: #{inspect(e)}"}
-        {:rollback, e} -> {:error, "Problem reversing migration: #{inspect(e)}"}
-        e -> {:error, "Something unexpected happened: #{inspect(e)}"}
-      end
-
-    IO.inspect(result)
-  end
-
   def run(["remove_embedded_objects" | args]) do
     {options, [], []} =
       OptionParser.parse(
@@ -194,4 +170,33 @@ def run(["ensure_expiration"]) do
     end)
     |> Stream.run()
   end
+
+  # Rolls back a specific migration (leaving subsequent migrations applied).
+  # WARNING: imposes a risk of unrecoverable data loss — proceed at your own responsibility.
+  # Based on https://stackoverflow.com/a/53825840
+  def run(["rollback", version]) do
+    prompt = "SEVERE WARNING: this operation may result in unrecoverable data loss. Continue?"
+
+    if shell_prompt(prompt, "n") in ~w(Yn Y y) do
+      {_, result, _} =
+        Ecto.Migrator.with_repo(Pleroma.Repo, fn repo ->
+          version = String.to_integer(version)
+          re = ~r/^#{version}_.*\.exs/
+          path = Ecto.Migrator.migrations_path(repo)
+
+          with {:find, "" <> file} <- {:find, Enum.find(File.ls!(path), &String.match?(&1, re))},
+               {:compile, [{mod, _} | _]} <- {:compile, Code.compile_file(Path.join(path, file))},
+               {:rollback, :ok} <- {:rollback, Ecto.Migrator.down(repo, version, mod)} do
+            {:ok, "Reversed migration: #{file}"}
+          else
+            {:find, _} -> {:error, "No migration found with version prefix: #{version}"}
+            {:compile, e} -> {:error, "Problem compiling migration module: #{inspect(e)}"}
+            {:rollback, e} -> {:error, "Problem reversing migration: #{inspect(e)}"}
+            e -> {:error, "Something unexpected happened: #{inspect(e)}"}
+          end
+        end)
+
+      IO.inspect(result)
+    end
+  end
 end
diff --git a/lib/pleroma/config.ex b/lib/pleroma/config.ex
index 0a6ac0ad0..f17e14128 100644
--- a/lib/pleroma/config.ex
+++ b/lib/pleroma/config.ex
@@ -96,9 +96,6 @@ def restrict_unauthenticated_access?(resource, kind) do
     end
   end
 
-  def improved_hashtag_timeline_path, do: [:instance, :improved_hashtag_timeline]
-  def improved_hashtag_timeline, do: get(improved_hashtag_timeline_path())
-
   def oauth_consumer_strategies, do: get([:auth, :oauth_consumer_strategies], [])
 
   def oauth_consumer_enabled?, do: oauth_consumer_strategies() != []
diff --git a/lib/pleroma/hashtag.ex b/lib/pleroma/hashtag.ex
index b05927563..9e4c6c894 100644
--- a/lib/pleroma/hashtag.ex
+++ b/lib/pleroma/hashtag.ex
@@ -10,11 +10,8 @@ defmodule Pleroma.Hashtag do
   alias Pleroma.Hashtag
   alias Pleroma.Repo
 
-  @derive {Jason.Encoder, only: [:data]}
-
   schema "hashtags" do
     field(:name, :string)
-    field(:data, :map, default: %{})
 
     many_to_many(:objects, Pleroma.Object, join_through: "hashtags_objects", on_replace: :delete)
 
@@ -50,7 +47,7 @@ def get_or_create_by_names(names) when is_list(names) do
 
   def changeset(%Hashtag{} = struct, params) do
     struct
-    |> cast(params, [:name, :data])
+    |> cast(params, [:name])
     |> update_change(:name, &String.downcase/1)
     |> validate_required([:name])
     |> unique_constraint(:name)
diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index 07b42a7f4..9a036e0b2 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -239,11 +239,11 @@ defp handle_success(data_migration) do
       data_migration.feature_lock ->
         :noop
 
-      not is_nil(Config.improved_hashtag_timeline()) ->
+      not is_nil(Config.get([:database, :improved_hashtag_timeline])) ->
         :noop
 
       true ->
-        Config.put(Config.improved_hashtag_timeline_path(), true)
+        Config.put([:database, :improved_hashtag_timeline], true)
         :ok
     end
   end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 573b4243c..7ac18e5c5 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1227,7 +1227,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
       |> exclude_invisible_actors(opts)
       |> exclude_visibility(opts)
 
-    if Config.improved_hashtag_timeline() do
+    if Config.get([:database, :improved_hashtag_timeline]) do
       query
       |> restrict_hashtag_any(opts)
       |> restrict_hashtag_all(opts)
diff --git a/priv/repo/migrations/20201221202251_create_hashtags.exs b/priv/repo/migrations/20201221202251_create_hashtags.exs
index afc522002..8d2e9ae66 100644
--- a/priv/repo/migrations/20201221202251_create_hashtags.exs
+++ b/priv/repo/migrations/20201221202251_create_hashtags.exs
@@ -4,7 +4,6 @@ defmodule Pleroma.Repo.Migrations.CreateHashtags do
   def change do
     create_if_not_exists table(:hashtags) do
       add(:name, :citext, null: false)
-      add(:data, :map, default: %{})
 
       timestamps()
     end
diff --git a/priv/repo/migrations/20201221202252_remove_data_from_hashtags.exs b/priv/repo/migrations/20201221202252_remove_data_from_hashtags.exs
new file mode 100644
index 000000000..0442c3b87
--- /dev/null
+++ b/priv/repo/migrations/20201221202252_remove_data_from_hashtags.exs
@@ -0,0 +1,15 @@
+defmodule Pleroma.Repo.Migrations.RemoveDataFromHashtags do
+  use Ecto.Migration
+
+  def up do
+    alter table(:hashtags) do
+      remove_if_exists(:data, :map)
+    end
+  end
+
+  def down do
+    alter table(:hashtags) do
+      add_if_not_exists(:data, :map, default: %{})
+    end
+  end
+end
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index 04fd1def3..bab5a199c 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -221,7 +221,7 @@ test "it fetches the appropriate tag-restricted posts" do
     {:ok, status_five} = CommonAPI.post(user, %{status: ". #any2 #any1"})
 
     for hashtag_timeline_strategy <- [true, false] do
-      clear_config([:instance, :improved_hashtag_timeline], hashtag_timeline_strategy)
+      clear_config([:database, :improved_hashtag_timeline], hashtag_timeline_strategy)
 
       fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})
 

From a996ab46a54acbfa7a19da3eae12c78ed6466a1a Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Thu, 11 Feb 2021 19:30:21 +0300
Subject: [PATCH 041/339] [#3213] Reorganized hashtags cleanup.
 Transaction-wrapped Hashtag.get_or_create_by_names/1. Misc. improvements.

---
 config/config.exs                             |  1 -
 config/description.exs                        |  1 -
 lib/pleroma/hashtag.ex                        | 58 +++++++++++++---
 .../migrators/hashtags_table_migrator.ex      | 68 +++++++++++++------
 lib/pleroma/object.ex                         | 27 +++++---
 .../workers/cron/hashtags_cleanup_worker.ex   | 57 ----------------
 6 files changed, 112 insertions(+), 100 deletions(-)
 delete mode 100644 lib/pleroma/workers/cron/hashtags_cleanup_worker.ex

diff --git a/config/config.exs b/config/config.exs
index 36c609936..91888c512 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -560,7 +560,6 @@
   ],
   plugins: [Oban.Plugins.Pruner],
   crontab: [
-    {"0 1 * * *", Pleroma.Workers.Cron.HashtagsCleanupWorker},
     {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
     {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
   ]
diff --git a/config/description.exs b/config/description.exs
index 02cdf2ff3..b2f301e2d 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -1964,7 +1964,6 @@
         type: {:list, :tuple},
         description: "Settings for cron background jobs",
         suggestions: [
-          {"0 1 * * *", Pleroma.Workers.Cron.HashtagsCleanupWorker},
           {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
           {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
         ]
diff --git a/lib/pleroma/hashtag.ex b/lib/pleroma/hashtag.ex
index 9e4c6c894..de52c4dae 100644
--- a/lib/pleroma/hashtag.ex
+++ b/lib/pleroma/hashtag.ex
@@ -6,14 +6,17 @@ defmodule Pleroma.Hashtag do
   use Ecto.Schema
 
   import Ecto.Changeset
+  import Ecto.Query
 
+  alias Ecto.Multi
   alias Pleroma.Hashtag
+  alias Pleroma.Object
   alias Pleroma.Repo
 
   schema "hashtags" do
     field(:name, :string)
 
-    many_to_many(:objects, Pleroma.Object, join_through: "hashtags_objects", on_replace: :delete)
+    many_to_many(:objects, Object, join_through: "hashtags_objects", on_replace: :delete)
 
     timestamps()
   end
@@ -34,15 +37,27 @@ def get_or_create_by_name(name) when is_bitstring(name) do
   end
 
   def get_or_create_by_names(names) when is_list(names) do
-    Enum.reduce_while(names, {:ok, []}, fn name, {:ok, list} ->
-      case get_or_create_by_name(name) do
-        {:ok, %Hashtag{} = hashtag} ->
-          {:cont, {:ok, list ++ [hashtag]}}
+    timestamp = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
 
-        error ->
-          {:halt, error}
-      end
-    end)
+    structs =
+      Enum.map(names, fn name ->
+        %Hashtag{}
+        |> changeset(%{name: name})
+        |> Map.get(:changes)
+        |> Map.merge(%{inserted_at: timestamp, updated_at: timestamp})
+      end)
+
+    with {:ok, %{query_op: hashtags}} <-
+           Multi.new()
+           |> Multi.insert_all(:insert_all_op, Hashtag, structs, on_conflict: :nothing)
+           |> Multi.run(:query_op, fn _repo, _changes ->
+             {:ok, Repo.all(from(ht in Hashtag, where: ht.name in ^names))}
+           end)
+           |> Repo.transaction() do
+      {:ok, hashtags}
+    else
+      {:error, _name, value, _changes_so_far} -> {:error, value}
+    end
   end
 
   def changeset(%Hashtag{} = struct, params) do
@@ -52,4 +67,29 @@ def changeset(%Hashtag{} = struct, params) do
     |> validate_required([:name])
     |> unique_constraint(:name)
   end
+
+  def unlink(%Object{id: object_id}) do
+    with {_, hashtag_ids} <-
+           from(hto in "hashtags_objects",
+             where: hto.object_id == ^object_id,
+             select: hto.hashtag_id
+           )
+           |> Repo.delete_all() do
+      delete_unreferenced(hashtag_ids)
+    end
+  end
+
+  @delete_unreferenced_query """
+  DELETE FROM hashtags WHERE id IN
+    (SELECT hashtags.id FROM hashtags
+      LEFT OUTER JOIN hashtags_objects
+        ON hashtags_objects.hashtag_id = hashtags.id
+      WHERE hashtags_objects.hashtag_id IS NULL AND hashtags.id = ANY($1));
+  """
+
+  def delete_unreferenced(ids) do
+    with {:ok, %{num_rows: deleted_count}} <- Repo.query(@delete_unreferenced_query, [ids]) do
+      {:ok, deleted_count}
+    end
+  end
 end
diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index 9a036e0b2..c53f6be12 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -74,16 +74,15 @@ def handle_continue(:init_state, _state) do
   def handle_info(:migrate_hashtags, state) do
     State.clear()
 
-    data_migration = data_migration()
+    update_status(:running)
+    put_stat(:started_at, NaiveDateTime.utc_now())
 
+    data_migration = data_migration()
     persistent_data = Map.take(data_migration.data, ["max_processed_id"])
 
     {:ok, data_migration} =
       DataMigration.update(data_migration, %{state: :running, data: persistent_data})
 
-    update_status(:running)
-    put_stat(:started_at, NaiveDateTime.utc_now())
-
     Logger.info("Starting transferring object embedded hashtags to `hashtags` table...")
 
     max_processed_id = data_migration.data["max_processed_id"] || 0
@@ -137,6 +136,8 @@ def handle_info(:migrate_hashtags, state) do
     |> Stream.run()
 
     with 0 <- failures_count(data_migration.id) do
+      _ = delete_non_create_activities_hashtags()
+
       {:ok, data_migration} = DataMigration.update_state(data_migration, :complete)
 
       handle_success(data_migration)
@@ -150,9 +151,37 @@ def handle_info(:migrate_hashtags, state) do
     {:noreply, state}
   end
 
+  @hashtags_objects_cleanup_query """
+  DELETE FROM hashtags_objects WHERE object_id IN
+    (SELECT DISTINCT objects.id FROM objects
+      JOIN hashtags_objects ON hashtags_objects.object_id = objects.id LEFT JOIN activities
+        ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') =
+          (objects.data->>'id')
+        AND activities.data->>'type' = 'Create'
+      WHERE activities.id IS NULL);
+  """
+
+  @hashtags_cleanup_query """
+  DELETE FROM hashtags WHERE id IN
+    (SELECT hashtags.id FROM hashtags
+      LEFT OUTER JOIN hashtags_objects
+        ON hashtags_objects.hashtag_id = hashtags.id
+      WHERE hashtags_objects.hashtag_id IS NULL);
+  """
+
+  def delete_non_create_activities_hashtags do
+    {:ok, %{num_rows: hashtags_objects_count}} =
+      Repo.query(@hashtags_objects_cleanup_query, [], timeout: :infinity)
+
+    {:ok, %{num_rows: hashtags_count}} =
+      Repo.query(@hashtags_cleanup_query, [], timeout: :infinity)
+
+    {:ok, hashtags_objects_count, hashtags_count}
+  end
+
   defp query do
     # Note: most objects have Mention-type AS2 tags and no hashtags (but we can't filter them out)
-    # Note: not checking activity type; HashtagsCleanupWorker should clean up unused records later
+    # Note: not checking activity type, expecting remove_non_create_objects_hashtags/_ to clean up
     from(
       object in Object,
       where:
@@ -182,25 +211,20 @@ defp transfer_object_hashtags(object) do
   defp transfer_object_hashtags(object, hashtags) do
     Repo.transaction(fn ->
       with {:ok, hashtag_records} <- Hashtag.get_or_create_by_names(hashtags) do
-        for hashtag_record <- hashtag_records do
-          with {:ok, _} <-
-                 Repo.query(
-                   "insert into hashtags_objects(hashtag_id, object_id) values ($1, $2);",
-                   [hashtag_record.id, object.id]
-                 ) do
-            nil
-          else
-            {:error, e} ->
-              error =
-                "ERROR: could not link object #{object.id} and hashtag " <>
-                  "#{hashtag_record.id}: #{inspect(e)}"
+        maps = Enum.map(hashtag_records, &%{hashtag_id: &1.id, object_id: object.id})
+        expected_rows = length(hashtag_records)
 
-              Logger.error(error)
-              Repo.rollback(object.id)
-          end
+        with {^expected_rows, _} <- Repo.insert_all("hashtags_objects", maps) do
+          object.id
+        else
+          e ->
+            error =
+              "ERROR when inserting #{expected_rows} hashtags_objects " <>
+                "for object #{object.id}: #{inspect(e)}"
+
+            Logger.error(error)
+            Repo.rollback(object.id)
         end
-
-        object.id
       else
         e ->
           error = "ERROR: could not create hashtags for object #{object.id}: #{inspect(e)}"
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 52b77e41c..3ba749d1a 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -62,27 +62,30 @@ def change(struct, params \\ %{}) do
     |> cast(params, [:data])
     |> validate_required([:data])
     |> unique_constraint(:ap_id, name: :objects_unique_apid_index)
+    # Expecting `maybe_handle_hashtags_change/1` to run last:
     |> maybe_handle_hashtags_change(struct)
   end
 
-  # Note: not checking activity type; HashtagsCleanupWorker should clean up unused records later
+  # Note: not checking activity type (assuming non-legacy objects are associated with Create act.)
   defp maybe_handle_hashtags_change(changeset, struct) do
-    with data_hashtags_change = get_change(changeset, :data),
-         true <- hashtags_changed?(struct, data_hashtags_change),
+    with %Ecto.Changeset{valid?: true} <- changeset,
+         data_hashtags_change = get_change(changeset, :data),
+         {_, true} <- {:changed, hashtags_changed?(struct, data_hashtags_change)},
          {:ok, hashtag_records} <-
            data_hashtags_change
            |> object_data_hashtags()
            |> Hashtag.get_or_create_by_names() do
       put_assoc(changeset, :hashtags, hashtag_records)
     else
-      false ->
+      %{valid?: false} ->
         changeset
 
-      {:error, hashtag_changeset} ->
-        failed_hashtag = get_field(hashtag_changeset, :name)
+      {:changed, false} ->
+        changeset
 
+      {:error, _} ->
         validate_change(changeset, :data, fn _, _ ->
-          [data: "error referencing hashtag: #{failed_hashtag}"]
+          [data: "error referencing hashtags"]
         end)
     end
   end
@@ -221,9 +224,13 @@ def make_tombstone(%Object{data: %{"id" => id, "type" => type}}, deleted \\ Date
   def swap_object_with_tombstone(object) do
     tombstone = make_tombstone(object)
 
-    object
-    |> Object.change(%{data: tombstone})
-    |> Repo.update()
+    with {:ok, object} <-
+           object
+           |> Object.change(%{data: tombstone})
+           |> Repo.update() do
+      Hashtag.unlink(object)
+      {:ok, object}
+    end
   end
 
   def delete(%Object{data: %{"id" => id}} = object) do
diff --git a/lib/pleroma/workers/cron/hashtags_cleanup_worker.ex b/lib/pleroma/workers/cron/hashtags_cleanup_worker.ex
deleted file mode 100644
index b319067ca..000000000
--- a/lib/pleroma/workers/cron/hashtags_cleanup_worker.ex
+++ /dev/null
@@ -1,57 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Workers.Cron.HashtagsCleanupWorker do
-  @moduledoc """
-  The worker to clean up unused hashtags_objects and hashtags.
-  """
-
-  use Oban.Worker, queue: "hashtags_cleanup"
-
-  alias Pleroma.Repo
-
-  require Logger
-
-  @hashtags_objects_query """
-  DELETE FROM hashtags_objects WHERE object_id IN
-    (SELECT DISTINCT objects.id FROM objects
-      JOIN hashtags_objects ON hashtags_objects.object_id = objects.id LEFT JOIN activities
-        ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') =
-          (objects.data->>'id')
-        AND activities.data->>'type' = 'Create'
-      WHERE activities.id IS NULL);
-  """
-
-  @hashtags_query """
-  DELETE FROM hashtags WHERE id IN
-    (SELECT hashtags.id FROM hashtags
-      LEFT OUTER JOIN hashtags_objects
-        ON hashtags_objects.hashtag_id = hashtags.id
-      WHERE hashtags_objects.hashtag_id IS NULL AND hashtags.inserted_at < $1);
-  """
-
-  @impl Oban.Worker
-  def perform(_job) do
-    Logger.info("Cleaning up unused `hashtags_objects` records...")
-
-    {:ok, %{num_rows: hashtags_objects_count}} =
-      Repo.query(@hashtags_objects_query, [], timeout: :infinity)
-
-    Logger.info("Deleted #{hashtags_objects_count} unused `hashtags_objects` records.")
-
-    Logger.info("Cleaning up unused `hashtags` records...")
-
-    # Note: ignoring recently created hashtags since references are added after hashtag is created
-    {:ok, %{num_rows: hashtags_count}} =
-      Repo.query(@hashtags_query, [NaiveDateTime.add(NaiveDateTime.utc_now(), -3600 * 24)],
-        timeout: :infinity
-      )
-
-    Logger.info("Deleted #{hashtags_count} unused `hashtags` records.")
-
-    Logger.info("HashtagsCleanupWorker complete.")
-
-    :ok
-  end
-end

From 349b8b0f4fb1c2b86f913e1840f15c052ff43c24 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Sat, 13 Feb 2021 22:01:11 +0300
Subject: [PATCH 042/339] [#3213] `rescue` around potentially-raising
 `Repo.insert_all/_` calls. Misc. improvements (docs etc.).

---
 CHANGELOG.md                                  |  2 +-
 config/config.exs                             |  1 -
 config/description.exs                        | 14 +++++++++
 docs/configuration/cheatsheet.md              |  6 ++++
 lib/pleroma/hashtag.ex                        | 29 +++++++++++--------
 .../migrators/hashtags_table_migrator.ex      | 21 +++++++++-----
 6 files changed, 51 insertions(+), 22 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 23567a97c..a7b5f6ac0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,7 +33,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Admin API: Reports now ordered by newest
 
 </details>
-- Extracted object hashtags into separate table in order to improve hashtag timeline performance (via background migration in `Pleroma.Migrators.HashtagsTableMigrator`). 
+- Improved hashtag timeline performance (requires a background migration). 
 
 ### Added
 
diff --git a/config/config.exs b/config/config.exs
index 8a7c466d3..0fbca06f3 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -556,7 +556,6 @@
     remote_fetcher: 2,
     attachments_cleanup: 1,
     new_users_digest: 1,
-    hashtags_cleanup: 1,
     mute_expire: 5
   ],
   plugins: [Oban.Plugins.Pruner],
diff --git a/config/description.exs b/config/description.exs
index 2e96024f5..29fc5fbd4 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -473,6 +473,20 @@
       }
     ]
   },
+  %{
+    group: :pleroma,
+    key: :populate_hashtags_table,
+    type: :group,
+    description: "`populate_hashtags_table` background migration settings",
+    children: [
+      %{
+        key: :sleep_interval_ms,
+        type: :integer,
+        description:
+          "Sleep interval between each chunk of processed records in order to decrease the load on the system (defaults to 0 and should be keep default on most instances)."
+      }
+    ]
+  },
   %{
     group: :pleroma,
     key: :instance,
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index ad5768465..68a5a3c7f 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -65,6 +65,12 @@ To add configuration to your config file, you can copy it from the base config.
 * `show_reactions`: Let favourites and emoji reactions be viewed through the API (default: `true`).
 * `password_reset_token_validity`: The time after which reset tokens aren't accepted anymore, in seconds (default: one day).
 
+## :database
+* `improved_hashtag_timeline`: If `true`, hashtags will be fetched from `hashtags` table for hashtags timeline. When `false`, object-embedded hashtags will be used (slower). Is auto-set to `true` (unless overridden) when `HashtagsTableMigrator` completes.
+
+## Background migrations
+* `populate_hashtags_table/sleep_interval_ms`: Sleep interval between each chunk of processed records in order to decrease the load on the system (defaults to 0 and should be keep default on most instances).
+
 ## Welcome
 * `direct_message`: - welcome message sent as a direct message.
   * `enabled`: Enables the send a direct message to a newly registered user. Defaults to `false`.
diff --git a/lib/pleroma/hashtag.ex b/lib/pleroma/hashtag.ex
index de52c4dae..0d6a4d09e 100644
--- a/lib/pleroma/hashtag.ex
+++ b/lib/pleroma/hashtag.ex
@@ -47,16 +47,20 @@ def get_or_create_by_names(names) when is_list(names) do
         |> Map.merge(%{inserted_at: timestamp, updated_at: timestamp})
       end)
 
-    with {:ok, %{query_op: hashtags}} <-
-           Multi.new()
-           |> Multi.insert_all(:insert_all_op, Hashtag, structs, on_conflict: :nothing)
-           |> Multi.run(:query_op, fn _repo, _changes ->
-             {:ok, Repo.all(from(ht in Hashtag, where: ht.name in ^names))}
-           end)
-           |> Repo.transaction() do
-      {:ok, hashtags}
-    else
-      {:error, _name, value, _changes_so_far} -> {:error, value}
+    try do
+      with {:ok, %{query_op: hashtags}} <-
+             Multi.new()
+             |> Multi.insert_all(:insert_all_op, Hashtag, structs, on_conflict: :nothing)
+             |> Multi.run(:query_op, fn _repo, _changes ->
+               {:ok, Repo.all(from(ht in Hashtag, where: ht.name in ^names))}
+             end)
+             |> Repo.transaction() do
+        {:ok, hashtags}
+      else
+        {:error, _name, value, _changes_so_far} -> {:error, value}
+      end
+    rescue
+      e -> {:error, e}
     end
   end
 
@@ -74,8 +78,9 @@ def unlink(%Object{id: object_id}) do
              where: hto.object_id == ^object_id,
              select: hto.hashtag_id
            )
-           |> Repo.delete_all() do
-      delete_unreferenced(hashtag_ids)
+           |> Repo.delete_all(),
+         {:ok, unreferenced_count} <- delete_unreferenced(hashtag_ids) do
+      {:ok, length(hashtag_ids), unreferenced_count}
     end
   end
 
diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index c53f6be12..432c3401a 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -214,15 +214,20 @@ defp transfer_object_hashtags(object, hashtags) do
         maps = Enum.map(hashtag_records, &%{hashtag_id: &1.id, object_id: object.id})
         expected_rows = length(hashtag_records)
 
-        with {^expected_rows, _} <- Repo.insert_all("hashtags_objects", maps) do
-          object.id
-        else
-          e ->
-            error =
-              "ERROR when inserting #{expected_rows} hashtags_objects " <>
-                "for object #{object.id}: #{inspect(e)}"
+        base_error =
+          "ERROR when inserting #{expected_rows} hashtags_objects for obj. #{object.id}"
 
-            Logger.error(error)
+        try do
+          with {^expected_rows, _} <- Repo.insert_all("hashtags_objects", maps) do
+            object.id
+          else
+            e ->
+              Logger.error("#{base_error}: #{inspect(e)}")
+              Repo.rollback(object.id)
+          end
+        rescue
+          e ->
+            Logger.error("#{base_error}: #{inspect(e)}")
             Repo.rollback(object.id)
         end
       else

From 1dac7d14623f36744953a523650211540d90d1fc Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Mon, 15 Feb 2021 21:13:14 +0300
Subject: [PATCH 043/339] [#3213] Fixed `hashtags.name` lookup (must use
 `citext` type to do index scan). Fixed embedded hashtags lookup
 (lowercasing), adjusted tests.

---
 lib/pleroma/hashtag.ex                        |  8 +++++--
 lib/pleroma/web/activity_pub/activity_pub.ex  | 22 ++++++++++++++-----
 .../web/activity_pub/activity_pub_test.exs    | 18 +++++++--------
 3 files changed, 31 insertions(+), 17 deletions(-)

diff --git a/lib/pleroma/hashtag.ex b/lib/pleroma/hashtag.ex
index 0d6a4d09e..a6d033816 100644
--- a/lib/pleroma/hashtag.ex
+++ b/lib/pleroma/hashtag.ex
@@ -22,7 +22,9 @@ defmodule Pleroma.Hashtag do
   end
 
   def get_by_name(name) do
-    Repo.get_by(Hashtag, name: name)
+    from(h in Hashtag)
+    |> where([h], fragment("name = ?::citext", ^String.downcase(name)))
+    |> Repo.one()
   end
 
   def get_or_create_by_name(name) when is_bitstring(name) do
@@ -37,6 +39,7 @@ def get_or_create_by_name(name) when is_bitstring(name) do
   end
 
   def get_or_create_by_names(names) when is_list(names) do
+    names = Enum.map(names, &String.downcase/1)
     timestamp = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
 
     structs =
@@ -52,7 +55,8 @@ def get_or_create_by_names(names) when is_list(names) do
              Multi.new()
              |> Multi.insert_all(:insert_all_op, Hashtag, structs, on_conflict: :nothing)
              |> Multi.run(:query_op, fn _repo, _changes ->
-               {:ok, Repo.all(from(ht in Hashtag, where: ht.name in ^names))}
+               {:ok,
+                Repo.all(from(ht in Hashtag, where: ht.name in fragment("?::citext[]", ^names)))}
              end)
              |> Repo.transaction() do
         {:ok, hashtags}
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 9623e635a..e012f2779 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -698,6 +698,8 @@ defp restrict_embedded_tag_all(_query, %{tag_all: _tag_all, skip_preload: true})
   end
 
   defp restrict_embedded_tag_all(query, %{tag_all: [_ | _] = tag_all}) do
+    tag_all = Enum.map(tag_all, &String.downcase/1)
+
     from(
       [_activity, object] in query,
       where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)
@@ -714,10 +716,12 @@ defp restrict_embedded_tag_any(_query, %{tag: _tag, skip_preload: true}) do
     raise_on_missing_preload()
   end
 
-  defp restrict_embedded_tag_any(query, %{tag: [_ | _] = tag}) do
+  defp restrict_embedded_tag_any(query, %{tag: [_ | _] = tag_any}) do
+    tag_any = Enum.map(tag_any, &String.downcase/1)
+
     from(
       [_activity, object] in query,
-      where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag)
+      where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag_any)
     )
   end
 
@@ -732,6 +736,8 @@ defp restrict_embedded_tag_reject_any(_query, %{tag_reject: _tag_reject, skip_pr
   end
 
   defp restrict_embedded_tag_reject_any(query, %{tag_reject: [_ | _] = tag_reject}) do
+    tag_reject = Enum.map(tag_reject, &String.downcase/1)
+
     from(
       [_activity, object] in query,
       where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
@@ -749,6 +755,10 @@ defp restrict_hashtag_all(_query, %{tag_all: _tag, skip_preload: true}) do
     raise_on_missing_preload()
   end
 
+  defp restrict_hashtag_all(query, %{tag_all: [single_tag]}) do
+    restrict_hashtag_any(query, %{tag: single_tag})
+  end
+
   defp restrict_hashtag_all(query, %{tag_all: [_ | _] = tags}) do
     from(
       [_activity, object] in query,
@@ -756,7 +766,7 @@ defp restrict_hashtag_all(query, %{tag_all: [_ | _] = tags}) do
         fragment(
           """
           (SELECT array_agg(hashtags.name) FROM hashtags JOIN hashtags_objects
-            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
+            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?::citext[])
               AND hashtags_objects.object_id = ?) @> ?
           """,
           ^tags,
@@ -767,7 +777,7 @@ defp restrict_hashtag_all(query, %{tag_all: [_ | _] = tags}) do
   end
 
   defp restrict_hashtag_all(query, %{tag_all: tag}) when is_binary(tag) do
-    restrict_hashtag_any(query, %{tag: tag})
+    restrict_hashtag_all(query, %{tag_all: [tag]})
   end
 
   defp restrict_hashtag_all(query, _), do: query
@@ -783,7 +793,7 @@ defp restrict_hashtag_any(query, %{tag: [_ | _] = tags}) do
         fragment(
           """
           EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
-            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
+            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?::citext[])
               AND hashtags_objects.object_id = ? LIMIT 1)
           """,
           ^tags,
@@ -809,7 +819,7 @@ defp restrict_hashtag_reject_any(query, %{tag_reject: [_ | _] = tags_reject}) do
         fragment(
           """
           NOT EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
-            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
+            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?::citext[])
               AND hashtags_objects.object_id = ? LIMIT 1)
           """,
           ^tags_reject,
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index bab5a199c..c41c8a5dd 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -213,24 +213,24 @@ test "works for guppe actors" do
   test "it fetches the appropriate tag-restricted posts" do
     user = insert(:user)
 
-    {:ok, status_one} = CommonAPI.post(user, %{status: ". #test"})
+    {:ok, status_one} = CommonAPI.post(user, %{status: ". #TEST"})
     {:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"})
-    {:ok, status_three} = CommonAPI.post(user, %{status: ". #test #reject"})
+    {:ok, status_three} = CommonAPI.post(user, %{status: ". #test #Reject"})
 
-    {:ok, status_four} = CommonAPI.post(user, %{status: ". #any1 #any2"})
-    {:ok, status_five} = CommonAPI.post(user, %{status: ". #any2 #any1"})
+    {:ok, status_four} = CommonAPI.post(user, %{status: ". #Any1 #any2"})
+    {:ok, status_five} = CommonAPI.post(user, %{status: ". #Any2 #any1"})
 
     for hashtag_timeline_strategy <- [true, false] do
       clear_config([:database, :improved_hashtag_timeline], hashtag_timeline_strategy)
 
       fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})
 
-      fetch_two = ActivityPub.fetch_activities([], %{type: "Create", tag: ["test", "essais"]})
+      fetch_two = ActivityPub.fetch_activities([], %{type: "Create", tag: ["TEST", "essais"]})
 
       fetch_three =
         ActivityPub.fetch_activities([], %{
           type: "Create",
-          tag: ["test", "essais"],
+          tag: ["test", "Essais"],
           tag_reject: ["reject"]
         })
 
@@ -238,21 +238,21 @@ test "it fetches the appropriate tag-restricted posts" do
         ActivityPub.fetch_activities([], %{
           type: "Create",
           tag: ["test"],
-          tag_all: ["test", "reject"]
+          tag_all: ["test", "REJECT"]
         })
 
       # Testing that deduplication (if needed) is done on DB (not Ecto) level; :limit is important
       fetch_five =
         ActivityPub.fetch_activities([], %{
           type: "Create",
-          tag: ["any1", "any2"],
+          tag: ["ANY1", "any2"],
           limit: 2
         })
 
       fetch_six =
         ActivityPub.fetch_activities([], %{
           type: "Create",
-          tag: ["any1", "any2"],
+          tag: ["any1", "Any2"],
           tag_all: [],
           tag_reject: []
         })

From 938823c73040f6b55896581daf5baf732f859f02 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Tue, 16 Feb 2021 23:14:15 +0300
Subject: [PATCH 044/339] [#3213] HashtagsTableMigrator state management
 refactoring & improvements (proper stats serialization etc.).

---
 lib/pleroma/data_migration.ex                 | 15 ++--
 .../migrators/hashtags_table_migrator.ex      | 87 ++++++++-----------
 .../hashtags_table_migrator/state.ex          | 87 ++++++++++++++++---
 3 files changed, 122 insertions(+), 67 deletions(-)

diff --git a/lib/pleroma/data_migration.ex b/lib/pleroma/data_migration.ex
index 64fa155ff..1377af16e 100644
--- a/lib/pleroma/data_migration.ex
+++ b/lib/pleroma/data_migration.ex
@@ -10,6 +10,7 @@ defmodule Pleroma.DataMigration do
   alias Pleroma.Repo
 
   import Ecto.Changeset
+  import Ecto.Query
 
   schema "data_migrations" do
     field(:name, :string)
@@ -28,14 +29,12 @@ def changeset(data_migration, params \\ %{}) do
     |> unique_constraint(:name)
   end
 
-  def update(data_migration, params \\ %{}) do
-    data_migration
-    |> changeset(params)
-    |> Repo.update()
-  end
-
-  def update_state(data_migration, new_state) do
-    update(data_migration, %{state: new_state})
+  def update_one_by_id(id, params \\ %{}) do
+    with {1, _} <-
+           from(dm in DataMigration, where: dm.id == ^id)
+           |> Repo.update_all(set: params) do
+      :ok
+    end
   end
 
   def get_by_name(name) do
diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index 432c3401a..a226d9d29 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -11,16 +11,16 @@ defmodule Pleroma.Migrators.HashtagsTableMigrator do
 
   alias __MODULE__.State
   alias Pleroma.Config
-  alias Pleroma.DataMigration
   alias Pleroma.Hashtag
   alias Pleroma.Object
   alias Pleroma.Repo
 
-  defdelegate state(), to: State, as: :get
-  defdelegate put_stat(key, value), to: State, as: :put
-  defdelegate increment_stat(key, increment), to: State, as: :increment
+  defdelegate data_migration(), to: State
 
-  defdelegate data_migration(), to: DataMigration, as: :populate_hashtags_table
+  defdelegate state(), to: State
+  defdelegate get_stat(key, value), to: State, as: :get_data_key
+  defdelegate put_stat(key, value), to: State, as: :put_data_key
+  defdelegate increment_stat(key, increment), to: State, as: :increment_data_key
 
   @reg_name {:global, __MODULE__}
 
@@ -45,7 +45,7 @@ def init(_) do
   def handle_continue(:init_state, _state) do
     {:ok, _} = State.start_link(nil)
 
-    update_status(:init)
+    update_status(:pending)
 
     data_migration = data_migration()
     manual_migrations = Config.get([:instance, :manual_data_migrations], [])
@@ -55,13 +55,13 @@ def handle_continue(:init_state, _state) do
         update_status(:noop)
 
       is_nil(data_migration) ->
-        update_status(:halt, "Data migration does not exist.")
+        update_status(:failed, "Data migration does not exist.")
 
       data_migration.state == :manual or data_migration.name in manual_migrations ->
-        update_status(:noop, "Data migration is in manual execution state.")
+        update_status(:manual, "Data migration is in manual execution state.")
 
       data_migration.state == :complete ->
-        handle_success(data_migration)
+        on_complete(data_migration)
 
       true ->
         send(self(), :migrate_hashtags)
@@ -72,20 +72,15 @@ def handle_continue(:init_state, _state) do
 
   @impl true
   def handle_info(:migrate_hashtags, state) do
-    State.clear()
+    State.reinit()
 
     update_status(:running)
     put_stat(:started_at, NaiveDateTime.utc_now())
 
-    data_migration = data_migration()
-    persistent_data = Map.take(data_migration.data, ["max_processed_id"])
+    %{id: data_migration_id} = data_migration()
+    max_processed_id = get_stat(:max_processed_id, 0)
 
-    {:ok, data_migration} =
-      DataMigration.update(data_migration, %{state: :running, data: persistent_data})
-
-    Logger.info("Starting transferring object embedded hashtags to `hashtags` table...")
-
-    max_processed_id = data_migration.data["max_processed_id"] || 0
+    Logger.info("Transferring embedded hashtags to `hashtags` (from oid: #{max_processed_id})...")
 
     query()
     |> where([object], object.id > ^max_processed_id)
@@ -104,7 +99,7 @@ def handle_info(:migrate_hashtags, state) do
           Repo.query(
             "INSERT INTO data_migration_failed_ids(data_migration_id, record_id) " <>
               "VALUES ($1, $2) ON CONFLICT DO NOTHING;",
-            [data_migration.id, failed_id]
+            [data_migration_id, failed_id]
           )
       end
 
@@ -112,7 +107,7 @@ def handle_info(:migrate_hashtags, state) do
         Repo.query(
           "DELETE FROM data_migration_failed_ids " <>
             "WHERE data_migration_id = $1 AND record_id = ANY($2)",
-          [data_migration.id, object_ids -- failed_ids]
+          [data_migration_id, object_ids -- failed_ids]
         )
 
       max_object_id = Enum.at(object_ids, -1)
@@ -120,14 +115,8 @@ def handle_info(:migrate_hashtags, state) do
       put_stat(:max_processed_id, max_object_id)
       increment_stat(:processed_count, length(object_ids))
       increment_stat(:failed_count, length(failed_ids))
-
-      put_stat(
-        :records_per_second,
-        state()[:processed_count] /
-          Enum.max([NaiveDateTime.diff(NaiveDateTime.utc_now(), state()[:started_at]), 1])
-      )
-
-      persist_stats(data_migration)
+      put_stat(:records_per_second, records_per_second())
+      _ = State.persist_to_db()
 
       # A quick and dirty approach to controlling the load this background migration imposes
       sleep_interval = Config.get([:populate_hashtags_table, :sleep_interval_ms], 0)
@@ -135,22 +124,25 @@ def handle_info(:migrate_hashtags, state) do
     end)
     |> Stream.run()
 
-    with 0 <- failures_count(data_migration.id) do
+    with 0 <- failures_count(data_migration_id) do
       _ = delete_non_create_activities_hashtags()
-
-      {:ok, data_migration} = DataMigration.update_state(data_migration, :complete)
-
-      handle_success(data_migration)
+      set_complete()
     else
       _ ->
-        _ = DataMigration.update_state(data_migration, :failed)
-
         update_status(:failed, "Please check data_migration_failed_ids records.")
     end
 
     {:noreply, state}
   end
 
+  defp records_per_second do
+    get_stat(:processed_count, 0) / Enum.max([running_time(), 1])
+  end
+
+  defp running_time do
+    NaiveDateTime.diff(NaiveDateTime.utc_now(), get_stat(:started_at, NaiveDateTime.utc_now()))
+  end
+
   @hashtags_objects_cleanup_query """
   DELETE FROM hashtags_objects WHERE object_id IN
     (SELECT DISTINCT objects.id FROM objects
@@ -169,6 +161,10 @@ def handle_info(:migrate_hashtags, state) do
       WHERE hashtags_objects.hashtag_id IS NULL);
   """
 
+  @doc """
+  Deletes `hashtags_objects` for legacy objects not asoociated with Create activity.
+  Also deletes unreferenced `hashtags` records (might occur after deletion of `hashtags_objects`).
+  """
   def delete_non_create_activities_hashtags do
     {:ok, %{num_rows: hashtags_objects_count}} =
       Repo.query(@hashtags_objects_cleanup_query, [], timeout: :infinity)
@@ -256,14 +252,7 @@ def count(force \\ false, timeout \\ :infinity) do
     end
   end
 
-  defp persist_stats(data_migration) do
-    runner_state = Map.drop(state(), [:status])
-    _ = DataMigration.update(data_migration, %{data: runner_state})
-  end
-
-  defp handle_success(data_migration) do
-    update_status(:complete)
-
+  defp on_complete(data_migration) do
     cond do
       data_migration.feature_lock ->
         :noop
@@ -321,18 +310,18 @@ def force_continue do
   end
 
   def force_restart do
-    {:ok, _} = DataMigration.update(data_migration(), %{state: :pending, data: %{}})
+    :ok = State.reset()
     force_continue()
   end
 
-  def force_complete do
-    {:ok, data_migration} = DataMigration.update_state(data_migration(), :complete)
-
-    handle_success(data_migration)
+  def set_complete do
+    update_status(:complete)
+    _ = State.persist_to_db()
+    on_complete(data_migration())
   end
 
   defp update_status(status, message \\ nil) do
-    put_stat(:status, status)
+    put_stat(:state, status)
     put_stat(:message, message)
   end
 end
diff --git a/lib/pleroma/migrators/hashtags_table_migrator/state.ex b/lib/pleroma/migrators/hashtags_table_migrator/state.ex
index 901563426..ed9848824 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator/state.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator/state.ex
@@ -5,31 +5,98 @@
 defmodule Pleroma.Migrators.HashtagsTableMigrator.State do
   use Agent
 
-  @init_state %{}
+  alias Pleroma.DataMigration
+
+  defdelegate data_migration(), to: DataMigration, as: :populate_hashtags_table
+
   @reg_name {:global, __MODULE__}
 
   def start_link(_) do
-    Agent.start_link(fn -> @init_state end, name: @reg_name)
+    Agent.start_link(fn -> load_state_from_db() end, name: @reg_name)
   end
 
-  def clear do
-    Agent.update(@reg_name, fn _state -> @init_state end)
+  defp load_state_from_db do
+    data_migration = data_migration()
+
+    data =
+      if data_migration do
+        Map.new(data_migration.data, fn {k, v} -> {String.to_atom(k), v} end)
+      else
+        %{}
+      end
+
+    %{
+      data_migration_id: data_migration && data_migration.id,
+      data: data
+    }
   end
 
-  def get do
+  def persist_to_db do
+    %{data_migration_id: data_migration_id, data: data} = state()
+
+    if data_migration_id do
+      DataMigration.update_one_by_id(data_migration_id, data: data)
+    else
+      {:error, :nil_data_migration_id}
+    end
+  end
+
+  def reset do
+    %{data_migration_id: data_migration_id} = state()
+
+    with false <- is_nil(data_migration_id),
+         :ok <-
+           DataMigration.update_one_by_id(data_migration_id,
+             state: :pending,
+             data: %{}
+           ) do
+      reinit()
+    else
+      true -> {:error, :nil_data_migration_id}
+      e -> e
+    end
+  end
+
+  def reinit do
+    Agent.update(@reg_name, fn _state -> load_state_from_db() end)
+  end
+
+  def state do
     Agent.get(@reg_name, & &1)
   end
 
-  def put(key, value) do
+  def get_data_key(key, default \\ nil) do
+    get_in(state(), [:data, key]) || default
+  end
+
+  def put_data_key(key, value) do
+    _ = persist_non_data_change(key, value)
+
     Agent.update(@reg_name, fn state ->
-      Map.put(state, key, value)
+      put_in(state, [:data, key], value)
     end)
   end
 
-  def increment(key, increment \\ 1) do
+  def increment_data_key(key, increment \\ 1) do
     Agent.update(@reg_name, fn state ->
-      updated_value = (state[key] || 0) + increment
-      Map.put(state, key, updated_value)
+      initial_value = get_in(state, [:data, key]) || 0
+      updated_value = initial_value + increment
+      put_in(state, [:data, key], updated_value)
     end)
   end
+
+  defp persist_non_data_change(:state, value) do
+    with true <- get_data_key(:state) != value,
+         true <- value in Pleroma.DataMigration.State.__valid_values__(),
+         %{data_migration_id: data_migration_id} when not is_nil(data_migration_id) <- state() do
+      DataMigration.update_one_by_id(data_migration_id, state: value)
+    else
+      false -> :ok
+      _ -> {:error, :nil_data_migration_id}
+    end
+  end
+
+  defp persist_non_data_change(_, _) do
+    nil
+  end
 end

From 854ea1aefb5ff4e03e9e9af6e8dd50f66c61c913 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Wed, 17 Feb 2021 09:23:35 +0300
Subject: [PATCH 045/339] [#3213] Fixed `HashtagsTableMigrator.count/1`.

---
 lib/pleroma/migrators/hashtags_table_migrator.ex | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index a226d9d29..ac17f91cc 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -18,7 +18,8 @@ defmodule Pleroma.Migrators.HashtagsTableMigrator do
   defdelegate data_migration(), to: State
 
   defdelegate state(), to: State
-  defdelegate get_stat(key, value), to: State, as: :get_data_key
+  defdelegate persist_state(), to: State, as: :persist_to_db
+  defdelegate get_stat(key, value \\ nil), to: State, as: :get_data_key
   defdelegate put_stat(key, value), to: State, as: :put_data_key
   defdelegate increment_stat(key, increment), to: State, as: :increment_data_key
 
@@ -116,7 +117,7 @@ def handle_info(:migrate_hashtags, state) do
       increment_stat(:processed_count, length(object_ids))
       increment_stat(:failed_count, length(failed_ids))
       put_stat(:records_per_second, records_per_second())
-      _ = State.persist_to_db()
+      persist_state()
 
       # A quick and dirty approach to controlling the load this background migration imposes
       sleep_interval = Config.get([:populate_hashtags_table, :sleep_interval_ms], 0)
@@ -237,17 +238,19 @@ defp transfer_object_hashtags(object, hashtags) do
 
   @doc "Approximate count for current iteration (including processed records count)"
   def count(force \\ false, timeout \\ :infinity) do
-    stored_count = state()[:count]
+    stored_count = get_stat(:count)
 
     if stored_count && !force do
       stored_count
     else
-      processed_count = state()[:processed_count] || 0
-      max_processed_id = data_migration().data["max_processed_id"] || 0
+      processed_count = get_stat(:processed_count, 0)
+      max_processed_id = get_stat(:max_processed_id, 0)
       query = where(query(), [object], object.id > ^max_processed_id)
 
       count = Repo.aggregate(query, :count, :id, timeout: timeout) + processed_count
       put_stat(:count, count)
+      persist_state()
+
       count
     end
   end
@@ -316,7 +319,7 @@ def force_restart do
 
   def set_complete do
     update_status(:complete)
-    _ = State.persist_to_db()
+    persist_state()
     on_complete(data_migration())
   end
 

From b981edad8a7d8f27b231bc6164fc0546efbdb646 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Thu, 18 Feb 2021 20:40:10 +0300
Subject: [PATCH 046/339] [#3213] HashtagsTableMigrator: fault rate allowance
 to enable the feature (defaults to 1%), counting of affected objects, misc.
 tweaks.

---
 config/config.exs                             |   2 +
 config/description.exs                        |   7 ++
 docs/configuration/cheatsheet.md              |   1 +
 .../migrators/hashtags_table_migrator.ex      | 101 ++++++++++++------
 .../hashtags_table_migrator/state.ex          |   4 +-
 5 files changed, 84 insertions(+), 31 deletions(-)

diff --git a/config/config.exs b/config/config.exs
index 0fbca06f3..c371c397c 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -657,6 +657,8 @@
 
 config :pleroma, :database, rum_enabled: false
 
+config :pleroma, :populate_hashtags_table, fault_rate_allowance: 0.01
+
 config :pleroma, :env, Mix.env()
 
 config :http_signatures,
diff --git a/config/description.exs b/config/description.exs
index 29fc5fbd4..6ffc71278 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -479,6 +479,13 @@
     type: :group,
     description: "`populate_hashtags_table` background migration settings",
     children: [
+      %{
+        key: :fault_rate_allowance,
+        type: :float,
+        description:
+          "Max rate of failed objects to actually processed objects in order to enable the feature (any value from 0.0 which tolerates no errors to 1.0 which will enable the feature even if hashtags transfer failed for all records).",
+        suggestions: [0.01]
+      },
       %{
         key: :sleep_interval_ms,
         type: :integer,
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index 68a5a3c7f..6a1031f15 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -70,6 +70,7 @@ To add configuration to your config file, you can copy it from the base config.
 
 ## Background migrations
 * `populate_hashtags_table/sleep_interval_ms`: Sleep interval between each chunk of processed records in order to decrease the load on the system (defaults to 0 and should be keep default on most instances).
+* `populate_hashtags_table/fault_rate_allowance`: Max rate of failed objects to actually processed objects in order to enable the feature (any value from 0.0 which tolerates no errors to 1.0 which will enable the feature even if hashtags transfer failed for all records).
 
 ## Welcome
 * `direct_message`: - welcome message sent as a direct message.
diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index ac17f91cc..45dab8470 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -15,7 +15,8 @@ defmodule Pleroma.Migrators.HashtagsTableMigrator do
   alias Pleroma.Object
   alias Pleroma.Repo
 
-  defdelegate data_migration(), to: State
+  defdelegate data_migration(), to: Pleroma.DataMigration, as: :populate_hashtags_table
+  defdelegate data_migration_id(), to: State
 
   defdelegate state(), to: State
   defdelegate persist_state(), to: State, as: :persist_to_db
@@ -23,10 +24,13 @@ defmodule Pleroma.Migrators.HashtagsTableMigrator do
   defdelegate put_stat(key, value), to: State, as: :put_data_key
   defdelegate increment_stat(key, increment), to: State, as: :increment_data_key
 
+  @feature_config_path [:database, :improved_hashtag_timeline]
   @reg_name {:global, __MODULE__}
 
   def whereis, do: GenServer.whereis(@reg_name)
 
+  def feature_state, do: Config.get(@feature_config_path)
+
   def start_link(_) do
     case whereis() do
       nil ->
@@ -46,8 +50,6 @@ def init(_) do
   def handle_continue(:init_state, _state) do
     {:ok, _} = State.start_link(nil)
 
-    update_status(:pending)
-
     data_migration = data_migration()
     manual_migrations = Config.get([:instance, :manual_data_migrations], [])
 
@@ -56,10 +58,14 @@ def handle_continue(:init_state, _state) do
         update_status(:noop)
 
       is_nil(data_migration) ->
-        update_status(:failed, "Data migration does not exist.")
+        message = "Data migration does not exist."
+        update_status(:failed, message)
+        Logger.error("#{__MODULE__}: #{message}")
 
       data_migration.state == :manual or data_migration.name in manual_migrations ->
-        update_status(:manual, "Data migration is in manual execution state.")
+        message = "Data migration is in manual execution or manual fix mode."
+        update_status(:manual, message)
+        Logger.warn("#{__MODULE__}: #{message}")
 
       data_migration.state == :complete ->
         on_complete(data_migration)
@@ -78,7 +84,7 @@ def handle_info(:migrate_hashtags, state) do
     update_status(:running)
     put_stat(:started_at, NaiveDateTime.utc_now())
 
-    %{id: data_migration_id} = data_migration()
+    data_migration_id = data_migration_id()
     max_processed_id = get_stat(:max_processed_id, 0)
 
     Logger.info("Transferring embedded hashtags to `hashtags` (from oid: #{max_processed_id})...")
@@ -89,12 +95,19 @@ def handle_info(:migrate_hashtags, state) do
     |> Stream.each(fn objects ->
       object_ids = Enum.map(objects, & &1.id)
 
+      results = Enum.map(objects, &transfer_object_hashtags(&1))
+
       failed_ids =
-        objects
-        |> Enum.map(&transfer_object_hashtags(&1))
+        results
         |> Enum.filter(&(elem(&1, 0) == :error))
         |> Enum.map(&elem(&1, 1))
 
+      # Count of objects with hashtags (`{:noop, id}` is returned for objects having other AS2 tags)
+      chunk_affected_count =
+        results
+        |> Enum.filter(&(elem(&1, 0) == :ok))
+        |> length()
+
       for failed_id <- failed_ids do
         _ =
           Repo.query(
@@ -116,6 +129,7 @@ def handle_info(:migrate_hashtags, state) do
       put_stat(:max_processed_id, max_object_id)
       increment_stat(:processed_count, length(object_ids))
       increment_stat(:failed_count, length(failed_ids))
+      increment_stat(:affected_count, chunk_affected_count)
       put_stat(:records_per_second, records_per_second())
       persist_state()
 
@@ -125,17 +139,42 @@ def handle_info(:migrate_hashtags, state) do
     end)
     |> Stream.run()
 
-    with 0 <- failures_count(data_migration_id) do
-      _ = delete_non_create_activities_hashtags()
-      set_complete()
-    else
-      _ ->
-        update_status(:failed, "Please check data_migration_failed_ids records.")
+    fault_rate = fault_rate()
+    put_stat(:fault_rate, fault_rate)
+    fault_rate_allowance = Config.get([:populate_hashtags_table, :fault_rate_allowance], 0)
+
+    cond do
+      fault_rate == 0 ->
+        set_complete()
+
+      is_float(fault_rate) and fault_rate <= fault_rate_allowance ->
+        message = """
+        Done with fault rate of #{fault_rate} which doesn't exceed #{fault_rate_allowance}.
+        Putting data migration to manual fix mode. Check `retry_failed/0`.
+        """
+
+        Logger.warn("#{__MODULE__}: #{message}")
+        update_status(:manual, message)
+        on_complete(data_migration())
+
+      true ->
+        message = "Too many failures. Check data_migration_failed_ids records / `retry_failed/0`."
+        Logger.error("#{__MODULE__}: #{message}")
+        update_status(:failed, message)
     end
 
+    persist_state()
     {:noreply, state}
   end
 
+  def fault_rate do
+    with failures_count when is_integer(failures_count) <- failures_count() do
+      failures_count / Enum.max([get_stat(:affected_count, 0), 1])
+    else
+      _ -> :error
+    end
+  end
+
   defp records_per_second do
     get_stat(:processed_count, 0) / Enum.max([running_time(), 1])
   end
@@ -194,6 +233,7 @@ defp query do
     |> where([_o, hashtags_objects], is_nil(hashtags_objects.object_id))
   end
 
+  @spec transfer_object_hashtags(Map.t()) :: {:noop | :ok | :error, integer()}
   defp transfer_object_hashtags(object) do
     embedded_tags = if Map.has_key?(object, :tag), do: object.tag, else: object.data["tag"]
     hashtags = Object.object_data_hashtags(%{"tag" => embedded_tags})
@@ -201,7 +241,7 @@ defp transfer_object_hashtags(object) do
     if Enum.any?(hashtags) do
       transfer_object_hashtags(object, hashtags)
     else
-      {:ok, object.id}
+      {:noop, object.id}
     end
   end
 
@@ -209,13 +249,11 @@ defp transfer_object_hashtags(object, hashtags) do
     Repo.transaction(fn ->
       with {:ok, hashtag_records} <- Hashtag.get_or_create_by_names(hashtags) do
         maps = Enum.map(hashtag_records, &%{hashtag_id: &1.id, object_id: object.id})
-        expected_rows = length(hashtag_records)
-
-        base_error =
-          "ERROR when inserting #{expected_rows} hashtags_objects for obj. #{object.id}"
+        base_error = "ERROR when inserting hashtags_objects for object with id #{object.id}"
 
         try do
-          with {^expected_rows, _} <- Repo.insert_all("hashtags_objects", maps) do
+          with {rows_count, _} when is_integer(rows_count) <-
+                 Repo.insert_all("hashtags_objects", maps, on_conflict: :nothing) do
             object.id
           else
             e ->
@@ -260,11 +298,11 @@ defp on_complete(data_migration) do
       data_migration.feature_lock ->
         :noop
 
-      not is_nil(Config.get([:database, :improved_hashtag_timeline])) ->
+      not is_nil(feature_state()) ->
         :noop
 
       true ->
-        Config.put([:database, :improved_hashtag_timeline], true)
+        Config.put(@feature_config_path, true)
         :ok
     end
   end
@@ -274,38 +312,41 @@ def failed_objects_query do
     |> join(:inner, [o], dmf in fragment("SELECT * FROM data_migration_failed_ids"),
       on: dmf.record_id == o.id
     )
-    |> where([_o, dmf], dmf.data_migration_id == ^data_migration().id)
+    |> where([_o, dmf], dmf.data_migration_id == ^data_migration_id())
     |> order_by([o], asc: o.id)
   end
 
-  def failures_count(data_migration_id \\ nil) do
-    data_migration_id = data_migration_id || data_migration().id
-
+  def failures_count do
     with {:ok, %{rows: [[count]]}} <-
            Repo.query(
              "SELECT COUNT(record_id) FROM data_migration_failed_ids WHERE data_migration_id = $1;",
-             [data_migration_id]
+             [data_migration_id()]
            ) do
       count
     end
   end
 
   def retry_failed do
-    data_migration = data_migration()
+    data_migration_id = data_migration_id()
 
     failed_objects_query()
     |> Repo.chunk_stream(100, :one)
     |> Stream.each(fn object ->
-      with {:ok, _} <- transfer_object_hashtags(object) do
+      with {res, _} when res != :error <- transfer_object_hashtags(object) do
         _ =
           Repo.query(
             "DELETE FROM data_migration_failed_ids " <>
               "WHERE data_migration_id = $1 AND record_id = $2",
-            [data_migration.id, object.id]
+            [data_migration_id, object.id]
           )
       end
     end)
     |> Stream.run()
+
+    put_stat(:failed_count, failures_count())
+    persist_state()
+
+    force_continue()
   end
 
   def force_continue do
diff --git a/lib/pleroma/migrators/hashtags_table_migrator/state.ex b/lib/pleroma/migrators/hashtags_table_migrator/state.ex
index ed9848824..ee0009b2e 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator/state.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator/state.ex
@@ -7,7 +7,7 @@ defmodule Pleroma.Migrators.HashtagsTableMigrator.State do
 
   alias Pleroma.DataMigration
 
-  defdelegate data_migration(), to: DataMigration, as: :populate_hashtags_table
+  defdelegate data_migration(), to: Pleroma.Migrators.HashtagsTableMigrator
 
   @reg_name {:global, __MODULE__}
 
@@ -99,4 +99,6 @@ defp persist_non_data_change(:state, value) do
   defp persist_non_data_change(_, _) do
     nil
   end
+
+  def data_migration_id, do: Map.get(state(), :data_migration_id)
 end

From 998437d4a4111055e019f28dd84a8af1f9a27047 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Thu, 18 Feb 2021 21:03:06 +0300
Subject: [PATCH 047/339] [#3213] Experimental / debug feature: `database:
 [improved_hashtag_timeline: :preselect_hashtag_ids]`.

---
 lib/pleroma/web/activity_pub/activity_pub.ex | 47 +++++++++++++++-----
 1 file changed, 35 insertions(+), 12 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index e012f2779..5392ce7c9 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -787,19 +787,42 @@ defp restrict_hashtag_any(_query, %{tag: _tag, skip_preload: true}) do
   end
 
   defp restrict_hashtag_any(query, %{tag: [_ | _] = tags}) do
-    from(
-      [_activity, object] in query,
-      where:
-        fragment(
-          """
-          EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
-            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?::citext[])
-              AND hashtags_objects.object_id = ? LIMIT 1)
-          """,
-          ^tags,
-          object.id
+    # TODO: refactor: debug / experimental feature
+    if Config.get([:database, :improved_hashtag_timeline]) == :preselect_hashtag_ids do
+      hashtag_ids =
+        from(ht in Pleroma.Hashtag,
+          where: fragment("name = ANY(?::citext[])", ^tags),
+          select: ht.id
         )
-    )
+        |> Repo.all()
+
+      from(
+        [_activity, object] in query,
+        where:
+          fragment(
+            """
+            EXISTS (
+            SELECT 1 FROM hashtags_objects WHERE hashtag_id = ANY(?) AND object_id = ? LIMIT 1)
+            """,
+            ^hashtag_ids,
+            object.id
+          )
+      )
+    else
+      from(
+        [_activity, object] in query,
+        where:
+          fragment(
+            """
+            EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
+              ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?::citext[])
+                AND hashtags_objects.object_id = ? LIMIT 1)
+            """,
+            ^tags,
+            object.id
+          )
+      )
+    end
   end
 
   defp restrict_hashtag_any(query, %{tag: tag}) when is_binary(tag) do

From 6531eddf361fa52db3906ab011a4e33c7a5f9552 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Mon, 22 Feb 2021 23:26:07 +0300
Subject: [PATCH 048/339] [#3213] `hashtags`: altered `name` type to `text`.
 `hashtags_objects`: removed unused index. HashtagsTableMigrator:
 records_per_second calculation fix. ActivityPub: hashtags-related options
 normalization.

---
 lib/pleroma/hashtag.ex                        | 22 ++++--
 .../migrators/hashtags_table_migrator.ex      |  4 +-
 lib/pleroma/web/activity_pub/activity_pub.ex  | 75 ++++++++-----------
 ...20201221203824_create_hashtags_objects.exs |  2 +-
 ...emove_hashtags_objects_duplicate_index.exs | 11 +++
 ...222184616_change_hashtags_name_to_text.exs | 15 ++++
 6 files changed, 76 insertions(+), 53 deletions(-)
 create mode 100644 priv/repo/migrations/20210222183840_remove_hashtags_objects_duplicate_index.exs
 create mode 100644 priv/repo/migrations/20210222184616_change_hashtags_name_to_text.exs

diff --git a/lib/pleroma/hashtag.ex b/lib/pleroma/hashtag.ex
index a6d033816..e9d143fb1 100644
--- a/lib/pleroma/hashtag.ex
+++ b/lib/pleroma/hashtag.ex
@@ -21,10 +21,14 @@ defmodule Pleroma.Hashtag do
     timestamps()
   end
 
+  def normalize_name(name) do
+    name
+    |> String.downcase()
+    |> String.trim()
+  end
+
   def get_by_name(name) do
-    from(h in Hashtag)
-    |> where([h], fragment("name = ?::citext", ^String.downcase(name)))
-    |> Repo.one()
+    Repo.get_by(Hashtag, name: normalize_name(name))
   end
 
   def get_or_create_by_name(name) when is_bitstring(name) do
@@ -39,7 +43,7 @@ def get_or_create_by_name(name) when is_bitstring(name) do
   end
 
   def get_or_create_by_names(names) when is_list(names) do
-    names = Enum.map(names, &String.downcase/1)
+    names = Enum.map(names, &normalize_name/1)
     timestamp = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
 
     structs =
@@ -53,10 +57,12 @@ def get_or_create_by_names(names) when is_list(names) do
     try do
       with {:ok, %{query_op: hashtags}} <-
              Multi.new()
-             |> Multi.insert_all(:insert_all_op, Hashtag, structs, on_conflict: :nothing)
+             |> Multi.insert_all(:insert_all_op, Hashtag, structs,
+               on_conflict: :nothing,
+               conflict_target: :name
+             )
              |> Multi.run(:query_op, fn _repo, _changes ->
-               {:ok,
-                Repo.all(from(ht in Hashtag, where: ht.name in fragment("?::citext[]", ^names)))}
+               {:ok, Repo.all(from(ht in Hashtag, where: ht.name in ^names))}
              end)
              |> Repo.transaction() do
         {:ok, hashtags}
@@ -71,7 +77,7 @@ def get_or_create_by_names(names) when is_list(names) do
   def changeset(%Hashtag{} = struct, params) do
     struct
     |> cast(params, [:name])
-    |> update_change(:name, &String.downcase/1)
+    |> update_change(:name, &normalize_name/1)
     |> validate_required([:name])
     |> unique_constraint(:name)
   end
diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index 45dab8470..07bb9aeb2 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -82,6 +82,7 @@ def handle_info(:migrate_hashtags, state) do
     State.reinit()
 
     update_status(:running)
+    put_stat(:iteration_processed_count, 0)
     put_stat(:started_at, NaiveDateTime.utc_now())
 
     data_migration_id = data_migration_id()
@@ -127,6 +128,7 @@ def handle_info(:migrate_hashtags, state) do
       max_object_id = Enum.at(object_ids, -1)
 
       put_stat(:max_processed_id, max_object_id)
+      increment_stat(:iteration_processed_count, length(object_ids))
       increment_stat(:processed_count, length(object_ids))
       increment_stat(:failed_count, length(failed_ids))
       increment_stat(:affected_count, chunk_affected_count)
@@ -176,7 +178,7 @@ def fault_rate do
   end
 
   defp records_per_second do
-    get_stat(:processed_count, 0) / Enum.max([running_time(), 1])
+    get_stat(:iteration_processed_count, 0) / Enum.max([running_time(), 1])
   end
 
   defp running_time do
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 5392ce7c9..8182bc205 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   alias Pleroma.Conversation
   alias Pleroma.Conversation.Participation
   alias Pleroma.Filter
+  alias Pleroma.Hashtag
   alias Pleroma.Maps
   alias Pleroma.Notification
   alias Pleroma.Object
@@ -698,8 +699,6 @@ defp restrict_embedded_tag_all(_query, %{tag_all: _tag_all, skip_preload: true})
   end
 
   defp restrict_embedded_tag_all(query, %{tag_all: [_ | _] = tag_all}) do
-    tag_all = Enum.map(tag_all, &String.downcase/1)
-
     from(
       [_activity, object] in query,
       where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)
@@ -717,8 +716,6 @@ defp restrict_embedded_tag_any(_query, %{tag: _tag, skip_preload: true}) do
   end
 
   defp restrict_embedded_tag_any(query, %{tag: [_ | _] = tag_any}) do
-    tag_any = Enum.map(tag_any, &String.downcase/1)
-
     from(
       [_activity, object] in query,
       where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag_any)
@@ -736,8 +733,6 @@ defp restrict_embedded_tag_reject_any(_query, %{tag_reject: _tag_reject, skip_pr
   end
 
   defp restrict_embedded_tag_reject_any(query, %{tag_reject: [_ | _] = tag_reject}) do
-    tag_reject = Enum.map(tag_reject, &String.downcase/1)
-
     from(
       [_activity, object] in query,
       where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
@@ -766,7 +761,7 @@ defp restrict_hashtag_all(query, %{tag_all: [_ | _] = tags}) do
         fragment(
           """
           (SELECT array_agg(hashtags.name) FROM hashtags JOIN hashtags_objects
-            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?::citext[])
+            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
               AND hashtags_objects.object_id = ?) @> ?
           """,
           ^tags,
@@ -787,42 +782,19 @@ defp restrict_hashtag_any(_query, %{tag: _tag, skip_preload: true}) do
   end
 
   defp restrict_hashtag_any(query, %{tag: [_ | _] = tags}) do
-    # TODO: refactor: debug / experimental feature
-    if Config.get([:database, :improved_hashtag_timeline]) == :preselect_hashtag_ids do
-      hashtag_ids =
-        from(ht in Pleroma.Hashtag,
-          where: fragment("name = ANY(?::citext[])", ^tags),
-          select: ht.id
+    from(
+      [_activity, object] in query,
+      where:
+        fragment(
+          """
+          EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
+            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
+              AND hashtags_objects.object_id = ? LIMIT 1)
+          """,
+          ^tags,
+          object.id
         )
-        |> Repo.all()
-
-      from(
-        [_activity, object] in query,
-        where:
-          fragment(
-            """
-            EXISTS (
-            SELECT 1 FROM hashtags_objects WHERE hashtag_id = ANY(?) AND object_id = ? LIMIT 1)
-            """,
-            ^hashtag_ids,
-            object.id
-          )
-      )
-    else
-      from(
-        [_activity, object] in query,
-        where:
-          fragment(
-            """
-            EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
-              ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?::citext[])
-                AND hashtags_objects.object_id = ? LIMIT 1)
-            """,
-            ^tags,
-            object.id
-          )
-      )
-    end
+    )
   end
 
   defp restrict_hashtag_any(query, %{tag: tag}) when is_binary(tag) do
@@ -842,7 +814,7 @@ defp restrict_hashtag_reject_any(query, %{tag_reject: [_ | _] = tags_reject}) do
         fragment(
           """
           NOT EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
-            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?::citext[])
+            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
               AND hashtags_objects.object_id = ? LIMIT 1)
           """,
           ^tags_reject,
@@ -1220,6 +1192,21 @@ defp maybe_order(query, %{order: :asc}) do
 
   defp maybe_order(query, _), do: query
 
+  defp normalize_fetch_activities_query_opts(opts) do
+    Enum.reduce([:tag, :tag_all, :tag_reject], opts, fn key, opts ->
+      case opts[key] do
+        value when is_bitstring(value) ->
+          Map.put(opts, key, Hashtag.normalize_name(value))
+
+        value when is_list(value) ->
+          Map.put(opts, key, Enum.map(value, &Hashtag.normalize_name/1))
+
+        _ ->
+          opts
+      end
+    end)
+  end
+
   defp fetch_activities_query_ap_ids_ops(opts) do
     source_user = opts[:muting_user]
     ap_id_relationships = if source_user, do: [:mute, :reblog_mute], else: []
@@ -1243,6 +1230,8 @@ defp fetch_activities_query_ap_ids_ops(opts) do
   end
 
   def fetch_activities_query(recipients, opts \\ %{}) do
+    opts = normalize_fetch_activities_query_opts(opts)
+
     {restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts} =
       fetch_activities_query_ap_ids_ops(opts)
 
diff --git a/priv/repo/migrations/20201221203824_create_hashtags_objects.exs b/priv/repo/migrations/20201221203824_create_hashtags_objects.exs
index efd60369d..581f32b3c 100644
--- a/priv/repo/migrations/20201221203824_create_hashtags_objects.exs
+++ b/priv/repo/migrations/20201221203824_create_hashtags_objects.exs
@@ -7,7 +7,7 @@ def change do
       add(:object_id, references(:objects), null: false, primary_key: true)
     end
 
-    create_if_not_exists(unique_index(:hashtags_objects, [:hashtag_id, :object_id]))
+    # Note: PK index: "hashtags_objects_pkey" PRIMARY KEY, btree (hashtag_id, object_id)
     create_if_not_exists(index(:hashtags_objects, [:object_id]))
   end
 end
diff --git a/priv/repo/migrations/20210222183840_remove_hashtags_objects_duplicate_index.exs b/priv/repo/migrations/20210222183840_remove_hashtags_objects_duplicate_index.exs
new file mode 100644
index 000000000..6c4a2dfdc
--- /dev/null
+++ b/priv/repo/migrations/20210222183840_remove_hashtags_objects_duplicate_index.exs
@@ -0,0 +1,11 @@
+defmodule Pleroma.Repo.Migrations.RemoveHashtagsObjectsDuplicateIndex do
+  use Ecto.Migration
+
+  @moduledoc "Removes `hashtags_objects_hashtag_id_object_id_index` index (duplicate of PK index)."
+
+  def up do
+    drop_if_exists(unique_index(:hashtags_objects, [:hashtag_id, :object_id]))
+  end
+
+  def down, do: nil
+end
diff --git a/priv/repo/migrations/20210222184616_change_hashtags_name_to_text.exs b/priv/repo/migrations/20210222184616_change_hashtags_name_to_text.exs
new file mode 100644
index 000000000..8940b6ca3
--- /dev/null
+++ b/priv/repo/migrations/20210222184616_change_hashtags_name_to_text.exs
@@ -0,0 +1,15 @@
+defmodule Pleroma.Repo.Migrations.ChangeHashtagsNameToText do
+  use Ecto.Migration
+
+  def up do
+    alter table(:hashtags) do
+      modify(:name, :text)
+    end
+  end
+
+  def down do
+    alter table(:hashtags) do
+      modify(:name, :citext)
+    end
+  end
+end

From a98c4423f374c6be8202ae884989e708e7d8ca3b Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantbusiness@gmail.com>
Date: Mon, 22 Feb 2021 20:41:57 +0000
Subject: [PATCH 049/339] Apply i1t's suggestion(s) to 1 file(s)

---
 config/description.exs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/config/description.exs b/config/description.exs
index 6ffc71278..e280ed8cf 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -483,7 +483,7 @@
         key: :fault_rate_allowance,
         type: :float,
         description:
-          "Max rate of failed objects to actually processed objects in order to enable the feature (any value from 0.0 which tolerates no errors to 1.0 which will enable the feature even if hashtags transfer failed for all records).",
+          "Max accepted rate of objects that failed in the migration. Any value from 0.0 which tolerates no errors to 1.0 which will enable the feature even if hashtags transfer failed for all records.",
         suggestions: [0.01]
       },
       %{

From 77f3da035894e2add911101466bfe41b99ee481e Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Tue, 23 Feb 2021 13:52:28 +0300
Subject: [PATCH 050/339] [#3213] Misc. tweaks: proper upsert in Hashtag,
 better feature toggle management.

---
 config/config.exs                             |  2 ++
 config/description.exs                        |  9 +++++----
 docs/configuration/cheatsheet.md              |  2 +-
 lib/pleroma/config.ex                         |  4 ++++
 lib/pleroma/hashtag.ex                        | 20 ++++++++-----------
 .../migrators/hashtags_table_migrator.ex      | 18 +++++++----------
 lib/pleroma/web/activity_pub/activity_pub.ex  |  2 +-
 .../web/activity_pub/activity_pub_test.exs    |  4 ++--
 8 files changed, 30 insertions(+), 31 deletions(-)

diff --git a/config/config.exs b/config/config.exs
index c371c397c..05acbf169 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -657,6 +657,8 @@
 
 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()
diff --git a/config/description.exs b/config/description.exs
index e280ed8cf..41e5e4056 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -461,15 +461,16 @@
   },
   %{
     group: :pleroma,
-    key: :database,
+    key: :features,
     type: :group,
-    description: "Database-related settings",
+    description: "Customizable features",
     children: [
       %{
         key: :improved_hashtag_timeline,
-        type: :keyword,
+        type: {:dropdown, :atom},
         description:
-          "If `true`, hashtags will be fetched from `hashtags` table for hashtags timeline. When `false`, object-embedded hashtags will be used (slower). Is auto-set to `true` (unless overridden) when HashtagsTableMigrator completes."
+          "Setting to force toggle / force disable improved hashtags timeline. `:enabled` forces hashtags to be fetched from `hashtags` table for hashtags timeline. `:disabled` forces object-embedded hashtags to be used (slower). Keep it `:auto` for automatic behaviour (it is auto-set to `:enabled` [unless overridden] when HashtagsTableMigrator completes).",
+        suggestions: [:auto, :enabled, :disabled]
       }
     ]
   },
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index 6a1031f15..db1deb665 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -66,7 +66,7 @@ To add configuration to your config file, you can copy it from the base config.
 * `password_reset_token_validity`: The time after which reset tokens aren't accepted anymore, in seconds (default: one day).
 
 ## :database
-* `improved_hashtag_timeline`: If `true`, hashtags will be fetched from `hashtags` table for hashtags timeline. When `false`, object-embedded hashtags will be used (slower). Is auto-set to `true` (unless overridden) when `HashtagsTableMigrator` completes.
+* `improved_hashtag_timeline`: Setting to force toggle / force disable improved hashtags timeline. `:enabled` forces hashtags to be fetched from `hashtags` table for hashtags timeline. `:disabled` forces object-embedded hashtags to be used (slower). Keep it `:auto` for automatic behaviour (it is auto-set to `:enabled` [unless overridden] when HashtagsTableMigrator completes).
 
 ## Background migrations
 * `populate_hashtags_table/sleep_interval_ms`: Sleep interval between each chunk of processed records in order to decrease the load on the system (defaults to 0 and should be keep default on most instances).
diff --git a/lib/pleroma/config.ex b/lib/pleroma/config.ex
index f17e14128..e057d8c02 100644
--- a/lib/pleroma/config.ex
+++ b/lib/pleroma/config.ex
@@ -111,4 +111,8 @@ def oauth_admin_scopes(scopes) when is_list(scopes) do
       end
     )
   end
+
+  def feature_enabled?(feature_name) do
+    get([:features, feature_name]) not in [nil, false, :disabled, :auto]
+  end
 end
diff --git a/lib/pleroma/hashtag.ex b/lib/pleroma/hashtag.ex
index e9d143fb1..53e2e9c89 100644
--- a/lib/pleroma/hashtag.ex
+++ b/lib/pleroma/hashtag.ex
@@ -27,19 +27,15 @@ def normalize_name(name) do
     |> String.trim()
   end
 
-  def get_by_name(name) do
-    Repo.get_by(Hashtag, name: normalize_name(name))
-  end
+  def get_or_create_by_name(name) do
+    changeset = changeset(%Hashtag{}, %{name: name})
 
-  def get_or_create_by_name(name) when is_bitstring(name) do
-    with %Hashtag{} = hashtag <- get_by_name(name) do
-      {:ok, hashtag}
-    else
-      _ ->
-        %Hashtag{}
-        |> changeset(%{name: name})
-        |> Repo.insert()
-    end
+    Repo.insert(
+      changeset,
+      on_conflict: [set: [name: get_field(changeset, :name)]],
+      conflict_target: :name,
+      returning: true
+    )
   end
 
   def get_or_create_by_names(names) when is_list(names) do
diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index 07bb9aeb2..6123c88e0 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -24,7 +24,7 @@ defmodule Pleroma.Migrators.HashtagsTableMigrator do
   defdelegate put_stat(key, value), to: State, as: :put_data_key
   defdelegate increment_stat(key, increment), to: State, as: :increment_data_key
 
-  @feature_config_path [:database, :improved_hashtag_timeline]
+  @feature_config_path [:features, :improved_hashtag_timeline]
   @reg_name {:global, __MODULE__}
 
   def whereis, do: GenServer.whereis(@reg_name)
@@ -296,16 +296,12 @@ def count(force \\ false, timeout \\ :infinity) do
   end
 
   defp on_complete(data_migration) do
-    cond do
-      data_migration.feature_lock ->
-        :noop
-
-      not is_nil(feature_state()) ->
-        :noop
-
-      true ->
-        Config.put(@feature_config_path, true)
-        :ok
+    if data_migration.feature_lock || feature_state() == :disabled do
+      Logger.warn("#{__MODULE__}: migration complete but feature is locked; consider enabling.")
+      :noop
+    else
+      Config.put(@feature_config_path, :enabled)
+      :ok
     end
   end
 
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 8182bc205..9d557c2cd 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1273,7 +1273,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
       |> exclude_invisible_actors(opts)
       |> exclude_visibility(opts)
 
-    if Config.get([:database, :improved_hashtag_timeline]) do
+    if Config.feature_enabled?(:improved_hashtag_timeline) do
       query
       |> restrict_hashtag_any(opts)
       |> restrict_hashtag_all(opts)
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index c41c8a5dd..f92323abe 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -220,8 +220,8 @@ test "it fetches the appropriate tag-restricted posts" do
     {:ok, status_four} = CommonAPI.post(user, %{status: ". #Any1 #any2"})
     {:ok, status_five} = CommonAPI.post(user, %{status: ". #Any2 #any1"})
 
-    for hashtag_timeline_strategy <- [true, false] do
-      clear_config([:database, :improved_hashtag_timeline], hashtag_timeline_strategy)
+    for hashtag_timeline_strategy <- [:eanbled, :disabled] do
+      clear_config([:features, :improved_hashtag_timeline], hashtag_timeline_strategy)
 
       fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})
 

From 40d4362261abaf0856a1b4397a4bff6344137120 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Tue, 23 Feb 2021 18:11:25 +0300
Subject: [PATCH 051/339] [#3213] `mix pleroma.database rollback` tweaks.

---
 lib/mix/tasks/pleroma/database.ex | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index 2136ddb02..e7f4b67a4 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -231,19 +231,18 @@ def run(["rollback", version]) do
           re = ~r/^#{version}_.*\.exs/
           path = Ecto.Migrator.migrations_path(repo)
 
-          with {:find, "" <> file} <- {:find, Enum.find(File.ls!(path), &String.match?(&1, re))},
-               {:compile, [{mod, _} | _]} <- {:compile, Code.compile_file(Path.join(path, file))},
-               {:rollback, :ok} <- {:rollback, Ecto.Migrator.down(repo, version, mod)} do
+          with {_, "" <> file} <- {:find, Enum.find(File.ls!(path), &String.match?(&1, re))},
+               {_, [{mod, _} | _]} <- {:compile, Code.compile_file(Path.join(path, file))},
+               {_, :ok} <- {:rollback, Ecto.Migrator.down(repo, version, mod)} do
             {:ok, "Reversed migration: #{file}"}
           else
             {:find, _} -> {:error, "No migration found with version prefix: #{version}"}
             {:compile, e} -> {:error, "Problem compiling migration module: #{inspect(e)}"}
             {:rollback, e} -> {:error, "Problem reversing migration: #{inspect(e)}"}
-            e -> {:error, "Something unexpected happened: #{inspect(e)}"}
           end
         end)
 
-      IO.inspect(result)
+      shell_info(inspect(result))
     end
   end
 end

From 3bc7d122712b5cc35ba509542bde63ca130d6a40 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Mon, 28 Dec 2020 23:21:53 +0100
Subject: [PATCH 052/339] Remove sensitive-property setting #nsfw, create
 HashtagPolicy

---
 CHANGELOG.md                                  |   2 +-
 config/config.exs                             |   5 +
 docs/configuration/cheatsheet.md              |  10 ++
 lib/pleroma/web/activity_pub/mrf.ex           |   4 +-
 .../web/activity_pub/mrf/hashtag_policy.ex    | 116 ++++++++++++++++++
 .../web/activity_pub/mrf/simple_policy.ex     |  12 +-
 .../web/activity_pub/mrf/tag_policy.ex        |  13 +-
 .../web/activity_pub/transmogrifier.ex        |  11 --
 lib/pleroma/web/common_api/activity_draft.ex  |   2 +-
 lib/pleroma/web/common_api/utils.ex           |   8 --
 .../activity_pub/mrf/hashtag_policy_test.exs  |  31 +++++
 .../activity_pub/mrf/simple_policy_test.exs   |  10 +-
 .../web/activity_pub/mrf/tag_policy_test.exs  |   2 +-
 test/pleroma/web/activity_pub/mrf_test.exs    |  14 ++-
 .../web/activity_pub/transmogrifier_test.exs  |   9 --
 15 files changed, 187 insertions(+), 62 deletions(-)
 create mode 100644 lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex
 create mode 100644 test/pleroma/web/activity_pub/mrf/hashtag_policy_test.exs

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a7b5f6ac0..52fdcb932 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 - **Breaking**: Changed `mix pleroma.user toggle_confirmed` to `mix pleroma.user confirm`
 - **Breaking**: Changed `mix pleroma.user toggle_activated` to `mix pleroma.user activate/deactivate`
+- **Breaking:** NSFW hashtag is no longer added on sensitive posts
 - Polls now always return a `voters_count`, even if they are single-choice.
 - Admin Emails: The ap id is used as the user link in emails now.
 - Improved registration workflow for email confirmation and account approval modes.
@@ -489,7 +490,6 @@ switched to a new configuration mechanism, however it was not officially removed
 - Static-FE: Fix remote posts not being sanitized
 
 ### Fixed
-=======
 - Rate limiter crashes when there is no explicitly specified ip in the config
 - 500 errors when no `Accept` header is present if Static-FE is enabled
 - Instance panel not being updated immediately due to wrong `Cache-Control` headers
diff --git a/config/config.exs b/config/config.exs
index c371c397c..97e440fee 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -391,6 +391,11 @@
   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
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index 6a1031f15..f3eee3e67 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -210,6 +210,16 @@ config :pleroma, :mrf_user_allowlist, %{
 
 * `days`: Default global expiration time for all local Create activities (in days)
 
+#### :mrf_hashtag
+
+* `sensitive`: List of hashtags to mark activities as sensitive (default: `nsfw`)
+* `federated_timeline_removal`: List of hashtags to remove activities from the federated timeline (aka TWNK)
+* `reject`: List of hashtags to reject activities from
+
+Notes:
+- The hashtags in the configuration do not have a leading `#`.
+- This MRF Policy is always enabled, if you want to disable it you have to set empty lists
+
 ### :activitypub
 * `unfollow_blocked`: Whether blocks result in people getting unfollowed
 * `outgoing_blocks`: Whether to federate blocks to other instances
diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex
index ef5a09a93..f2fec3ff6 100644
--- a/lib/pleroma/web/activity_pub/mrf.ex
+++ b/lib/pleroma/web/activity_pub/mrf.ex
@@ -92,7 +92,9 @@ def pipeline_filter(%{} = message, meta) do
   end
 
   def get_policies do
-    Pleroma.Config.get([:mrf, :policies], []) |> get_policies()
+    Pleroma.Config.get([:mrf, :policies], [])
+    |> get_policies()
+    |> Enum.concat([Pleroma.Web.ActivityPub.MRF.HashtagPolicy])
   end
 
   defp get_policies(policy) when is_atom(policy), do: [policy]
diff --git a/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex b/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex
new file mode 100644
index 000000000..def0c437c
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex
@@ -0,0 +1,116 @@
+# 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.MRF.HashtagPolicy do
+  require Pleroma.Constants
+
+  alias Pleroma.Config
+  alias Pleroma.Object
+
+  @moduledoc """
+  Reject, TWKN-remove or Set-Sensitive messsages with specific hashtags (without the leading #)
+
+  Note: This MRF Policy is always enabled, if you want to disable it you have to set empty lists.
+  """
+
+  @behaviour Pleroma.Web.ActivityPub.MRF
+
+  defp check_reject(message, hashtags) do
+    if Enum.any?(Config.get([:mrf_hashtag, :reject]), fn match -> match in hashtags end) do
+      {:reject, "[HashtagPolicy] Matches with rejected keyword"}
+    else
+      {:ok, message}
+    end
+  end
+
+  defp check_ftl_removal(%{"to" => to} = message, hashtags) do
+    if Pleroma.Constants.as_public() in to and
+         Enum.any?(Config.get([:mrf_hashtag, :federated_timeline_removal]), fn match ->
+           match in hashtags
+         end) do
+      to = List.delete(to, Pleroma.Constants.as_public())
+      cc = [Pleroma.Constants.as_public() | message["cc"] || []]
+
+      message =
+        message
+        |> Map.put("to", to)
+        |> Map.put("cc", cc)
+        |> Kernel.put_in(["object", "to"], to)
+        |> Kernel.put_in(["object", "cc"], cc)
+
+      {:ok, message}
+    else
+      {:ok, message}
+    end
+  end
+
+  defp check_ftl_removal(message, _hashtags), do: {:ok, message}
+
+  defp check_sensitive(message, hashtags) do
+    if Enum.any?(Config.get([:mrf_hashtag, :sensitive]), fn match -> match in hashtags end) do
+      {:ok, Kernel.put_in(message, ["object", "sensitive"], true)}
+    else
+      {:ok, message}
+    end
+  end
+
+  @impl true
+  def filter(%{"type" => "Create", "object" => object} = message) do
+    hashtags = Object.hashtags(%Object{data: object})
+
+    if hashtags != [] do
+      with {:ok, message} <- check_reject(message, hashtags),
+           {:ok, message} <- check_ftl_removal(message, hashtags),
+           {:ok, message} <- check_sensitive(message, hashtags) do
+        {:ok, message}
+      end
+    else
+      {:ok, message}
+    end
+  end
+
+  @impl true
+  def filter(message), do: {:ok, message}
+
+  @impl true
+  def describe do
+    mrf_hashtag =
+      Config.get(:mrf_hashtag)
+      |> Enum.into(%{})
+
+    {:ok, %{mrf_hashtag: mrf_hashtag}}
+  end
+
+  @impl true
+  def config_description do
+    %{
+      key: :mrf_hashtag,
+      related_policy: "Pleroma.Web.ActivityPub.MRF.HashtagPolicy",
+      label: "MRF Hashtag",
+      description: @moduledoc,
+      children: [
+        %{
+          key: :reject,
+          type: {:list, :string},
+          description: "A list of hashtags which result in message being rejected.",
+          suggestions: ["foo"]
+        },
+        %{
+          key: :federated_timeline_removal,
+          type: {:list, :string},
+          description:
+            "A list of hashtags which result in message being removed from federated timelines (a.k.a unlisted).",
+          suggestions: ["foo"]
+        },
+        %{
+          key: :sensitive,
+          type: {:list, :string},
+          description:
+            "A list of hashtags which result in message being set as sensitive (a.k.a NSFW/R-18)",
+          suggestions: ["nsfw", "r18"]
+        }
+      ]
+    }
+  end
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index 0b1be8c51..62024c58c 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -64,22 +64,16 @@ defp check_media_nsfw(
          %{host: actor_host} = _actor_info,
          %{
            "type" => "Create",
-           "object" => child_object
+           "object" => %{} = _child_object
          } = object
-       )
-       when is_map(child_object) do
+       ) do
     media_nsfw =
       Config.get([:mrf_simple, :media_nsfw])
       |> MRF.subdomains_regex()
 
     object =
       if MRF.subdomain_match?(media_nsfw, actor_host) do
-        child_object =
-          child_object
-          |> Map.put("tag", (child_object["tag"] || []) ++ ["nsfw"])
-          |> Map.put("sensitive", true)
-
-        Map.put(object, "object", child_object)
+        Kernel.put_in(object, ["object", "sensitive"], true)
       else
         object
       end
diff --git a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
index 5739cee63..528093ac0 100644
--- a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
@@ -28,20 +28,11 @@ defp process_tag(
          "mrf_tag:media-force-nsfw",
          %{
            "type" => "Create",
-           "object" => %{"attachment" => child_attachment} = object
+           "object" => %{"attachment" => child_attachment}
          } = message
        )
        when length(child_attachment) > 0 do
-    tags = (object["tag"] || []) ++ ["nsfw"]
-
-    object =
-      object
-      |> Map.put("tag", tags)
-      |> Map.put("sensitive", true)
-
-    message = Map.put(message, "object", object)
-
-    {:ok, message}
+    {:ok, Kernel.put_in(message, ["object", "sensitive"], true)}
   end
 
   defp process_tag(
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 0a701334f..8c7d6a747 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -40,7 +40,6 @@ def fix_object(object, options \\ []) do
     |> fix_in_reply_to(options)
     |> fix_emoji()
     |> fix_tag()
-    |> set_sensitive()
     |> fix_content_map()
     |> fix_addressing()
     |> fix_summary()
@@ -741,7 +740,6 @@ def replies(_), do: []
   # Prepares the object of an outgoing create activity.
   def prepare_object(object) do
     object
-    |> set_sensitive
     |> add_hashtags
     |> add_mention_tags
     |> add_emoji_tags
@@ -932,15 +930,6 @@ def set_conversation(object) do
     Map.put(object, "conversation", object["context"])
   end
 
-  def set_sensitive(%{"sensitive" => _} = object) do
-    object
-  end
-
-  def set_sensitive(object) do
-    tags = object["tag"] || []
-    Map.put(object, "sensitive", "nsfw" in tags)
-  end
-
   def set_type(%{"type" => "Answer"} = object) do
     Map.put(object, "type", "Note")
   end
diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
index fb059c27c..da726a690 100644
--- a/lib/pleroma/web/common_api/activity_draft.ex
+++ b/lib/pleroma/web/common_api/activity_draft.ex
@@ -179,7 +179,7 @@ defp context(draft) do
   end
 
   defp sensitive(draft) do
-    sensitive = draft.params[:sensitive] || Enum.member?(draft.tags, {"#nsfw", "nsfw"})
+    sensitive = draft.params[:sensitive]
     %__MODULE__{draft | sensitive: sensitive}
   end
 
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 9587dfa25..4e6a3feb0 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -217,7 +217,6 @@ def make_content_html(%ActivityDraft{} = draft) do
     draft.status
     |> format_input(content_type, options)
     |> maybe_add_attachments(draft.attachments, attachment_links)
-    |> maybe_add_nsfw_tag(draft.params)
   end
 
   defp get_content_type(content_type) do
@@ -228,13 +227,6 @@ defp get_content_type(content_type) do
     end
   end
 
-  defp maybe_add_nsfw_tag({text, mentions, tags}, %{"sensitive" => sensitive})
-       when sensitive in [true, "True", "true", "1"] do
-    {text, mentions, [{"#nsfw", "nsfw"} | tags]}
-  end
-
-  defp maybe_add_nsfw_tag(data, _), do: data
-
   def make_context(_, %Participation{} = participation) do
     Repo.preload(participation, :conversation).conversation.ap_id
   end
diff --git a/test/pleroma/web/activity_pub/mrf/hashtag_policy_test.exs b/test/pleroma/web/activity_pub/mrf/hashtag_policy_test.exs
new file mode 100644
index 000000000..13415bb79
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/hashtag_policy_test.exs
@@ -0,0 +1,31 @@
+# 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.MRF.HashtagPolicyTest do
+  use Oban.Testing, repo: Pleroma.Repo
+  use Pleroma.DataCase
+
+  alias Pleroma.Web.ActivityPub.Transmogrifier
+  alias Pleroma.Web.CommonAPI
+
+  import Pleroma.Factory
+
+  test "it sets the sensitive property with relevant hashtags" do
+    user = insert(:user)
+
+    {:ok, activity} = CommonAPI.post(user, %{status: "#nsfw hey"})
+    {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
+
+    assert modified["object"]["sensitive"]
+  end
+
+  test "it doesn't sets the sensitive property with irrelevant hashtags" do
+    user = insert(:user)
+
+    {:ok, activity} = CommonAPI.post(user, %{status: "#cofe hey"})
+    {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
+
+    refute modified["object"]["sensitive"]
+  end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs b/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs
index f48e5b39b..5c0aff26e 100644
--- a/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs
+++ b/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs
@@ -75,10 +75,7 @@ test "has a matching host" do
       local_message = build_local_message()
 
       assert SimplePolicy.filter(media_message) ==
-               {:ok,
-                media_message
-                |> put_in(["object", "tag"], ["foo", "nsfw"])
-                |> put_in(["object", "sensitive"], true)}
+               {:ok, put_in(media_message, ["object", "sensitive"], true)}
 
       assert SimplePolicy.filter(local_message) == {:ok, local_message}
     end
@@ -89,10 +86,7 @@ test "match with wildcard domain" do
       local_message = build_local_message()
 
       assert SimplePolicy.filter(media_message) ==
-               {:ok,
-                media_message
-                |> put_in(["object", "tag"], ["foo", "nsfw"])
-                |> put_in(["object", "sensitive"], true)}
+               {:ok, put_in(media_message, ["object", "sensitive"], true)}
 
       assert SimplePolicy.filter(local_message) == {:ok, local_message}
     end
diff --git a/test/pleroma/web/activity_pub/mrf/tag_policy_test.exs b/test/pleroma/web/activity_pub/mrf/tag_policy_test.exs
index 66e98b7ee..faaadff79 100644
--- a/test/pleroma/web/activity_pub/mrf/tag_policy_test.exs
+++ b/test/pleroma/web/activity_pub/mrf/tag_policy_test.exs
@@ -114,7 +114,7 @@ test "Mark as sensitive on presence of attachments" do
       except_message = %{
         "actor" => actor.ap_id,
         "type" => "Create",
-        "object" => %{"tag" => ["test", "nsfw"], "attachment" => ["file1"], "sensitive" => true}
+        "object" => %{"tag" => ["test"], "attachment" => ["file1"], "sensitive" => true}
       }
 
       assert TagPolicy.filter(message) == {:ok, except_message}
diff --git a/test/pleroma/web/activity_pub/mrf_test.exs b/test/pleroma/web/activity_pub/mrf_test.exs
index 7c1eef7e0..61d308b97 100644
--- a/test/pleroma/web/activity_pub/mrf_test.exs
+++ b/test/pleroma/web/activity_pub/mrf_test.exs
@@ -68,7 +68,12 @@ test "it works as expected with noop policy" do
       clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.NoOpPolicy])
 
       expected = %{
-        mrf_policies: ["NoOpPolicy"],
+        mrf_policies: ["NoOpPolicy", "HashtagPolicy"],
+        mrf_hashtag: %{
+          federated_timeline_removal: [],
+          reject: [],
+          sensitive: ["nsfw"]
+        },
         exclusions: false
       }
 
@@ -79,8 +84,13 @@ test "it works as expected with mock policy" do
       clear_config([:mrf, :policies], [MRFModuleMock])
 
       expected = %{
-        mrf_policies: ["MRFModuleMock"],
+        mrf_policies: ["MRFModuleMock", "HashtagPolicy"],
         mrf_module_mock: "some config data",
+        mrf_hashtag: %{
+          federated_timeline_removal: [],
+          reject: [],
+          sensitive: ["nsfw"]
+        },
         exclusions: false
       }
 
diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs
index 7c97fa8f8..a7894a8d0 100644
--- a/test/pleroma/web/activity_pub/transmogrifier_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs
@@ -153,15 +153,6 @@ test "it turns mentions into tags" do
       end
     end
 
-    test "it adds the sensitive property" do
-      user = insert(:user)
-
-      {:ok, activity} = CommonAPI.post(user, %{status: "#nsfw hey"})
-      {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
-
-      assert modified["object"]["sensitive"]
-    end
-
     test "it adds the json-ld context and the conversation property" do
       user = insert(:user)
 

From 3aae5231b2c8f669eadba9228cece254349dd2aa Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Tue, 2 Mar 2021 20:49:17 +0400
Subject: [PATCH 053/339] Add OpenAPI spec for AdminAPI.UserController

---
 CHANGELOG.md                                  |   1 +
 lib/pleroma/user.ex                           |   7 -
 .../admin_api/controllers/user_controller.ex  | 128 +++---
 .../web/admin_api/views/account_view.ex       |  19 +-
 .../operations/admin/user_operation.ex        | 389 ++++++++++++++++++
 lib/pleroma/web/router.ex                     |   2 +-
 .../controllers/user_controller_test.exs      | 118 +++---
 7 files changed, 539 insertions(+), 125 deletions(-)
 create mode 100644 lib/pleroma/web/api_spec/operations/admin/user_operation.ex

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 812816f48..78f21e69f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -64,6 +64,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 <details>
   <summary>API Changes</summary>
 - Admin API: (`GET /api/pleroma/admin/users`) filter users by `unconfirmed` status and `actor_type`.
+- Admin API: OpenAPI spec for the user-related operations
 - Pleroma API: `GET /api/v2/pleroma/chats` added. It is exactly like `GET /api/v1/pleroma/chats` except supports pagination.
 - Pleroma API: Add `idempotency_key` to the chat message entity that can be used for optimistic message sending.
 - Pleroma API: (`GET /api/v1/pleroma/federation_status`) Add a way to get a list of unreachable instances.
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 9942617d8..c1aa0f716 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -2255,13 +2255,6 @@ def update_background(user, background) do
     |> update_and_set_cache()
   end
 
-  def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
-    %{
-      admin: is_admin,
-      moderator: is_moderator
-    }
-  end
-
   def validate_fields(changeset, remote? \\ false) do
     limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
     limit = Config.get([:instance, limit_name], 0)
diff --git a/lib/pleroma/web/admin_api/controllers/user_controller.ex b/lib/pleroma/web/admin_api/controllers/user_controller.ex
index 65bc63cb9..d3e4c18a3 100644
--- a/lib/pleroma/web/admin_api/controllers/user_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/user_controller.ex
@@ -13,16 +13,17 @@ defmodule Pleroma.Web.AdminAPI.UserController do
   alias Pleroma.Web.ActivityPub.Builder
   alias Pleroma.Web.ActivityPub.Pipeline
   alias Pleroma.Web.AdminAPI
-  alias Pleroma.Web.AdminAPI.AccountView
   alias Pleroma.Web.AdminAPI.Search
   alias Pleroma.Web.Plugs.OAuthScopesPlug
 
   @users_page_size 50
 
+  plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
   plug(
     OAuthScopesPlug,
     %{scopes: ["admin:read:accounts"]}
-    when action in [:list, :show]
+    when action in [:index, :show]
   )
 
   plug(
@@ -44,13 +45,19 @@ defmodule Pleroma.Web.AdminAPI.UserController do
     when action in [:follow, :unfollow]
   )
 
+  plug(:put_view, Pleroma.Web.AdminAPI.AccountView)
+
   action_fallback(AdminAPI.FallbackController)
 
-  def delete(conn, %{"nickname" => nickname}) do
-    delete(conn, %{"nicknames" => [nickname]})
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.UserOperation
+
+  def delete(conn, %{nickname: nickname}) do
+    conn
+    |> Map.put(:body_params, %{nicknames: [nickname]})
+    |> delete(%{})
   end
 
-  def delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+  def delete(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
 
     Enum.each(users, fn user ->
@@ -67,10 +74,16 @@ def delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
     json(conn, nicknames)
   end
 
-  def follow(%{assigns: %{user: admin}} = conn, %{
-        "follower" => follower_nick,
-        "followed" => followed_nick
-      }) do
+  def follow(
+        %{
+          assigns: %{user: admin},
+          body_params: %{
+            follower: follower_nick,
+            followed: followed_nick
+          }
+        } = conn,
+        _
+      ) do
     with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
          %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
       User.follow(follower, followed)
@@ -86,10 +99,16 @@ def follow(%{assigns: %{user: admin}} = conn, %{
     json(conn, "ok")
   end
 
-  def unfollow(%{assigns: %{user: admin}} = conn, %{
-        "follower" => follower_nick,
-        "followed" => followed_nick
-      }) do
+  def unfollow(
+        %{
+          assigns: %{user: admin},
+          body_params: %{
+            follower: follower_nick,
+            followed: followed_nick
+          }
+        } = conn,
+        _
+      ) do
     with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
          %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
       User.unfollow(follower, followed)
@@ -105,9 +124,10 @@ def unfollow(%{assigns: %{user: admin}} = conn, %{
     json(conn, "ok")
   end
 
-  def create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
+  def create(%{assigns: %{user: admin}, body_params: %{users: users}} = conn, _) do
     changesets =
-      Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
+      users
+      |> Enum.map(fn %{nickname: nickname, email: email, password: password} ->
         user_data = %{
           nickname: nickname,
           name: nickname,
@@ -124,52 +144,49 @@ def create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
       end)
 
     case Pleroma.Repo.transaction(changesets) do
-      {:ok, users} ->
-        res =
-          users
+      {:ok, users_map} ->
+        users =
+          users_map
           |> Map.values()
           |> Enum.map(fn user ->
             {:ok, user} = User.post_register_action(user)
 
             user
           end)
-          |> Enum.map(&AccountView.render("created.json", %{user: &1}))
 
         ModerationLog.insert_log(%{
           actor: admin,
-          subjects: Map.values(users),
+          subjects: users,
           action: "create"
         })
 
-        json(conn, res)
+        render(conn, "created_many.json", users: users)
 
       {:error, id, changeset, _} ->
-        res =
+        changesets =
           Enum.map(changesets.operations, fn
-            {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
-              AccountView.render("create-error.json", %{changeset: changeset})
+            {^id, {:changeset, _current_changeset, _}} ->
+              changeset
 
             {_, {:changeset, current_changeset, _}} ->
-              AccountView.render("create-error.json", %{changeset: current_changeset})
+              current_changeset
           end)
 
         conn
         |> put_status(:conflict)
-        |> json(res)
+        |> render("create_errors.json", changesets: changesets)
     end
   end
 
-  def show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
+  def show(%{assigns: %{user: admin}} = conn, %{nickname: nickname}) do
     with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
-      conn
-      |> put_view(AccountView)
-      |> render("show.json", %{user: user})
+      render(conn, "show.json", %{user: user})
     else
       _ -> {:error, :not_found}
     end
   end
 
-  def toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
+  def toggle_activation(%{assigns: %{user: admin}} = conn, %{nickname: nickname}) do
     user = User.get_cached_by_nickname(nickname)
 
     {:ok, updated_user} = User.set_activation(user, !user.is_active)
@@ -182,12 +199,10 @@ def toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nicknam
       action: action
     })
 
-    conn
-    |> put_view(AccountView)
-    |> render("show.json", %{user: updated_user})
+    render(conn, "show.json", user: updated_user)
   end
 
-  def activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+  def activate(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
     {:ok, updated_users} = User.set_activation(users, true)
 
@@ -197,12 +212,10 @@ def activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
       action: "activate"
     })
 
-    conn
-    |> put_view(AccountView)
-    |> render("index.json", %{users: Keyword.values(updated_users)})
+    render(conn, "index.json", users: Keyword.values(updated_users))
   end
 
-  def deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+  def deactivate(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
     {:ok, updated_users} = User.set_activation(users, false)
 
@@ -212,12 +225,10 @@ def deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) d
       action: "deactivate"
     })
 
-    conn
-    |> put_view(AccountView)
-    |> render("index.json", %{users: Keyword.values(updated_users)})
+    render(conn, "index.json", users: Keyword.values(updated_users))
   end
 
-  def approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+  def approve(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
     {:ok, updated_users} = User.approve(users)
 
@@ -227,36 +238,27 @@ def approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
       action: "approve"
     })
 
-    conn
-    |> put_view(AccountView)
-    |> render("index.json", %{users: updated_users})
+    render(conn, "index.json", users: updated_users)
   end
 
-  def list(conn, params) do
+  def index(conn, params) do
     {page, page_size} = page_params(params)
-    filters = maybe_parse_filters(params["filters"])
+    filters = maybe_parse_filters(params[:filters])
 
     search_params =
       %{
-        query: params["query"],
+        query: params[:query],
         page: page,
         page_size: page_size,
-        tags: params["tags"],
-        name: params["name"],
-        email: params["email"],
-        actor_types: params["actor_types"]
+        tags: params[:tags],
+        name: params[:name],
+        email: params[:email],
+        actor_types: params[:actor_types]
       }
       |> Map.merge(filters)
 
     with {:ok, users, count} <- Search.user(search_params) do
-      json(
-        conn,
-        AccountView.render("index.json",
-          users: users,
-          count: count,
-          page_size: page_size
-        )
-      )
+      render(conn, "index.json", users: users, count: count, page_size: page_size)
     end
   end
 
@@ -274,8 +276,8 @@ defp maybe_parse_filters(filters) do
 
   defp page_params(params) do
     {
-      fetch_integer_param(params, "page", 1),
-      fetch_integer_param(params, "page_size", @users_page_size)
+      fetch_integer_param(params, :page, 1),
+      fetch_integer_param(params, :page_size, @users_page_size)
     }
   end
 end
diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex
index d7c63d385..e053a9b67 100644
--- a/lib/pleroma/web/admin_api/views/account_view.ex
+++ b/lib/pleroma/web/admin_api/views/account_view.ex
@@ -75,7 +75,7 @@ def render("show.json", %{user: user}) do
       "display_name" => display_name,
       "is_active" => user.is_active,
       "local" => user.local,
-      "roles" => User.roles(user),
+      "roles" => roles(user),
       "tags" => user.tags || [],
       "is_confirmed" => user.is_confirmed,
       "is_approved" => user.is_approved,
@@ -85,6 +85,10 @@ def render("show.json", %{user: user}) do
     }
   end
 
+  def render("created_many.json", %{users: users}) do
+    render_many(users, AccountView, "created.json", as: :user)
+  end
+
   def render("created.json", %{user: user}) do
     %{
       type: "success",
@@ -96,7 +100,11 @@ def render("created.json", %{user: user}) do
     }
   end
 
-  def render("create-error.json", %{changeset: %Ecto.Changeset{changes: changes, errors: errors}}) do
+  def render("create_errors.json", %{changesets: changesets}) do
+    render_many(changesets, AccountView, "create_error.json", as: :changeset)
+  end
+
+  def render("create_error.json", %{changeset: %Ecto.Changeset{changes: changes, errors: errors}}) do
     %{
       type: "error",
       code: 409,
@@ -140,4 +148,11 @@ defp parse_error(errors) do
 
   defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
   defp image_url(_), do: nil
+
+  defp roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
+    %{
+      admin: is_admin,
+      moderator: is_moderator
+    }
+  end
 end
diff --git a/lib/pleroma/web/api_spec/operations/admin/user_operation.ex b/lib/pleroma/web/api_spec/operations/admin/user_operation.ex
new file mode 100644
index 000000000..183c61236
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/admin/user_operation.ex
@@ -0,0 +1,389 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Admin.UserOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.ActorType
+  alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+  import Pleroma.Web.ApiSpec.Helpers
+
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  def index_operation do
+    %Operation{
+      tags: ["Users"],
+      summary: "List users",
+      operationId: "AdminAPI.UserController.index",
+      security: [%{"oAuth" => ["admin:read:accounts"]}],
+      parameters: [
+        Operation.parameter(:filters, :query, :string, "Comma separated list of filters"),
+        Operation.parameter(:query, :query, :string, "Search users query"),
+        Operation.parameter(:name, :query, :string, "Search by display name"),
+        Operation.parameter(:email, :query, :string, "Search by email"),
+        Operation.parameter(:page, :query, :integer, "Page Number"),
+        Operation.parameter(:page_size, :query, :integer, "Number of users to return per page"),
+        Operation.parameter(
+          :actor_types,
+          :query,
+          %Schema{type: :array, items: ActorType},
+          "Filter by actor type"
+        ),
+        Operation.parameter(
+          :tags,
+          :query,
+          %Schema{type: :array, items: %Schema{type: :string}},
+          "Filter by tags"
+        )
+        | admin_api_params()
+      ],
+      responses: %{
+        200 =>
+          Operation.response(
+            "Response",
+            "application/json",
+            %Schema{
+              type: :object,
+              properties: %{
+                users: %Schema{type: :array, items: user()},
+                count: %Schema{type: :integer},
+                page_size: %Schema{type: :integer}
+              }
+            }
+          ),
+        403 => Operation.response("Forbidden", "application/json", ApiError)
+      }
+    }
+  end
+
+  def create_operation do
+    %Operation{
+      tags: ["Users"],
+      summary: "Create a single or multiple users",
+      operationId: "AdminAPI.UserController.create",
+      security: [%{"oAuth" => ["admin:write:accounts"]}],
+      parameters: admin_api_params(),
+      requestBody:
+        request_body(
+          "Parameters",
+          %Schema{
+            description: "POST body for creating users",
+            type: :object,
+            properties: %{
+              users: %Schema{
+                type: :array,
+                items: %Schema{
+                  type: :object,
+                  properties: %{
+                    nickname: %Schema{type: :string},
+                    email: %Schema{type: :string},
+                    password: %Schema{type: :string}
+                  }
+                }
+              }
+            }
+          }
+        ),
+      responses: %{
+        200 =>
+          Operation.response("Response", "application/json", %Schema{
+            type: :array,
+            items: %Schema{
+              type: :object,
+              properties: %{
+                code: %Schema{type: :integer},
+                type: %Schema{type: :string},
+                data: %Schema{
+                  type: :object,
+                  properties: %{
+                    email: %Schema{type: :string, format: :email},
+                    nickname: %Schema{type: :string}
+                  }
+                }
+              }
+            }
+          }),
+        403 => Operation.response("Forbidden", "application/json", ApiError),
+        409 =>
+          Operation.response("Conflict", "application/json", %Schema{
+            type: :array,
+            items: %Schema{
+              type: :object,
+              properties: %{
+                code: %Schema{type: :integer},
+                error: %Schema{type: :string},
+                type: %Schema{type: :string},
+                data: %Schema{
+                  type: :object,
+                  properties: %{
+                    email: %Schema{type: :string, format: :email},
+                    nickname: %Schema{type: :string}
+                  }
+                }
+              }
+            }
+          })
+      }
+    }
+  end
+
+  def show_operation do
+    %Operation{
+      tags: ["Users"],
+      summary: "Show user",
+      operationId: "AdminAPI.UserController.show",
+      security: [%{"oAuth" => ["admin:read:accounts"]}],
+      parameters: [
+        Operation.parameter(
+          :nickname,
+          :path,
+          :string,
+          "User nickname or ID"
+        )
+        | admin_api_params()
+      ],
+      responses: %{
+        200 => Operation.response("Response", "application/json", user()),
+        403 => Operation.response("Forbidden", "application/json", ApiError),
+        404 => Operation.response("Not Found", "application/json", ApiError)
+      }
+    }
+  end
+
+  def follow_operation do
+    %Operation{
+      tags: ["Users"],
+      summary: "Follow",
+      operationId: "AdminAPI.UserController.follow",
+      security: [%{"oAuth" => ["admin:write:follows"]}],
+      parameters: admin_api_params(),
+      requestBody:
+        request_body(
+          "Parameters",
+          %Schema{
+            type: :object,
+            properties: %{
+              follower: %Schema{type: :string, description: "Follower nickname"},
+              followed: %Schema{type: :string, description: "Followed nickname"}
+            }
+          }
+        ),
+      responses: %{
+        200 => Operation.response("Response", "application/json", %Schema{type: :string}),
+        403 => Operation.response("Forbidden", "application/json", ApiError)
+      }
+    }
+  end
+
+  def unfollow_operation do
+    %Operation{
+      tags: ["Users"],
+      summary: "Unfollow",
+      operationId: "AdminAPI.UserController.unfollow",
+      security: [%{"oAuth" => ["admin:write:follows"]}],
+      parameters: admin_api_params(),
+      requestBody:
+        request_body(
+          "Parameters",
+          %Schema{
+            type: :object,
+            properties: %{
+              follower: %Schema{type: :string, description: "Follower nickname"},
+              followed: %Schema{type: :string, description: "Followed nickname"}
+            }
+          }
+        ),
+      responses: %{
+        200 => Operation.response("Response", "application/json", %Schema{type: :string}),
+        403 => Operation.response("Forbidden", "application/json", ApiError)
+      }
+    }
+  end
+
+  def approve_operation do
+    %Operation{
+      tags: ["Users"],
+      summary: "Approve multiple users",
+      operationId: "AdminAPI.UserController.approve",
+      security: [%{"oAuth" => ["admin:write:accounts"]}],
+      parameters: admin_api_params(),
+      requestBody:
+        request_body(
+          "Parameters",
+          %Schema{
+            description: "POST body for deleting multiple users",
+            type: :object,
+            properties: %{
+              nicknames: %Schema{
+                type: :array,
+                items: %Schema{type: :string}
+              }
+            }
+          }
+        ),
+      responses: %{
+        200 =>
+          Operation.response("Response", "application/json", %Schema{
+            type: :object,
+            properties: %{user: %Schema{type: :array, items: user()}}
+          }),
+        403 => Operation.response("Forbidden", "application/json", ApiError)
+      }
+    }
+  end
+
+  def toggle_activation_operation do
+    %Operation{
+      tags: ["Users"],
+      summary: "Toggle user activation",
+      operationId: "AdminAPI.UserController.toggle_activation",
+      security: [%{"oAuth" => ["admin:write:accounts"]}],
+      parameters: [
+        Operation.parameter(:nickname, :path, :string, "User nickname")
+        | admin_api_params()
+      ],
+      responses: %{
+        200 => Operation.response("Response", "application/json", user()),
+        403 => Operation.response("Forbidden", "application/json", ApiError)
+      }
+    }
+  end
+
+  def activate_operation do
+    %Operation{
+      tags: ["Users"],
+      summary: "Activate multiple users",
+      operationId: "AdminAPI.UserController.activate",
+      security: [%{"oAuth" => ["admin:write:accounts"]}],
+      parameters: admin_api_params(),
+      requestBody:
+        request_body(
+          "Parameters",
+          %Schema{
+            description: "POST body for deleting multiple users",
+            type: :object,
+            properties: %{
+              nicknames: %Schema{
+                type: :array,
+                items: %Schema{type: :string}
+              }
+            }
+          }
+        ),
+      responses: %{
+        200 =>
+          Operation.response("Response", "application/json", %Schema{
+            type: :object,
+            properties: %{user: %Schema{type: :array, items: user()}}
+          }),
+        403 => Operation.response("Forbidden", "application/json", ApiError)
+      }
+    }
+  end
+
+  def deactivate_operation do
+    %Operation{
+      tags: ["Users"],
+      summary: "Deactivates multiple users",
+      operationId: "AdminAPI.UserController.deactivate",
+      security: [%{"oAuth" => ["admin:write:accounts"]}],
+      parameters: admin_api_params(),
+      requestBody:
+        request_body(
+          "Parameters",
+          %Schema{
+            description: "POST body for deleting multiple users",
+            type: :object,
+            properties: %{
+              nicknames: %Schema{
+                type: :array,
+                items: %Schema{type: :string}
+              }
+            }
+          }
+        ),
+      responses: %{
+        200 =>
+          Operation.response("Response", "application/json", %Schema{
+            type: :object,
+            properties: %{user: %Schema{type: :array, items: user()}}
+          }),
+        403 => Operation.response("Forbidden", "application/json", ApiError)
+      }
+    }
+  end
+
+  def delete_operation do
+    %Operation{
+      tags: ["Users"],
+      summary: "Removes a single or multiple users",
+      operationId: "AdminAPI.UserController.delete",
+      security: [%{"oAuth" => ["admin:write:accounts"]}],
+      parameters: [
+        Operation.parameter(
+          :nickname,
+          :query,
+          :string,
+          "User nickname"
+        )
+        | admin_api_params()
+      ],
+      requestBody:
+        request_body(
+          "Parameters",
+          %Schema{
+            description: "POST body for deleting multiple users",
+            type: :object,
+            properties: %{
+              nicknames: %Schema{
+                type: :array,
+                items: %Schema{type: :string}
+              }
+            }
+          }
+        ),
+      responses: %{
+        200 =>
+          Operation.response("Response", "application/json", %Schema{
+            description: "Array of nicknames",
+            type: :array,
+            items: %Schema{type: :string}
+          }),
+        403 => Operation.response("Forbidden", "application/json", ApiError)
+      }
+    }
+  end
+
+  defp user do
+    %Schema{
+      type: :object,
+      properties: %{
+        id: %Schema{type: :string},
+        email: %Schema{type: :string, format: :email},
+        avatar: %Schema{type: :string, format: :uri},
+        nickname: %Schema{type: :string},
+        display_name: %Schema{type: :string},
+        is_active: %Schema{type: :boolean},
+        local: %Schema{type: :boolean},
+        roles: %Schema{
+          type: :object,
+          properties: %{
+            admin: %Schema{type: :boolean},
+            moderator: %Schema{type: :boolean}
+          }
+        },
+        tags: %Schema{type: :array, items: %Schema{type: :string}},
+        is_confirmed: %Schema{type: :boolean},
+        is_approved: %Schema{type: :boolean},
+        url: %Schema{type: :string, format: :uri},
+        registration_reason: %Schema{type: :string, nullable: true},
+        actor_type: %Schema{type: :string}
+      }
+    }
+  end
+end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 72ad14f05..de0bd27d7 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -204,7 +204,7 @@ defmodule Pleroma.Web.Router do
     get("/users/:nickname/credentials", AdminAPIController, :show_user_credentials)
     patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials)
 
-    get("/users", UserController, :list)
+    get("/users", UserController, :index)
     get("/users/:nickname", UserController, :show)
     get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
     get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
diff --git a/test/pleroma/web/admin_api/controllers/user_controller_test.exs b/test/pleroma/web/admin_api/controllers/user_controller_test.exs
index beb8a5d58..31319b5e5 100644
--- a/test/pleroma/web/admin_api/controllers/user_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/user_controller_test.exs
@@ -44,7 +44,7 @@ test "with valid `admin_token` query parameter, skips OAuth scopes check" do
 
     conn = get(build_conn(), "/api/pleroma/admin/users/#{user.nickname}?admin_token=password123")
 
-    assert json_response(conn, 200)
+    assert json_response_and_validate_schema(conn, 200)
   end
 
   test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or broader scope",
@@ -67,7 +67,7 @@ test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or bro
         |> assign(:token, good_token)
         |> get(url)
 
-      assert json_response(conn, 200)
+      assert json_response_and_validate_schema(conn, 200)
     end
 
     for good_token <- [good_token1, good_token2, good_token3] do
@@ -87,7 +87,7 @@ test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or bro
         |> assign(:token, bad_token)
         |> get(url)
 
-      assert json_response(conn, :forbidden)
+      assert json_response_and_validate_schema(conn, :forbidden)
     end
   end
 
@@ -131,7 +131,7 @@ test "single user", %{admin: admin, conn: conn} do
         assert ModerationLog.get_log_entry_message(log_entry) ==
                  "@#{admin.nickname} deleted users: @#{user.nickname}"
 
-        assert json_response(conn, 200) == [user.nickname]
+        assert json_response_and_validate_schema(conn, 200) == [user.nickname]
 
         user = Repo.get(User, user.id)
         refute user.is_active
@@ -152,28 +152,30 @@ test "multiple users", %{admin: admin, conn: conn} do
       user_one = insert(:user)
       user_two = insert(:user)
 
-      conn =
+      response =
         conn
         |> put_req_header("accept", "application/json")
+        |> put_req_header("content-type", "application/json")
         |> delete("/api/pleroma/admin/users", %{
           nicknames: [user_one.nickname, user_two.nickname]
         })
+        |> json_response_and_validate_schema(200)
 
       log_entry = Repo.one(ModerationLog)
 
       assert ModerationLog.get_log_entry_message(log_entry) ==
                "@#{admin.nickname} deleted users: @#{user_one.nickname}, @#{user_two.nickname}"
 
-      response = json_response(conn, 200)
       assert response -- [user_one.nickname, user_two.nickname] == []
     end
   end
 
   describe "/api/pleroma/admin/users" do
     test "Create", %{conn: conn} do
-      conn =
+      response =
         conn
         |> put_req_header("accept", "application/json")
+        |> put_req_header("content-type", "application/json")
         |> post("/api/pleroma/admin/users", %{
           "users" => [
             %{
@@ -188,8 +190,9 @@ test "Create", %{conn: conn} do
             }
           ]
         })
+        |> json_response_and_validate_schema(200)
+        |> Enum.map(&Map.get(&1, "type"))
 
-      response = json_response(conn, 200) |> Enum.map(&Map.get(&1, "type"))
       assert response == ["success", "success"]
 
       log_entry = Repo.one(ModerationLog)
@@ -203,6 +206,7 @@ test "Cannot create user with existing email", %{conn: conn} do
       conn =
         conn
         |> put_req_header("accept", "application/json")
+        |> put_req_header("content-type", "application/json")
         |> post("/api/pleroma/admin/users", %{
           "users" => [
             %{
@@ -213,7 +217,7 @@ test "Cannot create user with existing email", %{conn: conn} do
           ]
         })
 
-      assert json_response(conn, 409) == [
+      assert json_response_and_validate_schema(conn, 409) == [
                %{
                  "code" => 409,
                  "data" => %{
@@ -232,6 +236,7 @@ test "Cannot create user with existing nickname", %{conn: conn} do
       conn =
         conn
         |> put_req_header("accept", "application/json")
+        |> put_req_header("content-type", "application/json")
         |> post("/api/pleroma/admin/users", %{
           "users" => [
             %{
@@ -242,7 +247,7 @@ test "Cannot create user with existing nickname", %{conn: conn} do
           ]
         })
 
-      assert json_response(conn, 409) == [
+      assert json_response_and_validate_schema(conn, 409) == [
                %{
                  "code" => 409,
                  "data" => %{
@@ -261,6 +266,7 @@ test "Multiple user creation works in transaction", %{conn: conn} do
       conn =
         conn
         |> put_req_header("accept", "application/json")
+        |> put_req_header("content-type", "application/json")
         |> post("/api/pleroma/admin/users", %{
           "users" => [
             %{
@@ -276,7 +282,7 @@ test "Multiple user creation works in transaction", %{conn: conn} do
           ]
         })
 
-      assert json_response(conn, 409) == [
+      assert json_response_and_validate_schema(conn, 409) == [
                %{
                  "code" => 409,
                  "data" => %{
@@ -307,7 +313,7 @@ test "Show", %{conn: conn} do
 
       conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
 
-      assert user_response(user) == json_response(conn, 200)
+      assert user_response(user) == json_response_and_validate_schema(conn, 200)
     end
 
     test "when the user doesn't exist", %{conn: conn} do
@@ -315,7 +321,7 @@ test "when the user doesn't exist", %{conn: conn} do
 
       conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
 
-      assert %{"error" => "Not found"} == json_response(conn, 404)
+      assert %{"error" => "Not found"} == json_response_and_validate_schema(conn, 404)
     end
   end
 
@@ -326,6 +332,7 @@ test "allows to force-follow another user", %{admin: admin, conn: conn} do
 
       conn
       |> put_req_header("accept", "application/json")
+      |> put_req_header("content-type", "application/json")
       |> post("/api/pleroma/admin/users/follow", %{
         "follower" => follower.nickname,
         "followed" => user.nickname
@@ -352,6 +359,7 @@ test "allows to force-unfollow another user", %{admin: admin, conn: conn} do
 
       conn
       |> put_req_header("accept", "application/json")
+      |> put_req_header("content-type", "application/json")
       |> post("/api/pleroma/admin/users/unfollow", %{
         "follower" => follower.nickname,
         "followed" => user.nickname
@@ -395,7 +403,7 @@ test "renders users array for the first page", %{conn: conn, admin: admin} do
         ]
         |> Enum.sort_by(& &1["nickname"])
 
-      assert json_response(conn, 200) == %{
+      assert json_response_and_validate_schema(conn, 200) == %{
                "count" => 3,
                "page_size" => 50,
                "users" => users
@@ -410,7 +418,7 @@ test "pagination works correctly with service users", %{conn: conn} do
       assert %{"count" => 26, "page_size" => 10, "users" => users1} =
                conn
                |> get("/api/pleroma/admin/users?page=1&filters=", %{page_size: "10"})
-               |> json_response(200)
+               |> json_response_and_validate_schema(200)
 
       assert Enum.count(users1) == 10
       assert service1 not in users1
@@ -418,7 +426,7 @@ test "pagination works correctly with service users", %{conn: conn} do
       assert %{"count" => 26, "page_size" => 10, "users" => users2} =
                conn
                |> get("/api/pleroma/admin/users?page=2&filters=", %{page_size: "10"})
-               |> json_response(200)
+               |> json_response_and_validate_schema(200)
 
       assert Enum.count(users2) == 10
       assert service1 not in users2
@@ -426,7 +434,7 @@ test "pagination works correctly with service users", %{conn: conn} do
       assert %{"count" => 26, "page_size" => 10, "users" => users3} =
                conn
                |> get("/api/pleroma/admin/users?page=3&filters=", %{page_size: "10"})
-               |> json_response(200)
+               |> json_response_and_validate_schema(200)
 
       assert Enum.count(users3) == 6
       assert service1 not in users3
@@ -437,7 +445,7 @@ test "renders empty array for the second page", %{conn: conn} do
 
       conn = get(conn, "/api/pleroma/admin/users?page=2")
 
-      assert json_response(conn, 200) == %{
+      assert json_response_and_validate_schema(conn, 200) == %{
                "count" => 2,
                "page_size" => 50,
                "users" => []
@@ -449,7 +457,7 @@ test "regular search", %{conn: conn} do
 
       conn = get(conn, "/api/pleroma/admin/users?query=bo")
 
-      assert json_response(conn, 200) == %{
+      assert json_response_and_validate_schema(conn, 200) == %{
                "count" => 1,
                "page_size" => 50,
                "users" => [user_response(user, %{"local" => true})]
@@ -462,7 +470,7 @@ test "search by domain", %{conn: conn} do
 
       conn = get(conn, "/api/pleroma/admin/users?query=domain.com")
 
-      assert json_response(conn, 200) == %{
+      assert json_response_and_validate_schema(conn, 200) == %{
                "count" => 1,
                "page_size" => 50,
                "users" => [user_response(user)]
@@ -475,7 +483,7 @@ test "search by full nickname", %{conn: conn} do
 
       conn = get(conn, "/api/pleroma/admin/users?query=nickname@domain.com")
 
-      assert json_response(conn, 200) == %{
+      assert json_response_and_validate_schema(conn, 200) == %{
                "count" => 1,
                "page_size" => 50,
                "users" => [user_response(user)]
@@ -488,7 +496,7 @@ test "search by display name", %{conn: conn} do
 
       conn = get(conn, "/api/pleroma/admin/users?name=display")
 
-      assert json_response(conn, 200) == %{
+      assert json_response_and_validate_schema(conn, 200) == %{
                "count" => 1,
                "page_size" => 50,
                "users" => [user_response(user)]
@@ -501,7 +509,7 @@ test "search by email", %{conn: conn} do
 
       conn = get(conn, "/api/pleroma/admin/users?email=email@example.com")
 
-      assert json_response(conn, 200) == %{
+      assert json_response_and_validate_schema(conn, 200) == %{
                "count" => 1,
                "page_size" => 50,
                "users" => [user_response(user)]
@@ -514,7 +522,7 @@ test "regular search with page size", %{conn: conn} do
 
       conn1 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=1")
 
-      assert json_response(conn1, 200) == %{
+      assert json_response_and_validate_schema(conn1, 200) == %{
                "count" => 2,
                "page_size" => 1,
                "users" => [user_response(user)]
@@ -522,7 +530,7 @@ test "regular search with page size", %{conn: conn} do
 
       conn2 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=2")
 
-      assert json_response(conn2, 200) == %{
+      assert json_response_and_validate_schema(conn2, 200) == %{
                "count" => 2,
                "page_size" => 1,
                "users" => [user_response(user2)]
@@ -542,7 +550,7 @@ test "only local users" do
         |> assign(:token, token)
         |> get("/api/pleroma/admin/users?query=bo&filters=local")
 
-      assert json_response(conn, 200) == %{
+      assert json_response_and_validate_schema(conn, 200) == %{
                "count" => 1,
                "page_size" => 50,
                "users" => [user_response(user)]
@@ -570,7 +578,7 @@ test "only local users with no query", %{conn: conn, admin: old_admin} do
         ]
         |> Enum.sort_by(& &1["nickname"])
 
-      assert json_response(conn, 200) == %{
+      assert json_response_and_validate_schema(conn, 200) == %{
                "count" => 3,
                "page_size" => 50,
                "users" => users
@@ -587,7 +595,7 @@ test "only unconfirmed users", %{conn: conn} do
       result =
         conn
         |> get("/api/pleroma/admin/users?filters=unconfirmed")
-        |> json_response(200)
+        |> json_response_and_validate_schema(200)
 
       users =
         Enum.map([old_user, sad_user], fn user ->
@@ -620,7 +628,7 @@ test "only unapproved users", %{conn: conn} do
         )
       ]
 
-      assert json_response(conn, 200) == %{
+      assert json_response_and_validate_schema(conn, 200) == %{
                "count" => 1,
                "page_size" => 50,
                "users" => users
@@ -647,7 +655,7 @@ test "load only admins", %{conn: conn, admin: admin} do
         ]
         |> Enum.sort_by(& &1["nickname"])
 
-      assert json_response(conn, 200) == %{
+      assert json_response_and_validate_schema(conn, 200) == %{
                "count" => 2,
                "page_size" => 50,
                "users" => users
@@ -661,7 +669,7 @@ test "load only moderators", %{conn: conn} do
 
       conn = get(conn, "/api/pleroma/admin/users?filters=is_moderator")
 
-      assert json_response(conn, 200) == %{
+      assert json_response_and_validate_schema(conn, 200) == %{
                "count" => 1,
                "page_size" => 50,
                "users" => [
@@ -682,8 +690,8 @@ test "load users with actor_type is Person", %{admin: admin, conn: conn} do
 
       response =
         conn
-        |> get(user_path(conn, :list), %{actor_types: ["Person"]})
-        |> json_response(200)
+        |> get(user_path(conn, :index), %{actor_types: ["Person"]})
+        |> json_response_and_validate_schema(200)
 
       users =
         [
@@ -705,8 +713,8 @@ test "load users with actor_type is Person and Service", %{admin: admin, conn: c
 
       response =
         conn
-        |> get(user_path(conn, :list), %{actor_types: ["Person", "Service"]})
-        |> json_response(200)
+        |> get(user_path(conn, :index), %{actor_types: ["Person", "Service"]})
+        |> json_response_and_validate_schema(200)
 
       users =
         [
@@ -728,8 +736,8 @@ test "load users with actor_type is Service", %{conn: conn} do
 
       response =
         conn
-        |> get(user_path(conn, :list), %{actor_types: ["Service"]})
-        |> json_response(200)
+        |> get(user_path(conn, :index), %{actor_types: ["Service"]})
+        |> json_response_and_validate_schema(200)
 
       users = [user_response(user_service, %{"actor_type" => "Service"})]
 
@@ -751,7 +759,7 @@ test "load users with tags list", %{conn: conn} do
         ]
         |> Enum.sort_by(& &1["nickname"])
 
-      assert json_response(conn, 200) == %{
+      assert json_response_and_validate_schema(conn, 200) == %{
                "count" => 2,
                "page_size" => 50,
                "users" => users
@@ -776,7 +784,7 @@ test "`active` filters out users pending approval", %{token: token} do
                  %{"id" => ^admin_id},
                  %{"id" => ^user_id}
                ]
-             } = json_response(conn, 200)
+             } = json_response_and_validate_schema(conn, 200)
     end
 
     test "it works with multiple filters" do
@@ -793,7 +801,7 @@ test "it works with multiple filters" do
         |> assign(:token, token)
         |> get("/api/pleroma/admin/users?filters=deactivated,external")
 
-      assert json_response(conn, 200) == %{
+      assert json_response_and_validate_schema(conn, 200) == %{
                "count" => 1,
                "page_size" => 50,
                "users" => [user_response(user)]
@@ -805,7 +813,7 @@ test "it omits relay user", %{admin: admin, conn: conn} do
 
       conn = get(conn, "/api/pleroma/admin/users")
 
-      assert json_response(conn, 200) == %{
+      assert json_response_and_validate_schema(conn, 200) == %{
                "count" => 1,
                "page_size" => 50,
                "users" => [
@@ -820,13 +828,14 @@ test "PATCH /api/pleroma/admin/users/activate", %{admin: admin, conn: conn} do
     user_two = insert(:user, is_active: false)
 
     conn =
-      patch(
-        conn,
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> patch(
         "/api/pleroma/admin/users/activate",
         %{nicknames: [user_one.nickname, user_two.nickname]}
       )
 
-    response = json_response(conn, 200)
+    response = json_response_and_validate_schema(conn, 200)
     assert Enum.map(response["users"], & &1["is_active"]) == [true, true]
 
     log_entry = Repo.one(ModerationLog)
@@ -840,13 +849,14 @@ test "PATCH /api/pleroma/admin/users/deactivate", %{admin: admin, conn: conn} do
     user_two = insert(:user, is_active: true)
 
     conn =
-      patch(
-        conn,
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> patch(
         "/api/pleroma/admin/users/deactivate",
         %{nicknames: [user_one.nickname, user_two.nickname]}
       )
 
-    response = json_response(conn, 200)
+    response = json_response_and_validate_schema(conn, 200)
     assert Enum.map(response["users"], & &1["is_active"]) == [false, false]
 
     log_entry = Repo.one(ModerationLog)
@@ -860,13 +870,14 @@ test "PATCH /api/pleroma/admin/users/approve", %{admin: admin, conn: conn} do
     user_two = insert(:user, is_approved: false)
 
     conn =
-      patch(
-        conn,
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> patch(
         "/api/pleroma/admin/users/approve",
         %{nicknames: [user_one.nickname, user_two.nickname]}
       )
 
-    response = json_response(conn, 200)
+    response = json_response_and_validate_schema(conn, 200)
     assert Enum.map(response["users"], & &1["is_approved"]) == [true, true]
 
     log_entry = Repo.one(ModerationLog)
@@ -878,9 +889,12 @@ test "PATCH /api/pleroma/admin/users/approve", %{admin: admin, conn: conn} do
   test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do
     user = insert(:user)
 
-    conn = patch(conn, "/api/pleroma/admin/users/#{user.nickname}/toggle_activation")
+    conn =
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> patch("/api/pleroma/admin/users/#{user.nickname}/toggle_activation")
 
-    assert json_response(conn, 200) ==
+    assert json_response_and_validate_schema(conn, 200) ==
              user_response(
                user,
                %{"is_active" => !user.is_active}

From 85b2387f665045a303486d10e6879a46a7ab922e Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 2 Mar 2021 11:37:37 -0600
Subject: [PATCH 054/339] Fix build_application/1 match

---
 lib/pleroma/web/mastodon_api/views/status_view.ex | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index bac897a57..a7e762ac1 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -536,6 +536,8 @@ defp build_emoji_map(emoji, users, current_user) do
   end
 
   @spec build_application(map() | nil) :: map() | nil
-  defp build_application(%{type: _type, name: name, url: url}), do: %{name: name, website: url}
+  defp build_application(%{"type" => _type, "name" => name, "url" => url}),
+    do: %{name: name, website: url}
+
   defp build_application(_), do: nil
 end

From f0208980e48ee361f9eaa40352f519a1b95ace28 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 2 Mar 2021 12:29:16 -0600
Subject: [PATCH 055/339] Test both ingestion of post in the status controller
 and the correct response during the view

---
 .../controllers/status_controller_test.exs            | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
index bd385bccd..634ebf79c 100644
--- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
@@ -376,6 +376,17 @@ test "discloses application metadata when enabled" do
           "status" => "cofe is my copilot"
         })
 
+      assert %{
+               "content" => "cofe is my copilot"
+             } = json_response_and_validate_schema(result, 200)
+
+      activity = result.assigns.activity.id
+
+      result =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> get("api/v1/statuses/#{activity}")
+
       assert %{
                "content" => "cofe is my copilot",
                "application" => %{

From ccbf162088951e4b7f28291ca4cd9b9280b40857 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 2 Mar 2021 12:33:32 -0600
Subject: [PATCH 056/339] Actually test viewing status after ingestion

---
 .../controllers/status_controller_test.exs             | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
index 634ebf79c..39ab90ba6 100644
--- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
@@ -407,6 +407,16 @@ test "hides application metadata when disabled" do
           "status" => "club mate is my wingman"
         })
 
+      assert %{"content" => "club mate is my wingman"} =
+               json_response_and_validate_schema(result, 200)
+
+      activity = result.assigns.activity.id
+
+      result =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> get("api/v1/statuses/#{activity}")
+
       assert %{
                "content" => "club mate is my wingman",
                "application" => nil

From 913d53b7d7301445fdb0fc8dbe5ecf8b59aafa43 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 2 Mar 2021 14:04:50 -0600
Subject: [PATCH 057/339] Remove useless header on the get request

---
 .../web/mastodon_api/controllers/status_controller_test.exs     | 2 --
 1 file changed, 2 deletions(-)

diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
index 39ab90ba6..f616f405e 100644
--- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
@@ -384,7 +384,6 @@ test "discloses application metadata when enabled" do
 
       result =
         conn
-        |> put_req_header("content-type", "application/json")
         |> get("api/v1/statuses/#{activity}")
 
       assert %{
@@ -414,7 +413,6 @@ test "hides application metadata when disabled" do
 
       result =
         conn
-        |> put_req_header("content-type", "application/json")
         |> get("api/v1/statuses/#{activity}")
 
       assert %{

From 8d601d3b234cfe2a6a942dd156712cc400af8500 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 2 Mar 2021 14:14:38 -0600
Subject: [PATCH 058/339] Make the object reference in both render("show.json",
 _) functions consistently named

---
 lib/pleroma/web/mastodon_api/views/status_view.ex | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index a7e762ac1..f3f54e03d 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -124,16 +124,16 @@ def render(
       ) do
     user = CommonAPI.get_user(activity.data["actor"])
     created_at = Utils.to_masto_date(activity.data["published"])
-    activity_object = Object.normalize(activity, fetch: false)
+    object = Object.normalize(activity, fetch: false)
 
     reblogged_parent_activity =
       if opts[:parent_activities] do
         Activity.Queries.find_by_object_ap_id(
           opts[:parent_activities],
-          activity_object.data["id"]
+          object.data["id"]
         )
       else
-        Activity.create_by_object_ap_id(activity_object.data["id"])
+        Activity.create_by_object_ap_id(object.data["id"])
         |> Activity.with_preloaded_bookmark(opts[:for])
         |> Activity.with_set_thread_muted_field(opts[:for])
         |> Repo.one()
@@ -142,7 +142,7 @@ def render(
     reblog_rendering_opts = Map.put(opts, :activity, reblogged_parent_activity)
     reblogged = render("show.json", reblog_rendering_opts)
 
-    favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || [])
+    favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
 
     bookmarked = Activity.get_bookmark(reblogged_parent_activity, opts[:for]) != nil
 
@@ -154,8 +154,8 @@ def render(
 
     %{
       id: to_string(activity.id),
-      uri: activity_object.data["id"],
-      url: activity_object.data["id"],
+      uri: object.data["id"],
+      url: object.data["id"],
       account:
         AccountView.render("show.json", %{
           user: user,
@@ -180,7 +180,7 @@ def render(
       media_attachments: reblogged[:media_attachments] || [],
       mentions: mentions,
       tags: reblogged[:tags] || [],
-      application: build_application(activity_object.data["generator"]),
+      application: build_application(object.data["generator"]),
       language: nil,
       emojis: [],
       pleroma: %{

From 5b8cceba09bda6a01adee4939e3c2521c2ea037e Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 2 Mar 2021 18:17:32 -0600
Subject: [PATCH 059/339] Fix migration in cases where database name has a
 hyphen

---
 .../20210121080964_add_default_text_search_config.exs           | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/priv/repo/migrations/20210121080964_add_default_text_search_config.exs b/priv/repo/migrations/20210121080964_add_default_text_search_config.exs
index 09b6cccc9..27f600b70 100644
--- a/priv/repo/migrations/20210121080964_add_default_text_search_config.exs
+++ b/priv/repo/migrations/20210121080964_add_default_text_search_config.exs
@@ -4,7 +4,7 @@ defmodule Pleroma.Repo.Migrations.AddDefaultTextSearchConfig do
   def change do
     execute("DO $$
     BEGIN
-    execute 'ALTER DATABASE '||current_database()||' SET default_text_search_config = ''english'' ';
+    execute 'ALTER DATABASE \"'||current_database()||'\" SET default_text_search_config = ''english'' ';
     END
     $$;")
   end

From c5352e90be363f88f011ed5a63129caf3ee1a9fc Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Wed, 3 Mar 2021 13:56:40 +0100
Subject: [PATCH 060/339] Changelog, mix: merge in stable

---
 CHANGELOG.md | 4 ++++
 mix.exs      | 2 +-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a55ebbf8a..40c423273 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
+## Unreleased
+
+## Unreleased (Patch)
+
 ## [2.3.0] - 2020-03-01
 
 ### Security
diff --git a/mix.exs b/mix.exs
index 436381f32..ec6e92df7 100644
--- a/mix.exs
+++ b/mix.exs
@@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
   def project do
     [
       app: :pleroma,
-      version: version("2.3.0"),
+      version: version("2.3.50"),
       elixir: "~> 1.9",
       elixirc_paths: elixirc_paths(Mix.env()),
       compilers: [:phoenix, :gettext] ++ Mix.compilers(),

From 2e296c079f0666a8239a0d3ce5b5fba6baf45a29 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Wed, 3 Mar 2021 15:33:06 +0100
Subject: [PATCH 061/339] Revert "StatusController: Deactivate application
 support for now."

This reverts commit 024c11c18d289d4acd65d749f939ad3684f31905.
---
 .../controllers/status_controller.ex          | 20 +++++++++----------
 .../controllers/status_controller_test.exs    |  1 -
 2 files changed, 9 insertions(+), 12 deletions(-)

diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index d1a58d5e1..b051fca74 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -21,7 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.MastodonAPI.AccountView
   alias Pleroma.Web.MastodonAPI.ScheduledActivityView
-  # alias Pleroma.Web.OAuth.Token
+  alias Pleroma.Web.OAuth.Token
   alias Pleroma.Web.Plugs.OAuthScopesPlug
   alias Pleroma.Web.Plugs.RateLimiter
 
@@ -420,16 +420,14 @@ def bookmarks(%{assigns: %{user: user}} = conn, params) do
     )
   end
 
-  # Deactivated for 2.3.0
-  # defp put_application(params,
-  #   %{assigns: %{token: %Token{user: %User{} = user} = token}} = _conn) do
-  #   if user.disclose_client do
-  #     %{client_name: client_name, website: website} = Repo.preload(token, :app).app
-  #     Map.put(params, :generator, %{type: "Application", name: client_name, url: website})
-  #   else
-  #     Map.put(params, :generator, nil)
-  #   end
-  # end
+  defp put_application(params, %{assigns: %{token: %Token{user: %User{} = user} = token}} = _conn) do
+    if user.disclose_client do
+      %{client_name: client_name, website: website} = Repo.preload(token, :app).app
+      Map.put(params, :generator, %{type: "Application", name: client_name, url: website})
+    else
+      Map.put(params, :generator, nil)
+    end
+  end
 
   defp put_application(params, _), do: Map.put(params, :generator, nil)
 end
diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
index e76c2760d..bd385bccd 100644
--- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
@@ -358,7 +358,6 @@ test "posting a direct status", %{conn: conn} do
       assert activity.data["cc"] == []
     end
 
-    @tag :skip
     test "discloses application metadata when enabled" do
       user = insert(:user, disclose_client: true)
       %{user: _user, token: token, conn: conn} = oauth_access(["write:statuses"], user: user)

From 10f402af6d0f088aa6ad8a3f26b5e226a2287634 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Wed, 3 Mar 2021 15:35:25 +0100
Subject: [PATCH 062/339] Changelog: Re-add application support

---
 CHANGELOG.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 40c423273..ed08701fd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## Unreleased
 
+- The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
+
 ## Unreleased (Patch)
 
 ## [2.3.0] - 2020-03-01

From 5856f51717c12f4c6b0b89e480ff689c8480393d Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Wed, 3 Mar 2021 23:09:30 +0300
Subject: [PATCH 063/339] [#3213] ActivityPub hashtags filtering refactoring.
 Test fix.

---
 lib/pleroma/repo.ex                           |  2 ++
 lib/pleroma/web/activity_pub/activity_pub.ex  | 29 ++++++-------------
 mix.exs                                       |  1 +
 mix.lock                                      |  1 +
 .../web/activity_pub/activity_pub_test.exs    |  2 +-
 5 files changed, 14 insertions(+), 21 deletions(-)

diff --git a/lib/pleroma/repo.ex b/lib/pleroma/repo.ex
index 61b64ed3e..b8ea06e33 100644
--- a/lib/pleroma/repo.ex
+++ b/lib/pleroma/repo.ex
@@ -8,6 +8,8 @@ defmodule Pleroma.Repo do
     adapter: Ecto.Adapters.Postgres,
     migration_timestamps: [type: :naive_datetime_usec]
 
+  use Ecto.Explain
+
   import Ecto.Query
   require Logger
 
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 9d557c2cd..a4b48ec9b 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -746,6 +746,13 @@ defp restrict_embedded_tag_reject_any(query, %{tag_reject: tag_reject})
 
   defp restrict_embedded_tag_reject_any(query, _), do: query
 
+  defp object_ids_query_for_tags(tags) do
+    from(hto in "hashtags_objects")
+    |> join(:inner, [hto], ht in Pleroma.Hashtag, on: hto.hashtag_id == ht.id)
+    |> where([hto, ht], ht.name in ^tags)
+    |> select([hto], hto.object_id)
+  end
+
   defp restrict_hashtag_all(_query, %{tag_all: _tag, skip_preload: true}) do
     raise_on_missing_preload()
   end
@@ -784,16 +791,7 @@ defp restrict_hashtag_any(_query, %{tag: _tag, skip_preload: true}) do
   defp restrict_hashtag_any(query, %{tag: [_ | _] = tags}) do
     from(
       [_activity, object] in query,
-      where:
-        fragment(
-          """
-          EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
-            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
-              AND hashtags_objects.object_id = ? LIMIT 1)
-          """,
-          ^tags,
-          object.id
-        )
+      where: object.id in subquery(object_ids_query_for_tags(tags))
     )
   end
 
@@ -810,16 +808,7 @@ defp restrict_hashtag_reject_any(_query, %{tag_reject: _tag_reject, skip_preload
   defp restrict_hashtag_reject_any(query, %{tag_reject: [_ | _] = tags_reject}) do
     from(
       [_activity, object] in query,
-      where:
-        fragment(
-          """
-          NOT EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
-            ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
-              AND hashtags_objects.object_id = ? LIMIT 1)
-          """,
-          ^tags_reject,
-          object.id
-        )
+      where: object.id not in subquery(object_ids_query_for_tags(tags_reject))
     )
   end
 
diff --git a/mix.exs b/mix.exs
index 50d4b4080..c06e27314 100644
--- a/mix.exs
+++ b/mix.exs
@@ -121,6 +121,7 @@ defp deps do
       {:phoenix_pubsub, "~> 2.0"},
       {:phoenix_ecto, "~> 4.0"},
       {:ecto_enum, "~> 1.4"},
+      {:ecto_explain, "~> 0.1.2"},
       {:ecto_sql, "~> 3.4.4"},
       {:postgrex, ">= 0.15.5"},
       {:oban, "~> 2.3.4"},
diff --git a/mix.lock b/mix.lock
index 3e5631c72..cb09ffead 100644
--- a/mix.lock
+++ b/mix.lock
@@ -31,6 +31,7 @@
   "earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
   "ecto": {:hex, :ecto, "3.4.6", "08f7afad3257d6eb8613309af31037e16c36808dfda5a3cd0cb4e9738db030e4", [: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", "6f13a9e2a62e75c2dcfc7207bfc65645ab387af8360db4c89fee8b5a4bf3f70b"},
   "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_explain": {:hex, :ecto_explain, "0.1.2", "a9d504cbd4adc809911f796d5ef7ebb17a576a6d32286c3d464c015bd39d5541", [:mix], [], "hexpm", "1d0e7798ae30ecf4ce34e912e5354a0c1c832b7ebceba39298270b9a9f316330"},
   "ecto_sql": {:hex, :ecto_sql, "3.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"},
   "eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"},
   "elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"},
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index f92323abe..1e1e74074 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -220,7 +220,7 @@ test "it fetches the appropriate tag-restricted posts" do
     {:ok, status_four} = CommonAPI.post(user, %{status: ". #Any1 #any2"})
     {:ok, status_five} = CommonAPI.post(user, %{status: ". #Any2 #any1"})
 
-    for hashtag_timeline_strategy <- [:eanbled, :disabled] do
+    for hashtag_timeline_strategy <- [:enabled, :disabled] do
       clear_config([:features, :improved_hashtag_timeline], hashtag_timeline_strategy)
 
       fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})

From 9876fa8e902e66a77193ebeef674a9f0e9f37657 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Thu, 4 Mar 2021 21:13:53 +0400
Subject: [PATCH 064/339] Add UserOperation to Redoc

---
 lib/pleroma/web/api_spec.ex                   |  5 +++--
 .../operations/admin/user_operation.ex        | 20 +++++++++----------
 2 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex
index adc8762dc..528cd9cf4 100644
--- a/lib/pleroma/web/api_spec.ex
+++ b/lib/pleroma/web/api_spec.ex
@@ -92,9 +92,10 @@ def spec(opts \\ []) do
               "Invites",
               "MediaProxy cache",
               "OAuth application managment",
-              "Report managment",
               "Relays",
-              "Status administration"
+              "Report managment",
+              "Status administration",
+              "User administration"
             ]
           },
           %{"name" => "Applications", "tags" => ["Applications", "Push subscriptions"]},
diff --git a/lib/pleroma/web/api_spec/operations/admin/user_operation.ex b/lib/pleroma/web/api_spec/operations/admin/user_operation.ex
index 183c61236..c9d0bfd7c 100644
--- a/lib/pleroma/web/api_spec/operations/admin/user_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/user_operation.ex
@@ -17,7 +17,7 @@ def open_api_operation(action) do
 
   def index_operation do
     %Operation{
-      tags: ["Users"],
+      tags: ["User administration"],
       summary: "List users",
       operationId: "AdminAPI.UserController.index",
       security: [%{"oAuth" => ["admin:read:accounts"]}],
@@ -63,7 +63,7 @@ def index_operation do
 
   def create_operation do
     %Operation{
-      tags: ["Users"],
+      tags: ["User administration"],
       summary: "Create a single or multiple users",
       operationId: "AdminAPI.UserController.create",
       security: [%{"oAuth" => ["admin:write:accounts"]}],
@@ -134,7 +134,7 @@ def create_operation do
 
   def show_operation do
     %Operation{
-      tags: ["Users"],
+      tags: ["User administration"],
       summary: "Show user",
       operationId: "AdminAPI.UserController.show",
       security: [%{"oAuth" => ["admin:read:accounts"]}],
@@ -157,7 +157,7 @@ def show_operation do
 
   def follow_operation do
     %Operation{
-      tags: ["Users"],
+      tags: ["User administration"],
       summary: "Follow",
       operationId: "AdminAPI.UserController.follow",
       security: [%{"oAuth" => ["admin:write:follows"]}],
@@ -182,7 +182,7 @@ def follow_operation do
 
   def unfollow_operation do
     %Operation{
-      tags: ["Users"],
+      tags: ["User administration"],
       summary: "Unfollow",
       operationId: "AdminAPI.UserController.unfollow",
       security: [%{"oAuth" => ["admin:write:follows"]}],
@@ -207,7 +207,7 @@ def unfollow_operation do
 
   def approve_operation do
     %Operation{
-      tags: ["Users"],
+      tags: ["User administration"],
       summary: "Approve multiple users",
       operationId: "AdminAPI.UserController.approve",
       security: [%{"oAuth" => ["admin:write:accounts"]}],
@@ -239,7 +239,7 @@ def approve_operation do
 
   def toggle_activation_operation do
     %Operation{
-      tags: ["Users"],
+      tags: ["User administration"],
       summary: "Toggle user activation",
       operationId: "AdminAPI.UserController.toggle_activation",
       security: [%{"oAuth" => ["admin:write:accounts"]}],
@@ -256,7 +256,7 @@ def toggle_activation_operation do
 
   def activate_operation do
     %Operation{
-      tags: ["Users"],
+      tags: ["User administration"],
       summary: "Activate multiple users",
       operationId: "AdminAPI.UserController.activate",
       security: [%{"oAuth" => ["admin:write:accounts"]}],
@@ -288,7 +288,7 @@ def activate_operation do
 
   def deactivate_operation do
     %Operation{
-      tags: ["Users"],
+      tags: ["User administration"],
       summary: "Deactivates multiple users",
       operationId: "AdminAPI.UserController.deactivate",
       security: [%{"oAuth" => ["admin:write:accounts"]}],
@@ -320,7 +320,7 @@ def deactivate_operation do
 
   def delete_operation do
     %Operation{
-      tags: ["Users"],
+      tags: ["User administration"],
       summary: "Removes a single or multiple users",
       operationId: "AdminAPI.UserController.delete",
       security: [%{"oAuth" => ["admin:write:accounts"]}],

From 92ab72dbbb4f56a0e0c3d0882ce29d54739437c1 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Fri, 5 Mar 2021 15:51:29 +0400
Subject: [PATCH 065/339] Update OpenApiSpex dependency

---
 lib/pleroma/web/api_spec/cast_and_validate.ex | 31 ++++++++-----------
 .../api_spec/operations/status_operation.ex   |  2 +-
 .../web/api_spec/schemas/boolean_like.ex      | 10 ++++--
 .../controllers/instance_controller.ex        |  2 +-
 .../controllers/backup_controller.ex          |  2 +-
 .../controllers/chat_controller.ex            |  2 +-
 .../controllers/user_import_controller.ex     |  2 +-
 mix.exs                                       |  4 +--
 mix.lock                                      |  2 +-
 test/support/conn_case.ex                     |  8 ++---
 10 files changed, 30 insertions(+), 35 deletions(-)

diff --git a/lib/pleroma/web/api_spec/cast_and_validate.ex b/lib/pleroma/web/api_spec/cast_and_validate.ex
index a3da856ff..d23a7dcb6 100644
--- a/lib/pleroma/web/api_spec/cast_and_validate.ex
+++ b/lib/pleroma/web/api_spec/cast_and_validate.ex
@@ -15,6 +15,7 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do
 
   @behaviour Plug
 
+  alias OpenApiSpex.Plug.PutApiSpec
   alias Plug.Conn
 
   @impl Plug
@@ -25,12 +26,10 @@ def init(opts) do
   end
 
   @impl Plug
-  def call(%{private: %{open_api_spex: private_data}} = conn, %{
-        operation_id: operation_id,
-        render_error: render_error
-      }) do
-    spec = private_data.spec
-    operation = private_data.operation_lookup[operation_id]
+
+  def call(conn, %{operation_id: operation_id, render_error: render_error}) do
+    {spec, operation_lookup} = PutApiSpec.get_spec_and_operation_lookup(conn)
+    operation = operation_lookup[operation_id]
 
     content_type =
       case Conn.get_req_header(conn, "content-type") do
@@ -43,8 +42,7 @@ def call(%{private: %{open_api_spex: private_data}} = conn, %{
           "application/json"
       end
 
-    private_data = Map.put(private_data, :operation_id, operation_id)
-    conn = Conn.put_private(conn, :open_api_spex, private_data)
+    conn = Conn.put_private(conn, :operation_id, operation_id)
 
     case cast_and_validate(spec, operation, conn, content_type, strict?()) do
       {:ok, conn} ->
@@ -64,25 +62,22 @@ def call(
           private: %{
             phoenix_controller: controller,
             phoenix_action: action,
-            open_api_spex: private_data
+            open_api_spex: %{spec_module: spec_module}
           }
         } = conn,
         opts
       ) do
+    {spec, operation_lookup} = PutApiSpec.get_spec_and_operation_lookup(conn)
+
     operation =
-      case private_data.operation_lookup[{controller, action}] do
+      case operation_lookup[{controller, action}] do
         nil ->
           operation_id = controller.open_api_operation(action).operationId
-          operation = private_data.operation_lookup[operation_id]
+          operation = operation_lookup[operation_id]
 
-          operation_lookup =
-            private_data.operation_lookup
-            |> Map.put({controller, action}, operation)
+          operation_lookup = Map.put(operation_lookup, {controller, action}, operation)
 
-          OpenApiSpex.Plug.Cache.adapter().put(
-            private_data.spec_module,
-            {private_data.spec, operation_lookup}
-          )
+          OpenApiSpex.Plug.Cache.adapter().put(spec_module, {spec, operation_lookup})
 
           operation
 
diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex
index 40edc747d..4bdb8e281 100644
--- a/lib/pleroma/web/api_spec/operations/status_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/status_operation.ex
@@ -59,7 +59,7 @@ def create_operation do
           Operation.response(
             "Status. When `scheduled_at` is present, ScheduledStatus is returned instead",
             "application/json",
-            %Schema{oneOf: [Status, ScheduledStatus]}
+            %Schema{anyOf: [Status, ScheduledStatus]}
           ),
         422 => Operation.response("Bad Request / MRF Rejection", "application/json", ApiError)
       }
diff --git a/lib/pleroma/web/api_spec/schemas/boolean_like.ex b/lib/pleroma/web/api_spec/schemas/boolean_like.ex
index eb001c5bb..778158f66 100644
--- a/lib/pleroma/web/api_spec/schemas/boolean_like.ex
+++ b/lib/pleroma/web/api_spec/schemas/boolean_like.ex
@@ -3,6 +3,7 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.ApiSpec.Schemas.BooleanLike do
+  alias OpenApiSpex.Cast
   alias OpenApiSpex.Schema
 
   require OpenApiSpex
@@ -27,10 +28,13 @@ defmodule Pleroma.Web.ApiSpec.Schemas.BooleanLike do
       %Schema{type: :boolean},
       %Schema{type: :string},
       %Schema{type: :integer}
-    ]
+    ],
+    "x-validate": __MODULE__
   })
 
-  def after_cast(value, _schmea) do
-    {:ok, Pleroma.Web.ControllerHelper.truthy_param?(value)}
+  def cast(%Cast{value: value} = context) do
+    context
+    |> Map.put(:value, Pleroma.Web.ControllerHelper.truthy_param?(value))
+    |> Cast.ok()
   end
 end
diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex
index 267d0f03b..c7a5267d4 100644
--- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.MastodonAPI.InstanceController do
   use Pleroma.Web, :controller
 
-  plug(OpenApiSpex.Plug.CastAndValidate)
+  plug(Pleroma.Web.ApiSpec.CastAndValidate)
 
   plug(
     :skip_plug,
diff --git a/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex b/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex
index 315657e9c..fc5d16771 100644
--- a/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex
@@ -10,7 +10,7 @@ defmodule Pleroma.Web.PleromaAPI.BackupController do
 
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
   plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action in [:index, :create])
-  plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
+  plug(Pleroma.Web.ApiSpec.CastAndValidate)
 
   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaBackupOperation
 
diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
index 4adc685fe..dcd54b1af 100644
--- a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
@@ -38,7 +38,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
     %{scopes: ["read:chats"]} when action in [:messages, :index, :index2, :show]
   )
 
-  plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
+  plug(Pleroma.Web.ApiSpec.CastAndValidate)
 
   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ChatOperation
 
diff --git a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex
index 6d9a11fb6..078d470d9 100644
--- a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex
@@ -15,7 +15,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do
   plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks)
   plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action == :mutes)
 
-  plug(OpenApiSpex.Plug.CastAndValidate)
+  plug(Pleroma.Web.ApiSpec.CastAndValidate)
   defdelegate open_api_operation(action), to: ApiSpec.UserImportOperation
 
   def follow(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do
diff --git a/mix.exs b/mix.exs
index ec6e92df7..7f8665ea1 100644
--- a/mix.exs
+++ b/mix.exs
@@ -195,9 +195,7 @@ defp deps do
       {:majic,
        git: "https://git.pleroma.social/pleroma/elixir-libraries/majic.git",
        ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"},
-      {:open_api_spex,
-       git: "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git",
-       ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"},
+      {:open_api_spex, "~> 3.10"},
 
       ## dev & test
       {:ex_doc, "~> 0.22", only: :dev, runtime: false},
diff --git a/mix.lock b/mix.lock
index 99be81826..61c79a7f9 100644
--- a/mix.lock
+++ b/mix.lock
@@ -82,7 +82,7 @@
   "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": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]},
+  "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": {:git, "https://github.com/uwiger/parse_trans.git", "76abb347c3c1d00fb0ccf9e4b43e22b3d2288484", [tag: "3.3.0"]},
   "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"},
diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex
index 953aa010a..deee98599 100644
--- a/test/support/conn_case.ex
+++ b/test/support/conn_case.ex
@@ -67,13 +67,11 @@ defp empty_json_response(conn) do
       end
 
       defp json_response_and_validate_schema(
-             %{
-               private: %{
-                 open_api_spex: %{operation_id: op_id, operation_lookup: lookup, spec: spec}
-               }
-             } = conn,
+             %{private: %{operation_id: op_id}} = conn,
              status
            ) do
+        {spec, lookup} = OpenApiSpex.Plug.PutApiSpec.get_spec_and_operation_lookup(conn)
+
         content_type =
           conn
           |> Plug.Conn.get_resp_header("content-type")

From e97b34f65d71b3dd11aab151fe7ce6def315635a Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 5 Mar 2021 13:18:37 -0600
Subject: [PATCH 066/339] Add simple way to decode fully qualified mediaproxy
 URLs

---
 lib/pleroma/web/media_proxy.ex | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lib/pleroma/web/media_proxy.ex b/lib/pleroma/web/media_proxy.ex
index 27f337138..d0d4bb4b3 100644
--- a/lib/pleroma/web/media_proxy.ex
+++ b/lib/pleroma/web/media_proxy.ex
@@ -121,6 +121,11 @@ def decode_url(sig, url) do
     end
   end
 
+  def decode_url(encoded) do
+    [_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/")
+    decode_url(sig, base64)
+  end
+
   defp signed_url(url) do
     :crypto.hmac(:sha, Config.get([Web.Endpoint, :secret_key_base]), url)
   end

From eaaa20e0f1ac56fee0a8a0eb6a21bc7bf11dbe48 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 5 Mar 2021 13:21:22 -0600
Subject: [PATCH 067/339] Make tests use it

---
 test/pleroma/web/media_proxy_test.exs | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/test/pleroma/web/media_proxy_test.exs b/test/pleroma/web/media_proxy_test.exs
index 7411d0a7a..b5ee6328d 100644
--- a/test/pleroma/web/media_proxy_test.exs
+++ b/test/pleroma/web/media_proxy_test.exs
@@ -11,8 +11,7 @@ defmodule Pleroma.Web.MediaProxyTest do
   alias Pleroma.Web.MediaProxy
 
   defp decode_result(encoded) do
-    [_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/")
-    {:ok, decoded} = MediaProxy.decode_url(sig, base64)
+    {:ok, decoded} = MediaProxy.decode_url(encoded)
     decoded
   end
 

From 7f8785fd9be11fbb09283c2dbd32aeb7903a4f58 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Sun, 7 Mar 2021 11:33:21 +0300
Subject: [PATCH 068/339] [#3213] Performance optimization of filtering by
 hashtags ("any" condition).

---
 lib/pleroma/pagination.ex                     |  3 ++
 lib/pleroma/web/activity_pub/activity_pub.ex  | 47 ++++++++++++++-----
 .../controllers/timeline_controller.ex        | 39 ++++++---------
 3 files changed, 53 insertions(+), 36 deletions(-)

diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex
index 0d24e1010..33e45a0eb 100644
--- a/lib/pleroma/pagination.ex
+++ b/lib/pleroma/pagination.ex
@@ -93,6 +93,7 @@ defp cast_params(params) do
       max_id: :string,
       offset: :integer,
       limit: :integer,
+      skip_extra_order: :boolean,
       skip_order: :boolean
     }
 
@@ -114,6 +115,8 @@ defp restrict(query, :max_id, %{max_id: max_id}, table_binding) do
 
   defp restrict(query, :order, %{skip_order: true}, _), do: query
 
+  defp restrict(%{order_bys: [_ | _]} = query, :order, %{skip_extra_order: true}, _), do: query
+
   defp restrict(query, :order, %{min_id: _}, table_binding) do
     order_by(
       query,
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index a4b48ec9b..230faf024 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -466,6 +466,23 @@ def fetch_latest_direct_activity_id_for_context(context, opts \\ %{}) do
     |> Repo.one()
   end
 
+  defp fetch_paginated_optimized(query, opts, pagination) do
+    # Note: tag-filtering funcs may apply "ORDER BY objects.id DESC",
+    #   and extra sorting on "activities.id DESC NULLS LAST" would worse the query plan
+    opts = Map.put(opts, :skip_extra_order, true)
+
+    Pagination.fetch_paginated(query, opts, pagination)
+  end
+
+  def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do
+    list_memberships = Pleroma.List.memberships(opts[:user])
+
+    fetch_activities_query(recipients ++ list_memberships, opts)
+    |> fetch_paginated_optimized(opts, pagination)
+    |> Enum.reverse()
+    |> maybe_update_cc(list_memberships, opts[:user])
+  end
+
   @spec fetch_public_or_unlisted_activities(map(), Pagination.type()) :: [Activity.t()]
   def fetch_public_or_unlisted_activities(opts \\ %{}, pagination \\ :keyset) do
     opts = Map.delete(opts, :user)
@@ -473,7 +490,7 @@ def fetch_public_or_unlisted_activities(opts \\ %{}, pagination \\ :keyset) do
     [Constants.as_public()]
     |> fetch_activities_query(opts)
     |> restrict_unlisted(opts)
-    |> Pagination.fetch_paginated(opts, pagination)
+    |> fetch_paginated_optimized(opts, pagination)
   end
 
   @spec fetch_public_activities(map(), Pagination.type()) :: [Activity.t()]
@@ -751,6 +768,7 @@ defp object_ids_query_for_tags(tags) do
     |> join(:inner, [hto], ht in Pleroma.Hashtag, on: hto.hashtag_id == ht.id)
     |> where([hto, ht], ht.name in ^tags)
     |> select([hto], hto.object_id)
+    |> distinct([hto], true)
   end
 
   defp restrict_hashtag_all(_query, %{tag_all: _tag, skip_preload: true}) do
@@ -789,9 +807,18 @@ defp restrict_hashtag_any(_query, %{tag: _tag, skip_preload: true}) do
   end
 
   defp restrict_hashtag_any(query, %{tag: [_ | _] = tags}) do
+    hashtag_ids =
+      from(ht in Hashtag, where: ht.name in ^tags, select: ht.id)
+      |> Repo.all()
+
+    # Note: NO extra ordering should be done on "activities.id desc nulls last" for optimal plan
     from(
       [_activity, object] in query,
-      where: object.id in subquery(object_ids_query_for_tags(tags))
+      join: hto in "hashtags_objects",
+      on: hto.object_id == object.id,
+      where: hto.hashtag_id in ^hashtag_ids,
+      distinct: [desc: object.id],
+      order_by: [desc: object.id]
     )
   end
 
@@ -1188,7 +1215,12 @@ defp normalize_fetch_activities_query_opts(opts) do
           Map.put(opts, key, Hashtag.normalize_name(value))
 
         value when is_list(value) ->
-          Map.put(opts, key, Enum.map(value, &Hashtag.normalize_name/1))
+          normalized_value =
+            value
+            |> Enum.map(&Hashtag.normalize_name/1)
+            |> Enum.uniq()
+
+          Map.put(opts, key, normalized_value)
 
         _ ->
           opts
@@ -1275,15 +1307,6 @@ def fetch_activities_query(recipients, opts \\ %{}) do
     end
   end
 
-  def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do
-    list_memberships = Pleroma.List.memberships(opts[:user])
-
-    fetch_activities_query(recipients ++ list_memberships, opts)
-    |> Pagination.fetch_paginated(opts, pagination)
-    |> Enum.reverse()
-    |> maybe_update_cc(list_memberships, opts[:user])
-  end
-
   @doc """
   Fetch favorites activities of user with order by sort adds to favorites
   """
diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
index 87effa00b..c611958be 100644
--- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
@@ -133,34 +133,25 @@ defp fail_on_bad_auth(conn) do
   end
 
   defp hashtag_fetching(params, user, local_only) do
-    tags =
+    # Note: not sanitizing tag options at this stage (may be mix-cased, have duplicates etc.)
+    tags_any =
       [params[:tag], params[:any]]
       |> List.flatten()
-      |> Enum.reject(&is_nil/1)
-      |> Enum.map(&String.downcase/1)
-      |> Enum.uniq()
+      |> Enum.filter(& &1)
 
-    tag_all =
-      params
-      |> Map.get(:all, [])
-      |> Enum.map(&String.downcase/1)
+    tag_all = Map.get(params, :all, [])
+    tag_reject = Map.get(params, :none, [])
 
-    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)
-      |> ActivityPub.fetch_public_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_any)
+    |> Map.put(:tag_all, tag_all)
+    |> Map.put(:tag_reject, tag_reject)
+    |> ActivityPub.fetch_public_activities()
   end
 
   # GET /api/v1/timelines/tag/:tag

From 8feeb672c8ec0b916d94fb516ea05b464342e19b Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 10 Mar 2021 13:03:14 -0600
Subject: [PATCH 069/339] Ensure we fetch deps during spec-build stage

---
 .gitlab-ci.yml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c7e8291d8..68644660c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -40,6 +40,7 @@ spec-build:
     paths:
     - spec.json
   script:
+  - mix deps.get
   - mix pleroma.openapi_spec spec.json
 
 benchmark:
@@ -393,4 +394,4 @@ docker-adhoc:
   tags:
     - dind
   only:
-    - /^build-docker/.*$/@pleroma/pleroma
\ No newline at end of file
+    - /^build-docker/.*$/@pleroma/pleroma

From 502d166b7e44e36a94974df4770de6c6a239ad75 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 10 Mar 2021 16:19:18 -0600
Subject: [PATCH 070/339] See if switching to same image as releases fixes the
 build

---
 .gitlab-ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 68644660c..ea6611947 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: elixir:1.9.4
+image: elixir:1.10.3
 
 variables: &global_variables
   POSTGRES_DB: pleroma_test

From fa75f11ca138e69952cf1a1483a8b848b3b0d1b9 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 10 Mar 2021 16:37:24 -0600
Subject: [PATCH 071/339] Revert "See if switching to same image as releases
 fixes the build"

This reverts commit 502d166b7e44e36a94974df4770de6c6a239ad75.
---
 .gitlab-ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ea6611947..68644660c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: elixir:1.10.3
+image: elixir:1.9.4
 
 variables: &global_variables
   POSTGRES_DB: pleroma_test

From 8246db2a968943a0cab615b8b5c1439aa4cb2547 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Sat, 6 Mar 2021 12:02:32 -0600
Subject: [PATCH 072/339] Workaround for URI.merge/2 bug
 https://github.com/elixir-lang/elixir/issues/10771

If we avoid URI.merge unless we know we need it we reduce the edge cases we could encounter.
The site would need to both have "//" in the %URI{:path} and the image needs to be a relative URL.
---
 lib/pleroma/web/mastodon_api/views/status_view.ex | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index f3f54e03d..cf8037abb 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -380,9 +380,15 @@ def render("card.json", %{rich_media: rich_media, page_url: page_url}) do
     page_url = page_url_data |> to_string
 
     image_url =
-      if is_binary(rich_media["image"]) do
-        URI.merge(page_url_data, URI.parse(rich_media["image"]))
-        |> to_string
+      cond do
+        !is_binary(rich_media["image"]) ->
+          nil
+
+        String.starts_with?(rich_media["image"], "http") ->
+          rich_media["image"]
+
+        true ->
+          URI.merge(page_url_data, URI.parse(rich_media["image"])) |> to_string
       end
 
     %{

From 029ff6538972b59c6259dd7345ad9c4465fb3f73 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Thu, 11 Mar 2021 09:20:29 -0600
Subject: [PATCH 073/339] Leverage function pattern matching instead

---
 .../web/mastodon_api/views/status_view.ex     | 36 +++++++++++++------
 1 file changed, 26 insertions(+), 10 deletions(-)

diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index cf8037abb..581b4e952 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -379,18 +379,15 @@ def render("card.json", %{rich_media: rich_media, page_url: page_url}) do
 
     page_url = page_url_data |> to_string
 
-    image_url =
-      cond do
-        !is_binary(rich_media["image"]) ->
-          nil
-
-        String.starts_with?(rich_media["image"], "http") ->
-          rich_media["image"]
-
-        true ->
-          URI.merge(page_url_data, URI.parse(rich_media["image"])) |> to_string
+    image_url_data =
+      if is_binary(rich_media["image"]) do
+        URI.parse(rich_media["image"])
+      else
+        nil
       end
 
+    image_url = get_image_url(image_url_data, page_url_data)
+
     %{
       type: "link",
       provider_name: page_url_data.host,
@@ -546,4 +543,23 @@ defp build_application(%{"type" => _type, "name" => name, "url" => url}),
     do: %{name: name, website: url}
 
   defp build_application(_), do: nil
+
+  # Workaround for Elixir issue #10771
+  # Avoid applying URI.merge unless necessary
+  # TODO: revert to always attempting URI.merge(image_url_data, page_url_data)
+  # when Elixir 1.12 is the minimum supported version
+  @spec get_image_url(struct() | nil, struct()) :: String.t() | nil
+  defp get_image_url(
+         %URI{scheme: image_scheme, host: image_host} = image_url_data,
+         %URI{} = _page_url_data
+       )
+       when not is_nil(image_scheme) and not is_nil(image_host) do
+    image_url_data |> to_string
+  end
+
+  defp get_image_url(%URI{} = image_url_data, %URI{} = page_url_data) do
+    URI.merge(page_url_data, image_url_data) |> to_string
+  end
+
+  defp get_image_url(_, _), do: nil
 end

From 884584772bd7ff52825bbb3bd38ca7c6190c084a Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Thu, 11 Mar 2021 09:40:40 -0600
Subject: [PATCH 074/339] Execute mix deps.get earlier and avoid duplicate
 invocations if possible

---
 .gitlab-ci.yml | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 68644660c..2bc571971 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -25,13 +25,13 @@ before_script:
   - apt-get update && apt-get install -y cmake
   - mix local.hex --force
   - mix local.rebar --force
+  - mix deps.get
   - apt-get -qq update
   - apt-get install -y libmagic-dev
 
 build:
   stage: build
   script:
-  - mix deps.get
   - mix compile --force
 
 spec-build:
@@ -40,7 +40,6 @@ spec-build:
     paths:
     - spec.json
   script:
-  - mix deps.get
   - mix pleroma.openapi_spec spec.json
 
 benchmark:
@@ -53,7 +52,6 @@ benchmark:
     alias: postgres
     command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
   script:
-    - mix deps.get
     - mix ecto.create
     - mix ecto.migrate
     - mix pleroma.load_testing
@@ -71,7 +69,6 @@ unit-testing:
     command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
   script:
     - apt-get update && apt-get install -y libimage-exiftool-perl ffmpeg
-    - mix deps.get
     - mix ecto.create
     - mix ecto.migrate
     - mix coveralls --preload-modules
@@ -105,7 +102,6 @@ unit-testing-rum:
     RUM_ENABLED: "true"
   script:
     - apt-get update && apt-get install -y libimage-exiftool-perl ffmpeg
-    - mix deps.get
     - mix ecto.create
     - mix ecto.migrate
     - "mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
@@ -121,7 +117,6 @@ analysis:
   stage: test
   cache: *testing_cache_policy
   script:
-    - mix deps.get
     - mix credo --strict --only=warnings,todo,fixme,consistency,readability
 
 docs-deploy:

From 3edf45021eb6c3fba06bc083b346f7db54cd073f Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Fri, 12 Mar 2021 12:18:11 +0300
Subject: [PATCH 075/339] [#3213] Background migration infrastructure
 refactoring. Extracted BaseMigrator and BaseMigratorState.

---
 lib/pleroma/application.ex                    |  11 +-
 .../migrators/hashtags_table_migrator.ex      | 265 ++++--------------
 .../hashtags_table_migrator/state.ex          | 104 -------
 .../migrators/support/base_migrator.ex        | 210 ++++++++++++++
 .../migrators/support/base_migrator_state.ex  | 116 ++++++++
 5 files changed, 385 insertions(+), 321 deletions(-)
 delete mode 100644 lib/pleroma/migrators/hashtags_table_migrator/state.ex
 create mode 100644 lib/pleroma/migrators/support/base_migrator.ex
 create mode 100644 lib/pleroma/migrators/support/base_migrator_state.ex

diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 2ff7562e2..06d399b2e 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -103,10 +103,7 @@ def start(_type, _args) do
         task_children(@mix_env) ++
         dont_run_in_test(@mix_env) ++
         chat_child(chat_enabled?()) ++
-        [
-          Pleroma.Migrators.HashtagsTableMigrator,
-          Pleroma.Gopher.Server
-        ]
+        [Pleroma.Gopher.Server]
 
     # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
     # for other strategies and supported options
@@ -231,6 +228,12 @@ defp dont_run_in_test(_) do
          keys: :duplicate,
          partitions: System.schedulers_online()
        ]}
+    ] ++ background_migrators()
+  end
+
+  defp background_migrators do
+    [
+      Pleroma.Migrators.HashtagsTableMigrator
     ]
   end
 
diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
index 6123c88e0..b84058e11 100644
--- a/lib/pleroma/migrators/hashtags_table_migrator.ex
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -3,88 +3,27 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Migrators.HashtagsTableMigrator do
-  use GenServer
+  defmodule State do
+    use Pleroma.Migrators.Support.BaseMigratorState
 
-  require Logger
+    @impl Pleroma.Migrators.Support.BaseMigratorState
+    defdelegate data_migration(), to: Pleroma.DataMigration, as: :populate_hashtags_table
+  end
 
-  import Ecto.Query
+  use Pleroma.Migrators.Support.BaseMigrator
 
-  alias __MODULE__.State
-  alias Pleroma.Config
   alias Pleroma.Hashtag
+  alias Pleroma.Migrators.Support.BaseMigrator
   alias Pleroma.Object
-  alias Pleroma.Repo
 
-  defdelegate data_migration(), to: Pleroma.DataMigration, as: :populate_hashtags_table
-  defdelegate data_migration_id(), to: State
+  @impl BaseMigrator
+  def feature_config_path, do: [:features, :improved_hashtag_timeline]
 
-  defdelegate state(), to: State
-  defdelegate persist_state(), to: State, as: :persist_to_db
-  defdelegate get_stat(key, value \\ nil), to: State, as: :get_data_key
-  defdelegate put_stat(key, value), to: State, as: :put_data_key
-  defdelegate increment_stat(key, increment), to: State, as: :increment_data_key
-
-  @feature_config_path [:features, :improved_hashtag_timeline]
-  @reg_name {:global, __MODULE__}
-
-  def whereis, do: GenServer.whereis(@reg_name)
-
-  def feature_state, do: Config.get(@feature_config_path)
-
-  def start_link(_) do
-    case whereis() do
-      nil ->
-        GenServer.start_link(__MODULE__, nil, name: @reg_name)
-
-      pid ->
-        {:ok, pid}
-    end
-  end
-
-  @impl true
-  def init(_) do
-    {:ok, nil, {:continue, :init_state}}
-  end
-
-  @impl true
-  def handle_continue(:init_state, _state) do
-    {:ok, _} = State.start_link(nil)
-
-    data_migration = data_migration()
-    manual_migrations = Config.get([:instance, :manual_data_migrations], [])
-
-    cond do
-      Config.get(:env) == :test ->
-        update_status(:noop)
-
-      is_nil(data_migration) ->
-        message = "Data migration does not exist."
-        update_status(:failed, message)
-        Logger.error("#{__MODULE__}: #{message}")
-
-      data_migration.state == :manual or data_migration.name in manual_migrations ->
-        message = "Data migration is in manual execution or manual fix mode."
-        update_status(:manual, message)
-        Logger.warn("#{__MODULE__}: #{message}")
-
-      data_migration.state == :complete ->
-        on_complete(data_migration)
-
-      true ->
-        send(self(), :migrate_hashtags)
-    end
-
-    {:noreply, nil}
-  end
-
-  @impl true
-  def handle_info(:migrate_hashtags, state) do
-    State.reinit()
-
-    update_status(:running)
-    put_stat(:iteration_processed_count, 0)
-    put_stat(:started_at, NaiveDateTime.utc_now())
+  @impl BaseMigrator
+  def fault_rate_allowance, do: Config.get([:populate_hashtags_table, :fault_rate_allowance], 0)
 
+  @impl BaseMigrator
+  def perform do
     data_migration_id = data_migration_id()
     max_processed_id = get_stat(:max_processed_id, 0)
 
@@ -103,7 +42,7 @@ def handle_info(:migrate_hashtags, state) do
         |> Enum.filter(&(elem(&1, 0) == :error))
         |> Enum.map(&elem(&1, 1))
 
-      # Count of objects with hashtags (`{:noop, id}` is returned for objects having other AS2 tags)
+      # Count of objects with hashtags: `{:noop, id}` is returned for objects having other AS2 tags
       chunk_affected_count =
         results
         |> Enum.filter(&(elem(&1, 0) == :ok))
@@ -140,84 +79,10 @@ def handle_info(:migrate_hashtags, state) do
       Process.sleep(sleep_interval)
     end)
     |> Stream.run()
-
-    fault_rate = fault_rate()
-    put_stat(:fault_rate, fault_rate)
-    fault_rate_allowance = Config.get([:populate_hashtags_table, :fault_rate_allowance], 0)
-
-    cond do
-      fault_rate == 0 ->
-        set_complete()
-
-      is_float(fault_rate) and fault_rate <= fault_rate_allowance ->
-        message = """
-        Done with fault rate of #{fault_rate} which doesn't exceed #{fault_rate_allowance}.
-        Putting data migration to manual fix mode. Check `retry_failed/0`.
-        """
-
-        Logger.warn("#{__MODULE__}: #{message}")
-        update_status(:manual, message)
-        on_complete(data_migration())
-
-      true ->
-        message = "Too many failures. Check data_migration_failed_ids records / `retry_failed/0`."
-        Logger.error("#{__MODULE__}: #{message}")
-        update_status(:failed, message)
-    end
-
-    persist_state()
-    {:noreply, state}
   end
 
-  def fault_rate do
-    with failures_count when is_integer(failures_count) <- failures_count() do
-      failures_count / Enum.max([get_stat(:affected_count, 0), 1])
-    else
-      _ -> :error
-    end
-  end
-
-  defp records_per_second do
-    get_stat(:iteration_processed_count, 0) / Enum.max([running_time(), 1])
-  end
-
-  defp running_time do
-    NaiveDateTime.diff(NaiveDateTime.utc_now(), get_stat(:started_at, NaiveDateTime.utc_now()))
-  end
-
-  @hashtags_objects_cleanup_query """
-  DELETE FROM hashtags_objects WHERE object_id IN
-    (SELECT DISTINCT objects.id FROM objects
-      JOIN hashtags_objects ON hashtags_objects.object_id = objects.id LEFT JOIN activities
-        ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') =
-          (objects.data->>'id')
-        AND activities.data->>'type' = 'Create'
-      WHERE activities.id IS NULL);
-  """
-
-  @hashtags_cleanup_query """
-  DELETE FROM hashtags WHERE id IN
-    (SELECT hashtags.id FROM hashtags
-      LEFT OUTER JOIN hashtags_objects
-        ON hashtags_objects.hashtag_id = hashtags.id
-      WHERE hashtags_objects.hashtag_id IS NULL);
-  """
-
-  @doc """
-  Deletes `hashtags_objects` for legacy objects not asoociated with Create activity.
-  Also deletes unreferenced `hashtags` records (might occur after deletion of `hashtags_objects`).
-  """
-  def delete_non_create_activities_hashtags do
-    {:ok, %{num_rows: hashtags_objects_count}} =
-      Repo.query(@hashtags_objects_cleanup_query, [], timeout: :infinity)
-
-    {:ok, %{num_rows: hashtags_count}} =
-      Repo.query(@hashtags_cleanup_query, [], timeout: :infinity)
-
-    {:ok, hashtags_objects_count, hashtags_count}
-  end
-
-  defp query do
+  @impl BaseMigrator
+  def query do
     # Note: most objects have Mention-type AS2 tags and no hashtags (but we can't filter them out)
     # Note: not checking activity type, expecting remove_non_create_objects_hashtags/_ to clean up
     from(
@@ -276,54 +141,7 @@ defp transfer_object_hashtags(object, hashtags) do
     end)
   end
 
-  @doc "Approximate count for current iteration (including processed records count)"
-  def count(force \\ false, timeout \\ :infinity) do
-    stored_count = get_stat(:count)
-
-    if stored_count && !force do
-      stored_count
-    else
-      processed_count = get_stat(:processed_count, 0)
-      max_processed_id = get_stat(:max_processed_id, 0)
-      query = where(query(), [object], object.id > ^max_processed_id)
-
-      count = Repo.aggregate(query, :count, :id, timeout: timeout) + processed_count
-      put_stat(:count, count)
-      persist_state()
-
-      count
-    end
-  end
-
-  defp on_complete(data_migration) do
-    if data_migration.feature_lock || feature_state() == :disabled do
-      Logger.warn("#{__MODULE__}: migration complete but feature is locked; consider enabling.")
-      :noop
-    else
-      Config.put(@feature_config_path, :enabled)
-      :ok
-    end
-  end
-
-  def failed_objects_query do
-    from(o in Object)
-    |> join(:inner, [o], dmf in fragment("SELECT * FROM data_migration_failed_ids"),
-      on: dmf.record_id == o.id
-    )
-    |> where([_o, dmf], dmf.data_migration_id == ^data_migration_id())
-    |> order_by([o], asc: o.id)
-  end
-
-  def failures_count do
-    with {:ok, %{rows: [[count]]}} <-
-           Repo.query(
-             "SELECT COUNT(record_id) FROM data_migration_failed_ids WHERE data_migration_id = $1;",
-             [data_migration_id()]
-           ) do
-      count
-    end
-  end
-
+  @impl BaseMigrator
   def retry_failed do
     data_migration_id = data_migration_id()
 
@@ -347,23 +165,44 @@ def retry_failed do
     force_continue()
   end
 
-  def force_continue do
-    send(whereis(), :migrate_hashtags)
+  defp failed_objects_query do
+    from(o in Object)
+    |> join(:inner, [o], dmf in fragment("SELECT * FROM data_migration_failed_ids"),
+      on: dmf.record_id == o.id
+    )
+    |> where([_o, dmf], dmf.data_migration_id == ^data_migration_id())
+    |> order_by([o], asc: o.id)
   end
 
-  def force_restart do
-    :ok = State.reset()
-    force_continue()
-  end
+  @doc """
+  Service func to delete `hashtags_objects` for legacy objects not associated with Create activity.
+  Also deletes unreferenced `hashtags` records (might occur after deletion of `hashtags_objects`).
+  """
+  def delete_non_create_activities_hashtags do
+    hashtags_objects_cleanup_query = """
+    DELETE FROM hashtags_objects WHERE object_id IN
+      (SELECT DISTINCT objects.id FROM objects
+        JOIN hashtags_objects ON hashtags_objects.object_id = objects.id LEFT JOIN activities
+          ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') =
+            (objects.data->>'id')
+          AND activities.data->>'type' = 'Create'
+        WHERE activities.id IS NULL);
+    """
 
-  def set_complete do
-    update_status(:complete)
-    persist_state()
-    on_complete(data_migration())
-  end
+    hashtags_cleanup_query = """
+    DELETE FROM hashtags WHERE id IN
+      (SELECT hashtags.id FROM hashtags
+        LEFT OUTER JOIN hashtags_objects
+          ON hashtags_objects.hashtag_id = hashtags.id
+        WHERE hashtags_objects.hashtag_id IS NULL);
+    """
 
-  defp update_status(status, message \\ nil) do
-    put_stat(:state, status)
-    put_stat(:message, message)
+    {:ok, %{num_rows: hashtags_objects_count}} =
+      Repo.query(hashtags_objects_cleanup_query, [], timeout: :infinity)
+
+    {:ok, %{num_rows: hashtags_count}} =
+      Repo.query(hashtags_cleanup_query, [], timeout: :infinity)
+
+    {:ok, hashtags_objects_count, hashtags_count}
   end
 end
diff --git a/lib/pleroma/migrators/hashtags_table_migrator/state.ex b/lib/pleroma/migrators/hashtags_table_migrator/state.ex
deleted file mode 100644
index ee0009b2e..000000000
--- a/lib/pleroma/migrators/hashtags_table_migrator/state.ex
+++ /dev/null
@@ -1,104 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Migrators.HashtagsTableMigrator.State do
-  use Agent
-
-  alias Pleroma.DataMigration
-
-  defdelegate data_migration(), to: Pleroma.Migrators.HashtagsTableMigrator
-
-  @reg_name {:global, __MODULE__}
-
-  def start_link(_) do
-    Agent.start_link(fn -> load_state_from_db() end, name: @reg_name)
-  end
-
-  defp load_state_from_db do
-    data_migration = data_migration()
-
-    data =
-      if data_migration do
-        Map.new(data_migration.data, fn {k, v} -> {String.to_atom(k), v} end)
-      else
-        %{}
-      end
-
-    %{
-      data_migration_id: data_migration && data_migration.id,
-      data: data
-    }
-  end
-
-  def persist_to_db do
-    %{data_migration_id: data_migration_id, data: data} = state()
-
-    if data_migration_id do
-      DataMigration.update_one_by_id(data_migration_id, data: data)
-    else
-      {:error, :nil_data_migration_id}
-    end
-  end
-
-  def reset do
-    %{data_migration_id: data_migration_id} = state()
-
-    with false <- is_nil(data_migration_id),
-         :ok <-
-           DataMigration.update_one_by_id(data_migration_id,
-             state: :pending,
-             data: %{}
-           ) do
-      reinit()
-    else
-      true -> {:error, :nil_data_migration_id}
-      e -> e
-    end
-  end
-
-  def reinit do
-    Agent.update(@reg_name, fn _state -> load_state_from_db() end)
-  end
-
-  def state do
-    Agent.get(@reg_name, & &1)
-  end
-
-  def get_data_key(key, default \\ nil) do
-    get_in(state(), [:data, key]) || default
-  end
-
-  def put_data_key(key, value) do
-    _ = persist_non_data_change(key, value)
-
-    Agent.update(@reg_name, fn state ->
-      put_in(state, [:data, key], value)
-    end)
-  end
-
-  def increment_data_key(key, increment \\ 1) do
-    Agent.update(@reg_name, fn state ->
-      initial_value = get_in(state, [:data, key]) || 0
-      updated_value = initial_value + increment
-      put_in(state, [:data, key], updated_value)
-    end)
-  end
-
-  defp persist_non_data_change(:state, value) do
-    with true <- get_data_key(:state) != value,
-         true <- value in Pleroma.DataMigration.State.__valid_values__(),
-         %{data_migration_id: data_migration_id} when not is_nil(data_migration_id) <- state() do
-      DataMigration.update_one_by_id(data_migration_id, state: value)
-    else
-      false -> :ok
-      _ -> {:error, :nil_data_migration_id}
-    end
-  end
-
-  defp persist_non_data_change(_, _) do
-    nil
-  end
-
-  def data_migration_id, do: Map.get(state(), :data_migration_id)
-end
diff --git a/lib/pleroma/migrators/support/base_migrator.ex b/lib/pleroma/migrators/support/base_migrator.ex
new file mode 100644
index 000000000..1f8a5402b
--- /dev/null
+++ b/lib/pleroma/migrators/support/base_migrator.ex
@@ -0,0 +1,210 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Migrators.Support.BaseMigrator do
+  @moduledoc """
+  Base background migrator functionality.
+  """
+
+  @callback perform() :: any()
+  @callback retry_failed() :: any()
+  @callback feature_config_path() :: list(atom())
+  @callback query() :: Ecto.Query.t()
+  @callback fault_rate_allowance() :: integer() | float()
+
+  defmacro __using__(_opts) do
+    quote do
+      use GenServer
+
+      require Logger
+
+      import Ecto.Query
+
+      alias __MODULE__.State
+      alias Pleroma.Config
+      alias Pleroma.Repo
+
+      @behaviour Pleroma.Migrators.Support.BaseMigrator
+
+      defdelegate data_migration(), to: State
+      defdelegate data_migration_id(), to: State
+      defdelegate state(), to: State
+      defdelegate persist_state(), to: State, as: :persist_to_db
+      defdelegate get_stat(key, value \\ nil), to: State, as: :get_data_key
+      defdelegate put_stat(key, value), to: State, as: :put_data_key
+      defdelegate increment_stat(key, increment), to: State, as: :increment_data_key
+
+      @reg_name {:global, __MODULE__}
+
+      def whereis, do: GenServer.whereis(@reg_name)
+
+      def start_link(_) do
+        case whereis() do
+          nil ->
+            GenServer.start_link(__MODULE__, nil, name: @reg_name)
+
+          pid ->
+            {:ok, pid}
+        end
+      end
+
+      @impl true
+      def init(_) do
+        {:ok, nil, {:continue, :init_state}}
+      end
+
+      @impl true
+      def handle_continue(:init_state, _state) do
+        {:ok, _} = State.start_link(nil)
+
+        data_migration = data_migration()
+        manual_migrations = Config.get([:instance, :manual_data_migrations], [])
+
+        cond do
+          Config.get(:env) == :test ->
+            update_status(:noop)
+
+          is_nil(data_migration) ->
+            message = "Data migration does not exist."
+            update_status(:failed, message)
+            Logger.error("#{__MODULE__}: #{message}")
+
+          data_migration.state == :manual or data_migration.name in manual_migrations ->
+            message = "Data migration is in manual execution or manual fix mode."
+            update_status(:manual, message)
+            Logger.warn("#{__MODULE__}: #{message}")
+
+          data_migration.state == :complete ->
+            on_complete(data_migration)
+
+          true ->
+            send(self(), :perform)
+        end
+
+        {:noreply, nil}
+      end
+
+      @impl true
+      def handle_info(:perform, state) do
+        State.reinit()
+
+        update_status(:running)
+        put_stat(:iteration_processed_count, 0)
+        put_stat(:started_at, NaiveDateTime.utc_now())
+
+        perform()
+
+        fault_rate = fault_rate()
+        put_stat(:fault_rate, fault_rate)
+        fault_rate_allowance = fault_rate_allowance()
+
+        cond do
+          fault_rate == 0 ->
+            set_complete()
+
+          is_float(fault_rate) and fault_rate <= fault_rate_allowance ->
+            message = """
+            Done with fault rate of #{fault_rate} which doesn't exceed #{fault_rate_allowance}.
+            Putting data migration to manual fix mode. Try running `#{__MODULE__}.retry_failed/0`.
+            """
+
+            Logger.warn("#{__MODULE__}: #{message}")
+            update_status(:manual, message)
+            on_complete(data_migration())
+
+          true ->
+            message = "Too many failures. Try running `#{__MODULE__}.retry_failed/0`."
+            Logger.error("#{__MODULE__}: #{message}")
+            update_status(:failed, message)
+        end
+
+        persist_state()
+        {:noreply, state}
+      end
+
+      defp on_complete(data_migration) do
+        if data_migration.feature_lock || feature_state() == :disabled do
+          Logger.warn(
+            "#{__MODULE__}: migration complete but feature is locked; consider enabling."
+          )
+
+          :noop
+        else
+          Config.put(feature_config_path(), :enabled)
+          :ok
+        end
+      end
+
+      @doc "Approximate count for current iteration (including processed records count)"
+      def count(force \\ false, timeout \\ :infinity) do
+        stored_count = get_stat(:count)
+
+        if stored_count && !force do
+          stored_count
+        else
+          processed_count = get_stat(:processed_count, 0)
+          max_processed_id = get_stat(:max_processed_id, 0)
+          query = where(query(), [entity], entity.id > ^max_processed_id)
+
+          count = Repo.aggregate(query, :count, :id, timeout: timeout) + processed_count
+          put_stat(:count, count)
+          persist_state()
+
+          count
+        end
+      end
+
+      def failures_count do
+        with {:ok, %{rows: [[count]]}} <-
+               Repo.query(
+                 "SELECT COUNT(record_id) FROM data_migration_failed_ids WHERE data_migration_id = $1;",
+                 [data_migration_id()]
+               ) do
+          count
+        end
+      end
+
+      def feature_state, do: Config.get(feature_config_path())
+
+      def force_continue do
+        send(whereis(), :perform)
+      end
+
+      def force_restart do
+        :ok = State.reset()
+        force_continue()
+      end
+
+      def set_complete do
+        update_status(:complete)
+        persist_state()
+        on_complete(data_migration())
+      end
+
+      defp update_status(status, message \\ nil) do
+        put_stat(:state, status)
+        put_stat(:message, message)
+      end
+
+      defp fault_rate do
+        with failures_count when is_integer(failures_count) <- failures_count() do
+          failures_count / Enum.max([get_stat(:affected_count, 0), 1])
+        else
+          _ -> :error
+        end
+      end
+
+      defp records_per_second do
+        get_stat(:iteration_processed_count, 0) / Enum.max([running_time(), 1])
+      end
+
+      defp running_time do
+        NaiveDateTime.diff(
+          NaiveDateTime.utc_now(),
+          get_stat(:started_at, NaiveDateTime.utc_now())
+        )
+      end
+    end
+  end
+end
diff --git a/lib/pleroma/migrators/support/base_migrator_state.ex b/lib/pleroma/migrators/support/base_migrator_state.ex
new file mode 100644
index 000000000..69724ae79
--- /dev/null
+++ b/lib/pleroma/migrators/support/base_migrator_state.ex
@@ -0,0 +1,116 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Migrators.Support.BaseMigratorState do
+  @moduledoc """
+  Base background migrator state functionality.
+  """
+
+  @callback data_migration() :: Pleroma.DataMigration.t()
+
+  defmacro __using__(_opts) do
+    quote do
+      use Agent
+
+      alias Pleroma.DataMigration
+
+      @behaviour Pleroma.Migrators.Support.BaseMigratorState
+      @reg_name {:global, __MODULE__}
+
+      def start_link(_) do
+        Agent.start_link(fn -> load_state_from_db() end, name: @reg_name)
+      end
+
+      def data_migration, do: raise("data_migration/0 is not implemented")
+      defoverridable data_migration: 0
+
+      defp load_state_from_db do
+        data_migration = data_migration()
+
+        data =
+          if data_migration do
+            Map.new(data_migration.data, fn {k, v} -> {String.to_atom(k), v} end)
+          else
+            %{}
+          end
+
+        %{
+          data_migration_id: data_migration && data_migration.id,
+          data: data
+        }
+      end
+
+      def persist_to_db do
+        %{data_migration_id: data_migration_id, data: data} = state()
+
+        if data_migration_id do
+          DataMigration.update_one_by_id(data_migration_id, data: data)
+        else
+          {:error, :nil_data_migration_id}
+        end
+      end
+
+      def reset do
+        %{data_migration_id: data_migration_id} = state()
+
+        with false <- is_nil(data_migration_id),
+             :ok <-
+               DataMigration.update_one_by_id(data_migration_id,
+                 state: :pending,
+                 data: %{}
+               ) do
+          reinit()
+        else
+          true -> {:error, :nil_data_migration_id}
+          e -> e
+        end
+      end
+
+      def reinit do
+        Agent.update(@reg_name, fn _state -> load_state_from_db() end)
+      end
+
+      def state do
+        Agent.get(@reg_name, & &1)
+      end
+
+      def get_data_key(key, default \\ nil) do
+        get_in(state(), [:data, key]) || default
+      end
+
+      def put_data_key(key, value) do
+        _ = persist_non_data_change(key, value)
+
+        Agent.update(@reg_name, fn state ->
+          put_in(state, [:data, key], value)
+        end)
+      end
+
+      def increment_data_key(key, increment \\ 1) do
+        Agent.update(@reg_name, fn state ->
+          initial_value = get_in(state, [:data, key]) || 0
+          updated_value = initial_value + increment
+          put_in(state, [:data, key], updated_value)
+        end)
+      end
+
+      defp persist_non_data_change(:state, value) do
+        with true <- get_data_key(:state) != value,
+             true <- value in Pleroma.DataMigration.State.__valid_values__(),
+             %{data_migration_id: data_migration_id} when not is_nil(data_migration_id) <- state() do
+          DataMigration.update_one_by_id(data_migration_id, state: value)
+        else
+          false -> :ok
+          _ -> {:error, :nil_data_migration_id}
+        end
+      end
+
+      defp persist_non_data_change(_, _) do
+        nil
+      end
+
+      def data_migration_id, do: Map.get(state(), :data_migration_id)
+    end
+  end
+end

From cb734566093f406fc3db12de2408fc166486f417 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov <ivantashkinov@gmail.com>
Date: Fri, 12 Mar 2021 12:25:18 +0300
Subject: [PATCH 076/339] [#3213] Code formatting fix.

---
 lib/pleroma/migrators/support/base_migrator_state.ex | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/migrators/support/base_migrator_state.ex b/lib/pleroma/migrators/support/base_migrator_state.ex
index 69724ae79..b698587f2 100644
--- a/lib/pleroma/migrators/support/base_migrator_state.ex
+++ b/lib/pleroma/migrators/support/base_migrator_state.ex
@@ -98,7 +98,8 @@ def increment_data_key(key, increment \\ 1) do
       defp persist_non_data_change(:state, value) do
         with true <- get_data_key(:state) != value,
              true <- value in Pleroma.DataMigration.State.__valid_values__(),
-             %{data_migration_id: data_migration_id} when not is_nil(data_migration_id) <- state() do
+             %{data_migration_id: data_migration_id} when not is_nil(data_migration_id) <-
+               state() do
           DataMigration.update_one_by_id(data_migration_id, state: value)
         else
           false -> :ok

From 2408363e2a5115e4856957ba46231211eec6b338 Mon Sep 17 00:00:00 2001
From: Ben Is <spambenis@fastwebnet.it>
Date: Thu, 11 Mar 2021 13:51:22 +0000
Subject: [PATCH 077/339] Translated using Weblate (Italian)

Currently translated at 100.0% (106 of 106 strings)

Translation: Pleroma/Pleroma backend
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma/it/
---
 priv/gettext/it/LC_MESSAGES/errors.po | 44 +++++++++++++--------------
 1 file changed, 21 insertions(+), 23 deletions(-)

diff --git a/priv/gettext/it/LC_MESSAGES/errors.po b/priv/gettext/it/LC_MESSAGES/errors.po
index cd0cd6c65..6a6ec058e 100644
--- a/priv/gettext/it/LC_MESSAGES/errors.po
+++ b/priv/gettext/it/LC_MESSAGES/errors.po
@@ -3,8 +3,8 @@ msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2020-06-19 14:33+0000\n"
-"PO-Revision-Date: 2020-07-09 14:40+0000\n"
-"Last-Translator: Ben Is <srsbzns@cock.li>\n"
+"PO-Revision-Date: 2021-03-13 09:40+0000\n"
+"Last-Translator: Ben Is <spambenis@fastwebnet.it>\n"
 "Language-Team: Italian <https://translate.pleroma.social/projects/pleroma/"
 "pleroma/it/>\n"
 "Language: it\n"
@@ -45,7 +45,7 @@ msgstr "ha una voce invalida"
 
 ## From Ecto.Changeset.validate_exclusion/3
 msgid "is reserved"
-msgstr "è vietato"
+msgstr "è riservato"
 
 ## From Ecto.Changeset.validate_confirmation/3
 msgid "does not match confirmation"
@@ -123,7 +123,7 @@ msgstr "Richiesta invalida"
 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425
 #, elixir-format
 msgid "Can't delete object"
-msgstr "Non puoi eliminare quest'oggetto"
+msgstr "Oggetto non eliminabile"
 
 #: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:196
 #, elixir-format
@@ -160,12 +160,12 @@ msgstr "Non puoi pubblicare un messaggio vuoto senza allegati"
 #: lib/pleroma/web/common_api/utils.ex:504
 #, elixir-format
 msgid "Comment must be up to %{max_size} characters"
-msgstr "I commenti posso al massimo consistere di %{max_size} caratteri"
+msgstr "I commenti posso al massimo contenere %{max_size} caratteri"
 
 #: lib/pleroma/config/config_db.ex:222
 #, elixir-format
 msgid "Config with params %{params} not found"
-msgstr "Configurazione con parametri %{max_size} non trovata"
+msgstr "Configurazione con parametri %{params} non trovata"
 
 #: lib/pleroma/web/common_api/common_api.ex:95
 #, elixir-format
@@ -200,7 +200,7 @@ msgstr "Non de-intestato"
 #: lib/pleroma/web/common_api/common_api.ex:126
 #, elixir-format
 msgid "Could not unrepeat"
-msgstr "Non de-ripetuto"
+msgstr "Non de-condiviso"
 
 #: lib/pleroma/web/common_api/common_api.ex:428
 #: lib/pleroma/web/common_api/common_api.ex:437
@@ -310,12 +310,12 @@ msgstr "Il messaggio ha superato la lunghezza massima"
 #: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
 #, elixir-format
 msgid "This resource requires authentication."
-msgstr "Accedi per leggere."
+msgstr "Accedi per poter leggere."
 
 #: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
 #, elixir-format
 msgid "Throttled"
-msgstr "Strozzato"
+msgstr "Limitato"
 
 #: lib/pleroma/web/common_api/common_api.ex:266
 #, elixir-format
@@ -347,17 +347,17 @@ msgstr "Devi aggiungere un indirizzo email valido"
 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389
 #, elixir-format
 msgid "can't read inbox of %{nickname} as %{as_nickname}"
-msgstr "non puoi leggere i messaggi privati di %{nickname} come %{as_nickname}"
+msgstr "non puoi leggere i messaggi di %{nickname} come %{as_nickname}"
 
 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472
 #, elixir-format
 msgid "can't update outbox of %{nickname} as %{as_nickname}"
-msgstr "non puoi aggiornare gli inviati di %{nickname} come %{as_nickname}"
+msgstr "non puoi inviare da %{nickname} come %{as_nickname}"
 
 #: lib/pleroma/web/common_api/common_api.ex:388
 #, elixir-format
 msgid "conversation is already muted"
-msgstr "la conversazione è già zittita"
+msgstr "la conversazione è già silenziata"
 
 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:316
 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491
@@ -419,7 +419,7 @@ msgstr "Errore interno"
 #: lib/pleroma/web/oauth/fallback_controller.ex:29
 #, elixir-format
 msgid "Invalid Username/Password"
-msgstr "Nome utente/parola d'ordine invalidi"
+msgstr "Nome utente/password invalidi"
 
 #: lib/pleroma/web/twitter_api/twitter_api.ex:118
 #, elixir-format
@@ -455,7 +455,7 @@ msgstr "Gestore OAuth non supportato: %{provider}."
 #: lib/pleroma/uploaders/uploader.ex:72
 #, elixir-format
 msgid "Uploader callback timeout"
-msgstr "Callback caricatmento scaduta"
+msgstr "Callback caricamento scaduta"
 
 #: lib/pleroma/web/uploader_controller.ex:23
 #, elixir-format
@@ -496,7 +496,7 @@ msgstr "Parametro mancante: %{name}"
 #: lib/pleroma/web/oauth/oauth_controller.ex:322
 #, elixir-format
 msgid "Password reset is required"
-msgstr "Necessario reimpostare parola d'ordine"
+msgstr "Necessario reimpostare password"
 
 #: lib/pleroma/tests/auth_test_controller.ex:9
 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/admin_api_controller.ex:6
@@ -540,34 +540,32 @@ msgstr ""
 #: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210
 #, elixir-format
 msgid "Unexpected error occurred while adding file to pack."
-msgstr "Errore inaspettato durante l'aggiunta del file al pacchetto."
+msgstr "Errore inatteso durante l'aggiunta del file al pacchetto."
 
 #: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138
 #, elixir-format
 msgid "Unexpected error occurred while creating pack."
-msgstr "Errore inaspettato durante la creazione del pacchetto."
+msgstr "Errore inatteso durante la creazione del pacchetto."
 
 #: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278
 #, elixir-format
 msgid "Unexpected error occurred while removing file from pack."
-msgstr "Errore inaspettato durante la rimozione del file dal pacchetto."
+msgstr "Errore inatteso durante la rimozione del file dal pacchetto."
 
 #: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250
 #, elixir-format
 msgid "Unexpected error occurred while updating file in pack."
-msgstr "Errore inaspettato durante l'aggiornamento del file nel pacchetto."
+msgstr "Errore inatteso durante l'aggiornamento del file nel pacchetto."
 
 #: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179
 #, elixir-format
 msgid "Unexpected error occurred while updating pack metadata."
-msgstr "Errore inaspettato durante l'aggiornamento dei metadati del pacchetto."
+msgstr "Errore inatteso durante l'aggiornamento dei metadati del pacchetto."
 
 #: lib/pleroma/plugs/user_is_admin_plug.ex:21
 #, elixir-format
 msgid "User is not an admin."
-msgstr ""
-"L'utente non è un amministratore."
-"OAuth."
+msgstr "L'utente non è un amministratore."
 
 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
 #, elixir-format

From b80f868c6b41d1407cf6e4f2df8913bdf7a954c0 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Sat, 13 Mar 2021 12:27:15 -0600
Subject: [PATCH 078/339] Prefer naming this function build_image_url/2

---
 lib/pleroma/web/mastodon_api/views/status_view.ex | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 581b4e952..71f659ba0 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -386,7 +386,7 @@ def render("card.json", %{rich_media: rich_media, page_url: page_url}) do
         nil
       end
 
-    image_url = get_image_url(image_url_data, page_url_data)
+    image_url = build_image_url(image_url_data, page_url_data)
 
     %{
       type: "link",
@@ -548,8 +548,8 @@ defp build_application(_), do: nil
   # Avoid applying URI.merge unless necessary
   # TODO: revert to always attempting URI.merge(image_url_data, page_url_data)
   # when Elixir 1.12 is the minimum supported version
-  @spec get_image_url(struct() | nil, struct()) :: String.t() | nil
-  defp get_image_url(
+  @spec build_image_url(struct() | nil, struct()) :: String.t() | nil
+  defp build_image_url(
          %URI{scheme: image_scheme, host: image_host} = image_url_data,
          %URI{} = _page_url_data
        )
@@ -557,9 +557,9 @@ defp get_image_url(
     image_url_data |> to_string
   end
 
-  defp get_image_url(%URI{} = image_url_data, %URI{} = page_url_data) do
+  defp build_image_url(%URI{} = image_url_data, %URI{} = page_url_data) do
     URI.merge(page_url_data, image_url_data) |> to_string
   end
 
-  defp get_image_url(_, _), do: nil
+  defp build_image_url(_, _), do: nil
 end

From b1d4b2b81ec97143c41d16ac3f5bc2825b836f4b Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Mon, 15 Mar 2021 06:43:12 +0100
Subject: [PATCH 079/339] Add support for actor icon being a list (Bridgy)

---
 lib/pleroma/web/activity_pub/activity_pub.ex  | 28 +++----
 test/fixtures/bridgy/actor.json               | 80 +++++++++++++++++++
 .../web/activity_pub/activity_pub_test.exs    | 27 +++++++
 3 files changed, 119 insertions(+), 16 deletions(-)
 create mode 100644 test/fixtures/bridgy/actor.json

diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 5b45e2ca1..ff478f44c 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1250,21 +1250,17 @@ defp get_actor_url(url) when is_list(url) do
 
   defp get_actor_url(_url), do: nil
 
+  defp normalize_image(%{"url" => url}) do
+    %{
+      "type" => "Image",
+      "url" => [%{"href" => url}]
+    }
+  end
+
+  defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image()
+  defp normalize_image(_), do: nil
+
   defp object_to_user_data(data) do
-    avatar =
-      data["icon"]["url"] &&
-        %{
-          "type" => "Image",
-          "url" => [%{"href" => data["icon"]["url"]}]
-        }
-
-    banner =
-      data["image"]["url"] &&
-        %{
-          "type" => "Image",
-          "url" => [%{"href" => data["image"]["url"]}]
-        }
-
     fields =
       data
       |> Map.get("attachment", [])
@@ -1308,13 +1304,13 @@ defp object_to_user_data(data) do
       ap_id: data["id"],
       uri: get_actor_url(data["url"]),
       ap_enabled: true,
-      banner: banner,
+      banner: normalize_image(data["image"]),
       fields: fields,
       emoji: emojis,
       is_locked: is_locked,
       is_discoverable: is_discoverable,
       invisible: invisible,
-      avatar: avatar,
+      avatar: normalize_image(data["icon"]),
       name: data["name"],
       follower_address: data["followers"],
       following_address: data["following"],
diff --git a/test/fixtures/bridgy/actor.json b/test/fixtures/bridgy/actor.json
new file mode 100644
index 000000000..5b2d8982b
--- /dev/null
+++ b/test/fixtures/bridgy/actor.json
@@ -0,0 +1,80 @@
+{
+  "id": "https://fed.brid.gy/jk.nipponalba.scot",
+  "url": "https://fed.brid.gy/r/https://jk.nipponalba.scot",
+  "urls": [
+    {
+      "value": "https://jk.nipponalba.scot"
+    },
+    {
+      "value": "https://social.nipponalba.scot/jk"
+    },
+    {
+      "value": "https://px.nipponalba.scot/jk"
+    }
+  ],
+  "@context": "https://www.w3.org/ns/activitystreams",
+  "type": "Person",
+  "name": "J K 🇯🇵🏴󠁧󠁢󠁳󠁣󠁴󠁿",
+  "image": [
+    {
+      "url": "https://jk.nipponalba.scot/images/profile.jpg",
+      "type": "Image",
+      "name": "profile picture"
+    }
+  ],
+  "tag": [
+    {
+      "type": "Tag",
+      "name": "Craft Beer"
+    },
+    {
+      "type": "Tag",
+      "name": "Single Malt Whisky"
+    },
+    {
+      "type": "Tag",
+      "name": "Homebrewing"
+    },
+    {
+      "type": "Tag",
+      "name": "Scottish Politics"
+    },
+    {
+      "type": "Tag",
+      "name": "Scottish History"
+    },
+    {
+      "type": "Tag",
+      "name": "Japanese History"
+    },
+    {
+      "type": "Tag",
+      "name": "Tech"
+    },
+    {
+      "type": "Tag",
+      "name": "Veganism"
+    },
+    {
+      "type": "Tag",
+      "name": "Cooking"
+    }
+  ],
+  "icon": [
+    {
+      "url": "https://jk.nipponalba.scot/images/profile.jpg",
+      "type": "Image",
+      "name": "profile picture"
+    }
+  ],
+  "preferredUsername": "jk.nipponalba.scot",
+  "summary": "",
+  "publicKey": {
+    "id": "jk.nipponalba.scot",
+    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdarxwzxnNbJ2hneWOYHkYJowk\npyigQtxlUd0VjgSQHwxU9kWqfbrHBVADyTtcqi/4dAzQd3UnCI1TPNnn4LPZY9PW\noiWd3Zl1/EfLFxO7LU9GS7fcSLQkyj5JNhSlN3I8QPudZbybrgRDVZYooDe1D+52\n5KLGqC2ajrIVOiDRTQIDAQAB\n-----END PUBLIC KEY-----"
+  },
+  "inbox": "https://fed.brid.gy/jk.nipponalba.scot/inbox",
+  "outbox": "https://fed.brid.gy/jk.nipponalba.scot/outbox",
+  "following": "https://fed.brid.gy/jk.nipponalba.scot/following",
+  "followers": "https://fed.brid.gy/jk.nipponalba.scot/followers"
+}
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index f4023856c..57f12f821 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -208,6 +208,33 @@ test "works for guppe actors" do
       assert user.name == "Bernie2020 group"
       assert user.actor_type == "Group"
     end
+
+    test "works for bridgy actors" do
+      user_id = "https://fed.brid.gy/jk.nipponalba.scot"
+
+      Tesla.Mock.mock(fn
+        %{method: :get, url: ^user_id} ->
+          %Tesla.Env{
+            status: 200,
+            body: File.read!("test/fixtures/bridgy/actor.json"),
+            headers: [{"content-type", "application/activity+json"}]
+          }
+      end)
+
+      {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
+
+      assert user.actor_type == "Person"
+
+      assert user.avatar == %{
+               "type" => "Image",
+               "url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}]
+             }
+
+      assert user.banner == %{
+               "type" => "Image",
+               "url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}]
+             }
+    end
   end
 
   test "it fetches the appropriate tag-restricted posts" do

From 7eecc3b61d6da64e0bfdc5b155cba0dae07b84d5 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Tue, 16 Feb 2021 23:23:35 +0100
Subject: [PATCH 080/339] OpenAPI: MastodonAPI Timeline Controller

---
 .../api_spec/operations/timeline_operation.ex    |  3 ++-
 .../controllers/timeline_controller_test.exs     | 16 ++++++++--------
 2 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/lib/pleroma/web/api_spec/operations/timeline_operation.ex b/lib/pleroma/web/api_spec/operations/timeline_operation.ex
index cae18c758..24d792916 100644
--- a/lib/pleroma/web/api_spec/operations/timeline_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/timeline_operation.ex
@@ -115,7 +115,8 @@ def hashtag_operation do
       ],
       operationId: "TimelineController.hashtag",
       responses: %{
-        200 => Operation.response("Array of Status", "application/json", array_of_statuses())
+        200 => Operation.response("Array of Status", "application/json", array_of_statuses()),
+        401 => Operation.response("Error", "application/json", ApiError)
       }
     }
   end
diff --git a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs
index cc409451c..ed1286675 100644
--- a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs
@@ -905,10 +905,10 @@ defp ensure_authenticated_access(base_uri) do
       %{conn: auth_conn} = oauth_access(["read:statuses"])
 
       res_conn = get(auth_conn, "#{base_uri}?local=true")
-      assert length(json_response(res_conn, 200)) == 1
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
 
       res_conn = get(auth_conn, "#{base_uri}?local=false")
-      assert length(json_response(res_conn, 200)) == 2
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 2
     end
 
     test "with default settings on private instances, returns 403 for unauthenticated users", %{
@@ -922,7 +922,7 @@ test "with default settings on private instances, returns 403 for unauthenticate
       for local <- [true, false] do
         res_conn = get(conn, "#{base_uri}?local=#{local}")
 
-        assert json_response(res_conn, :unauthorized) == error_response
+        assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
       end
 
       ensure_authenticated_access(base_uri)
@@ -939,7 +939,7 @@ test "with `%{local: true, federated: true}`, returns 403 for unauthenticated us
       for local <- [true, false] do
         res_conn = get(conn, "#{base_uri}?local=#{local}")
 
-        assert json_response(res_conn, :unauthorized) == error_response
+        assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
       end
 
       ensure_authenticated_access(base_uri)
@@ -951,10 +951,10 @@ test "with `%{local: false, federated: true}`, forbids unauthenticated access to
       clear_config([:restrict_unauthenticated, :timelines, :federated], true)
 
       res_conn = get(conn, "#{base_uri}?local=true")
-      assert length(json_response(res_conn, 200)) == 1
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
 
       res_conn = get(conn, "#{base_uri}?local=false")
-      assert json_response(res_conn, :unauthorized) == error_response
+      assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
 
       ensure_authenticated_access(base_uri)
     end
@@ -966,11 +966,11 @@ test "with `%{local: true, federated: false}`, forbids unauthenticated access to
       clear_config([:restrict_unauthenticated, :timelines, :federated], false)
 
       res_conn = get(conn, "#{base_uri}?local=true")
-      assert json_response(res_conn, :unauthorized) == error_response
+      assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
 
       # Note: local activities get delivered as part of federated timeline
       res_conn = get(conn, "#{base_uri}?local=false")
-      assert length(json_response(res_conn, 200)) == 2
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 2
 
       ensure_authenticated_access(base_uri)
     end

From 3123ecdd6e7a189f815624ee78be4f62487aa3db Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Tue, 16 Feb 2021 23:37:16 +0100
Subject: [PATCH 081/339] OpenAPI: MastodonAPI Media Controller

---
 lib/pleroma/web/api_spec/operations/media_operation.ex          | 1 +
 .../web/mastodon_api/controllers/media_controller_test.exs      | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/api_spec/operations/media_operation.ex b/lib/pleroma/web/api_spec/operations/media_operation.ex
index 85aa14869..1e245b291 100644
--- a/lib/pleroma/web/api_spec/operations/media_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/media_operation.ex
@@ -105,6 +105,7 @@ def show_operation do
       responses: %{
         200 => Operation.response("Media", "application/json", Attachment),
         401 => Operation.response("Media", "application/json", ApiError),
+        403 => Operation.response("Media", "application/json", ApiError),
         422 => Operation.response("Media", "application/json", ApiError)
       }
     }
diff --git a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs
index 6c8f984d5..39d7f99f6 100644
--- a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs
@@ -140,7 +140,7 @@ test "it returns 403 if media object requested by non-owner", %{object: object,
 
       conn
       |> get("/api/v1/media/#{object.id}")
-      |> json_response(403)
+      |> json_response_and_validate_schema(403)
     end
   end
 end

From e47f83cfc822716c00f3fcaffe73f31208749601 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Tue, 16 Feb 2021 23:39:07 +0100
Subject: [PATCH 082/339] OpenAPI: MastodonAPI Conversation Controller

---
 .../mastodon_api/controllers/conversation_controller_test.exs  | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/test/pleroma/web/mastodon_api/controllers/conversation_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/conversation_controller_test.exs
index 3176f1296..00797a9ea 100644
--- a/test/pleroma/web/mastodon_api/controllers/conversation_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/conversation_controller_test.exs
@@ -214,7 +214,8 @@ test "(vanilla) Mastodon frontend behaviour", %{user: user_one, conn: conn} do
 
     res_conn = get(conn, "/api/v1/statuses/#{direct.id}/context")
 
-    assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200)
+    assert %{"ancestors" => [], "descendants" => []} ==
+             json_response_and_validate_schema(res_conn, 200)
   end
 
   test "Removes a conversation", %{user: user_one, conn: conn} do

From 3a8404820d803ccea44071178cc90f6aafcee80b Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Tue, 16 Feb 2021 23:40:50 +0100
Subject: [PATCH 083/339] Verify MastoFE Controller put_settings response

---
 test/pleroma/web/mastodon_api/masto_fe_controller_test.exs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs b/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs
index ea66c708f..e679d781a 100644
--- a/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs
@@ -20,7 +20,7 @@ test "put settings", %{conn: conn} do
       |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:accounts"]))
       |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
 
-    assert _result = json_response(conn, 200)
+    assert %{} = json_response(conn, 200)
 
     user = User.get_cached_by_ap_id(user.ap_id)
     assert user.mastofe_settings == %{"programming" => "socks"}

From 0c7c6463d13b8a4471b8721912c82fe1cbe3e91a Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Wed, 17 Feb 2021 00:35:26 +0100
Subject: [PATCH 084/339] OpenAPI: MastodonAPI Account Controller, excluding
 OAuth

---
 .../controllers/account_controller_test.exs          | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs
index a327c0d1d..3036e25b3 100644
--- a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs
@@ -514,11 +514,11 @@ test "paginates a user's statuses", %{user: user, conn: conn} do
       {:ok, post_2} = CommonAPI.post(user, %{status: "second post"})
 
       response_1 = get(conn, "/api/v1/accounts/#{user.id}/statuses?limit=1")
-      assert [res] = json_response(response_1, 200)
+      assert [res] = json_response_and_validate_schema(response_1, 200)
       assert res["id"] == post_2.id
 
       response_2 = get(conn, "/api/v1/accounts/#{user.id}/statuses?limit=1&max_id=#{res["id"]}")
-      assert [res] = json_response(response_2, 200)
+      assert [res] = json_response_and_validate_schema(response_2, 200)
       assert res["id"] == post_1.id
 
       refute response_1 == response_2
@@ -881,7 +881,7 @@ test "following without reblogs" do
       assert [] ==
                conn
                |> get("/api/v1/timelines/home")
-               |> json_response(200)
+               |> json_response_and_validate_schema(200)
 
       assert %{"showing_reblogs" => true} =
                conn
@@ -892,7 +892,7 @@ test "following without reblogs" do
       assert [%{"id" => ^reblog_id}] =
                conn
                |> get("/api/v1/timelines/home")
-               |> json_response(200)
+               |> json_response_and_validate_schema(200)
     end
 
     test "following with reblogs" do
@@ -910,7 +910,7 @@ test "following with reblogs" do
       assert [%{"id" => ^reblog_id}] =
                conn
                |> get("/api/v1/timelines/home")
-               |> json_response(200)
+               |> json_response_and_validate_schema(200)
 
       assert %{"showing_reblogs" => false} =
                conn
@@ -921,7 +921,7 @@ test "following with reblogs" do
       assert [] ==
                conn
                |> get("/api/v1/timelines/home")
-               |> json_response(200)
+               |> json_response_and_validate_schema(200)
     end
 
     test "following / unfollowing errors", %{user: user, conn: conn} do

From ef5de5eb398b6d4cbc1ed338f2f41d3bfa1c5fe9 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Wed, 17 Feb 2021 00:45:01 +0100
Subject: [PATCH 085/339] OpenAPI: MastodonAPI Status Controller

---
 .../web/mastodon_api/controllers/status_controller_test.exs  | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
index f616f405e..4c0149a4c 100644
--- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
@@ -81,6 +81,7 @@ test "posting a status", %{conn: conn} do
           "sensitive" => 0
         })
 
+      # Idempotency plug response means detection fail
       assert %{"id" => second_id} = json_response(conn_two, 200)
       assert id == second_id
 
@@ -1542,7 +1543,7 @@ test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{c
       |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
       |> get("api/v1/timelines/home")
 
-    [reblogged_activity] = json_response(conn3, 200)
+    [reblogged_activity] = json_response_and_validate_schema(conn3, 200)
 
     assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
 
@@ -1896,7 +1897,7 @@ test "posting a local only status" do
     local = Pleroma.Constants.as_local_public()
 
     assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
-             json_response(conn_one, 200)
+             json_response_and_validate_schema(conn_one, 200)
 
     assert %Activity{id: ^id, data: %{"to" => [^local]}} = Activity.get_by_id(id)
   end

From e4743847a18cb7cbb9e607232f25eb1cf63a4551 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Wed, 17 Feb 2021 01:07:56 +0100
Subject: [PATCH 086/339] OpenAPI: PleromaAPI UserImport Controller

---
 lib/pleroma/web/api_spec/operations/user_import_operation.ex    | 1 +
 .../web/pleroma_api/controllers/user_import_controller_test.exs | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/api_spec/operations/user_import_operation.ex b/lib/pleroma/web/api_spec/operations/user_import_operation.ex
index 6292e2004..8df19f1fc 100644
--- a/lib/pleroma/web/api_spec/operations/user_import_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/user_import_operation.ex
@@ -23,6 +23,7 @@ def follow_operation do
       requestBody: request_body("Parameters", import_request(), required: true),
       responses: %{
         200 => ok_response(),
+        403 => Operation.response("Error", "application/json", ApiError),
         500 => Operation.response("Error", "application/json", ApiError)
       },
       security: [%{"oAuth" => ["write:follow"]}]
diff --git a/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs
index 25a7f8374..d977bc3a2 100644
--- a/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs
+++ b/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs
@@ -83,7 +83,7 @@ test "requires 'follow' or 'write:follows' permissions" do
           assert %{"error" => "Insufficient permissions: follow | write:follows."} ==
                    json_response(conn, 403)
         else
-          assert json_response(conn, 200)
+          assert json_response_and_validate_schema(conn, 200)
         end
       end
     end

From a22c53810b36c5382c805e1c5ed7e1cf3d747ebc Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Wed, 17 Feb 2021 01:19:25 +0100
Subject: [PATCH 087/339] Remove deprecated
 /api/qvitter/statuses/notifications/read

---
 CHANGELOG.md                                  |  3 ++
 lib/pleroma/web/router.ex                     |  6 ---
 lib/pleroma/web/twitter_api/controller.ex     | 33 -------------
 .../web/twitter_api/controller_test.exs       | 49 -------------------
 4 files changed, 3 insertions(+), 88 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50484aaef..ce0bb1cb5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 - The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
 
+### Removed
+- **Breaking**: Remove deprecated `/api/qvitter/statuses/notifications/read` (replaced by `/api/v1/pleroma/notifications/read`)
+
 ## Unreleased (Patch)
 
 ## [2.3.0] - 2020-03-01
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index de0bd27d7..ce2d701d7 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -620,12 +620,6 @@ defmodule Pleroma.Web.Router do
 
     get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens)
     delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token)
-
-    post(
-      "/qvitter/statuses/notifications/read",
-      TwitterAPI.Controller,
-      :mark_notifications_as_read
-    )
   end
 
   scope "/", Pleroma.Web do
diff --git a/lib/pleroma/web/twitter_api/controller.ex b/lib/pleroma/web/twitter_api/controller.ex
index 077bfa70d..e32713311 100644
--- a/lib/pleroma/web/twitter_api/controller.ex
+++ b/lib/pleroma/web/twitter_api/controller.ex
@@ -5,7 +5,6 @@
 defmodule Pleroma.Web.TwitterAPI.Controller do
   use Pleroma.Web, :controller
 
-  alias Pleroma.Notification
   alias Pleroma.User
   alias Pleroma.Web.OAuth.Token
   alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
@@ -14,11 +13,6 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
 
   require Logger
 
-  plug(
-    OAuthScopesPlug,
-    %{scopes: ["write:notifications"]} when action == :mark_notifications_as_read
-  )
-
   plug(
     :skip_plug,
     [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirm_email
@@ -67,31 +61,4 @@ defp json_reply(conn, status, json) do
     |> put_resp_content_type("application/json")
     |> send_resp(status, json)
   end
-
-  def mark_notifications_as_read(
-        %{assigns: %{user: user}} = conn,
-        %{"latest_id" => latest_id} = params
-      ) do
-    Notification.set_read_up_to(user, latest_id)
-
-    notifications = Notification.for_user(user, params)
-
-    conn
-    # XXX: This is a hack because pleroma-fe still uses that API.
-    |> put_view(Pleroma.Web.MastodonAPI.NotificationView)
-    |> render("index.json", %{notifications: notifications, for: user})
-  end
-
-  def mark_notifications_as_read(%{assigns: %{user: _user}} = conn, _) do
-    bad_request_reply(conn, "You need to specify latest_id")
-  end
-
-  defp bad_request_reply(conn, error_message) do
-    json = error_json(conn, error_message)
-    json_reply(conn, 400, json)
-  end
-
-  defp error_json(conn, error_message) do
-    %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
-  end
 end
diff --git a/test/pleroma/web/twitter_api/controller_test.exs b/test/pleroma/web/twitter_api/controller_test.exs
index 583c904b2..bca9e2dad 100644
--- a/test/pleroma/web/twitter_api/controller_test.exs
+++ b/test/pleroma/web/twitter_api/controller_test.exs
@@ -7,59 +7,10 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
 
   alias Pleroma.Repo
   alias Pleroma.User
-  alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.OAuth.Token
 
   import Pleroma.Factory
 
-  describe "POST /api/qvitter/statuses/notifications/read" do
-    test "without valid credentials", %{conn: conn} do
-      conn = post(conn, "/api/qvitter/statuses/notifications/read", %{"latest_id" => 1_234_567})
-      assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
-    end
-
-    test "with credentials, without any params" do
-      %{conn: conn} = oauth_access(["write:notifications"])
-
-      conn = post(conn, "/api/qvitter/statuses/notifications/read")
-
-      assert json_response(conn, 400) == %{
-               "error" => "You need to specify latest_id",
-               "request" => "/api/qvitter/statuses/notifications/read"
-             }
-    end
-
-    test "with credentials, with params" do
-      %{user: current_user, conn: conn} =
-        oauth_access(["read:notifications", "write:notifications"])
-
-      other_user = insert(:user)
-
-      {:ok, _activity} =
-        CommonAPI.post(other_user, %{
-          status: "Hey @#{current_user.nickname}"
-        })
-
-      response_conn =
-        conn
-        |> get("/api/v1/notifications")
-
-      [notification] = json_response(response_conn, 200)
-
-      assert notification["pleroma"]["is_seen"] == false
-
-      response_conn =
-        conn
-        |> post("/api/qvitter/statuses/notifications/read", %{"latest_id" => notification["id"]})
-
-      [notification] = response = json_response(response_conn, 200)
-
-      assert length(response) == 1
-
-      assert notification["pleroma"]["is_seen"] == true
-    end
-  end
-
   describe "GET /api/account/confirm_email/:id/:token" do
     setup do
       {:ok, user} =

From 65cd9cb6384676c1660aa7f4da0f98ff7f43b999 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Wed, 17 Feb 2021 09:41:40 +0100
Subject: [PATCH 088/339] TwitterAPI: Remove unused read notification function

---
 .../web/twitter_api/controllers/util_controller.ex  | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index 940a645bb..60266aaab 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -10,7 +10,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
   alias Pleroma.Config
   alias Pleroma.Emoji
   alias Pleroma.Healthcheck
-  alias Pleroma.Notification
   alias Pleroma.User
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.Plugs.OAuthScopesPlug
@@ -30,7 +29,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
          ]
   )
 
-  plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
 
   def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
     with %User{} = user <- User.get_cached_by_nickname(nick),
@@ -62,17 +60,6 @@ def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profil
     end
   end
 
-  def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
-    with {:ok, _} <- Notification.read_one(user, notification_id) do
-      json(conn, %{status: "success"})
-    else
-      {:error, message} ->
-        conn
-        |> put_resp_content_type("application/json")
-        |> send_resp(403, Jason.encode!(%{"error" => message}))
-    end
-  end
-
   def frontend_configurations(conn, _params) do
     render(conn, "frontend_configurations.json")
   end

From 55bdfb075c1cc5226948e3ff9d39fdae27aa9257 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Wed, 24 Feb 2021 23:40:33 +0100
Subject: [PATCH 089/339] OpenAPI: TwitterAPI Util Controller

---
 .../operations/twitter_util_operation.ex      | 219 ++++++++++++++++++
 .../controllers/util_controller.ex            |  24 +-
 .../web/twitter_api/util_controller_test.exs  | 204 +++++++++-------
 3 files changed, 360 insertions(+), 87 deletions(-)
 create mode 100644 lib/pleroma/web/api_spec/operations/twitter_util_operation.ex

diff --git a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex b/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
new file mode 100644
index 000000000..62c9826f6
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
@@ -0,0 +1,219 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.ApiError
+  alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
+
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  def emoji_operation do
+    %Operation{
+      tags: ["Emojis"],
+      summary: "List all custom emojis",
+      operationId: "UtilController.emoji",
+      parameters: [],
+      responses: %{
+        200 =>
+          Operation.response("List", "application/json", %Schema{
+            type: :object,
+            additionalProperties: %Schema{
+              type: :object,
+              properties: %{
+                image_url: %Schema{type: :string},
+                tags: %Schema{type: :array, items: %Schema{type: :string}}
+              }
+            },
+            example: %{
+              "firefox" => %{
+                "image_url" => "/emoji/firefox.png",
+                "tag" => ["Fun"]
+              }
+            }
+          })
+      }
+    }
+  end
+
+  def frontend_configurations_operation do
+    %Operation{
+      tags: ["Configuration"],
+      summary: "Dump frontend configurations",
+      operationId: "UtilController.frontend_configurations",
+      parameters: [],
+      responses: %{
+        200 =>
+          Operation.response("List", "application/json", %Schema{
+            type: :object,
+            additionalProperties: %Schema{type: :object}
+          })
+      }
+    }
+  end
+
+  def change_password_operation do
+    %Operation{
+      tags: ["Accounts"],
+      summary: "Change account password",
+      security: [%{"oAuth" => ["write:accounts"]}],
+      operationId: "UtilController.change_password",
+      parameters: [
+        Operation.parameter(:password, :query, :string, "Current password", required: true),
+        Operation.parameter(:new_password, :query, :string, "New password", required: true),
+        Operation.parameter(
+          :new_password_confirmation,
+          :query,
+          :string,
+          "New password, confirmation",
+          required: true
+        )
+      ],
+      responses: %{
+        200 =>
+          Operation.response("Success", "application/json", %Schema{
+            type: :object,
+            properties: %{status: %Schema{type: :string, example: "success"}}
+          }),
+        400 => Operation.response("Error", "application/json", ApiError),
+        403 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def change_email_operation do
+    %Operation{
+      tags: ["Accounts"],
+      summary: "Change account email",
+      security: [%{"oAuth" => ["write:accounts"]}],
+      operationId: "UtilController.change_email",
+      parameters: [
+        Operation.parameter(:password, :query, :string, "Current password", required: true),
+        Operation.parameter(:email, :query, :string, "New email", required: true)
+      ],
+      requestBody: nil,
+      responses: %{
+        200 =>
+          Operation.response("Success", "application/json", %Schema{
+            type: :object,
+            properties: %{status: %Schema{type: :string, example: "success"}}
+          }),
+        400 => Operation.response("Error", "application/json", ApiError),
+        403 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def update_notificaton_settings_operation do
+    %Operation{
+      tags: ["Accounts"],
+      summary: "Update Notification Settings",
+      security: [%{"oAuth" => ["write:accounts"]}],
+      operationId: "UtilController.update_notificaton_settings",
+      parameters: [
+        Operation.parameter(
+          :block_from_strangers,
+          :query,
+          BooleanLike,
+          "blocks notifications from accounts you do not follow"
+        ),
+        Operation.parameter(
+          :hide_notification_contents,
+          :query,
+          BooleanLike,
+          "removes the contents of a message from the push notification"
+        )
+      ],
+      requestBody: nil,
+      responses: %{
+        200 =>
+          Operation.response("Success", "application/json", %Schema{
+            type: :object,
+            properties: %{status: %Schema{type: :string, example: "success"}}
+          }),
+        400 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def disable_account_operation do
+    %Operation{
+      tags: ["Accounts"],
+      summary: "Disable Account",
+      security: [%{"oAuth" => ["write:accounts"]}],
+      operationId: "UtilController.disable_account",
+      parameters: [
+        Operation.parameter(:password, :query, :string, "Password")
+      ],
+      responses: %{
+        200 =>
+          Operation.response("Success", "application/json", %Schema{
+            type: :object,
+            properties: %{status: %Schema{type: :string, example: "success"}}
+          }),
+        403 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def delete_account_operation do
+    %Operation{
+      tags: ["Accounts"],
+      summary: "Delete Account",
+      security: [%{"oAuth" => ["write:accounts"]}],
+      operationId: "UtilController.delete_account",
+      parameters: [
+        Operation.parameter(:password, :query, :string, "Password")
+      ],
+      responses: %{
+        200 =>
+          Operation.response("Success", "application/json", %Schema{
+            type: :object,
+            properties: %{status: %Schema{type: :string, example: "success"}}
+          }),
+        403 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def captcha_operation do
+    %Operation{
+      summary: "Get a captcha",
+      operationId: "UtilController.captcha",
+      parameters: [],
+      responses: %{
+        200 => Operation.response("Success", "application/json", %Schema{type: :object})
+      }
+    }
+  end
+
+  def healthcheck_operation do
+    %Operation{
+      tags: ["Accounts"],
+      summary: "Disable Account",
+      security: [%{"oAuth" => ["write:accounts"]}],
+      operationId: "UtilController.healthcheck",
+      parameters: [],
+      responses: %{
+        200 => Operation.response("Healthy", "application/json", %Schema{type: :object}),
+        503 =>
+          Operation.response("Disabled or Unhealthy", "application/json", %Schema{type: :object})
+      }
+    }
+  end
+
+  def remote_subscribe_operation do
+    %Operation{
+      tags: ["Accounts"],
+      summary: "Remote Subscribe",
+      operationId: "UtilController.remote_subscribe",
+      parameters: [],
+      responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})}
+    }
+  end
+end
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index 60266aaab..a2e69666e 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -15,6 +15,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
   alias Pleroma.Web.Plugs.OAuthScopesPlug
   alias Pleroma.Web.WebFinger
 
+  plug(Pleroma.Web.ApiSpec.CastAndValidate when action != :remote_subscribe)
   plug(Pleroma.Web.Plugs.FederatingPlug when action == :remote_subscribe)
 
   plug(
@@ -29,6 +30,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
          ]
   )
 
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TwitterUtilOperation
 
   def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
     with %User{} = user <- User.get_cached_by_nickname(nick),
@@ -79,13 +81,17 @@ def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
     end
   end
 
-  def change_password(%{assigns: %{user: user}} = conn, params) do
-    case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
+  def change_password(%{assigns: %{user: user}} = conn, %{
+        password: password,
+        new_password: new_password,
+        new_password_confirmation: new_password_confirmation
+      }) do
+    case CommonAPI.Utils.confirm_current_password(user, password) do
       {:ok, user} ->
         with {:ok, _user} <-
                User.reset_password(user, %{
-                 password: params["new_password"],
-                 password_confirmation: params["new_password_confirmation"]
+                 password: new_password,
+                 password_confirmation: new_password_confirmation
                }) do
           json(conn, %{status: "success"})
         else
@@ -102,10 +108,10 @@ def change_password(%{assigns: %{user: user}} = conn, params) do
     end
   end
 
-  def change_email(%{assigns: %{user: user}} = conn, params) do
-    case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
+  def change_email(%{assigns: %{user: user}} = conn, %{password: password, email: email}) do
+    case CommonAPI.Utils.confirm_current_password(user, password) do
       {:ok, user} ->
-        with {:ok, _user} <- User.change_email(user, params["email"]) do
+        with {:ok, _user} <- User.change_email(user, email) do
           json(conn, %{status: "success"})
         else
           {:error, changeset} ->
@@ -122,7 +128,7 @@ def change_email(%{assigns: %{user: user}} = conn, params) do
   end
 
   def delete_account(%{assigns: %{user: user}} = conn, params) do
-    password = params["password"] || ""
+    password = params[:password] || ""
 
     case CommonAPI.Utils.confirm_current_password(user, password) do
       {:ok, user} ->
@@ -135,7 +141,7 @@ def delete_account(%{assigns: %{user: user}} = conn, params) do
   end
 
   def disable_account(%{assigns: %{user: user}} = conn, params) do
-    case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
+    case CommonAPI.Utils.confirm_current_password(user, params[:password]) do
       {:ok, user} ->
         User.set_activation_async(user, false)
         json(conn, %{status: "success"})
diff --git a/test/pleroma/web/twitter_api/util_controller_test.exs b/test/pleroma/web/twitter_api/util_controller_test.exs
index bdbc478c3..cc17940b5 100644
--- a/test/pleroma/web/twitter_api/util_controller_test.exs
+++ b/test/pleroma/web/twitter_api/util_controller_test.exs
@@ -25,11 +25,14 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
 
     test "it updates notification settings", %{user: user, conn: conn} do
       conn
-      |> put("/api/pleroma/notification_settings", %{
-        "block_from_strangers" => true,
-        "bar" => 1
-      })
-      |> json_response(:ok)
+      |> put(
+        "/api/pleroma/notification_settings?#{
+          URI.encode_query(%{
+            block_from_strangers: true
+          })
+        }"
+      )
+      |> json_response_and_validate_schema(:ok)
 
       user = refresh_record(user)
 
@@ -41,8 +44,14 @@ test "it updates notification settings", %{user: user, conn: conn} do
 
     test "it updates notification settings to enable hiding contents", %{user: user, conn: conn} do
       conn
-      |> put("/api/pleroma/notification_settings", %{"hide_notification_contents" => "1"})
-      |> json_response(:ok)
+      |> put(
+        "/api/pleroma/notification_settings?#{
+          URI.encode_query(%{
+            hide_notification_contents: 1
+          })
+        }"
+      )
+      |> json_response_and_validate_schema(:ok)
 
       user = refresh_record(user)
 
@@ -70,7 +79,7 @@ test "returns everything in :pleroma, :frontend_configurations", %{conn: conn} d
       response =
         conn
         |> get("/api/pleroma/frontend_configurations")
-        |> json_response(:ok)
+        |> json_response_and_validate_schema(:ok)
 
       assert response == Jason.encode!(config |> Enum.into(%{})) |> Jason.decode!()
     end
@@ -81,7 +90,7 @@ test "returns json with custom emoji with tags", %{conn: conn} do
       emoji =
         conn
         |> get("/api/pleroma/emoji")
-        |> json_response(200)
+        |> json_response_and_validate_schema(200)
 
       assert Enum.all?(emoji, fn
                {_key,
@@ -103,7 +112,7 @@ test "returns 503 when healthcheck disabled", %{conn: conn} do
       response =
         conn
         |> get("/api/pleroma/healthcheck")
-        |> json_response(503)
+        |> json_response_and_validate_schema(503)
 
       assert response == %{}
     end
@@ -116,7 +125,7 @@ test "returns 200 when healthcheck enabled and all ok", %{conn: conn} do
         response =
           conn
           |> get("/api/pleroma/healthcheck")
-          |> json_response(200)
+          |> json_response_and_validate_schema(200)
 
         assert %{
                  "active" => _,
@@ -136,7 +145,7 @@ test "returns 503 when healthcheck enabled and health is false", %{conn: conn} d
         response =
           conn
           |> get("/api/pleroma/healthcheck")
-          |> json_response(503)
+          |> json_response_and_validate_schema(503)
 
         assert %{
                  "active" => _,
@@ -155,8 +164,8 @@ test "returns 503 when healthcheck enabled and health is false", %{conn: conn} d
     test "with valid permissions and password, it disables the account", %{conn: conn, user: user} do
       response =
         conn
-        |> post("/api/pleroma/disable_account", %{"password" => "test"})
-        |> json_response(:ok)
+        |> post("/api/pleroma/disable_account?password=test")
+        |> json_response_and_validate_schema(:ok)
 
       assert response == %{"status" => "success"}
       ObanHelpers.perform_all()
@@ -171,8 +180,8 @@ test "with valid permissions and invalid password, it returns an error", %{conn:
 
       response =
         conn
-        |> post("/api/pleroma/disable_account", %{"password" => "test1"})
-        |> json_response(:ok)
+        |> post("/api/pleroma/disable_account?password=test1")
+        |> json_response_and_validate_schema(:ok)
 
       assert response == %{"error" => "Invalid password."}
       user = User.get_cached_by_id(user.id)
@@ -252,54 +261,61 @@ test "without permissions", %{conn: conn} do
       conn =
         conn
         |> assign(:token, nil)
-        |> post("/api/pleroma/change_email")
+        |> post(
+          "/api/pleroma/change_email?#{
+            URI.encode_query(%{password: "hi", email: "test@test.com"})
+          }"
+        )
 
-      assert json_response(conn, 403) == %{"error" => "Insufficient permissions: write:accounts."}
+      assert json_response_and_validate_schema(conn, 403) == %{
+               "error" => "Insufficient permissions: write:accounts."
+             }
     end
 
     test "with proper permissions and invalid password", %{conn: conn} do
       conn =
-        post(conn, "/api/pleroma/change_email", %{
-          "password" => "hi",
-          "email" => "test@test.com"
-        })
+        post(
+          conn,
+          "/api/pleroma/change_email?#{
+            URI.encode_query(%{password: "hi", email: "test@test.com"})
+          }"
+        )
 
-      assert json_response(conn, 200) == %{"error" => "Invalid password."}
+      assert json_response_and_validate_schema(conn, 200) == %{"error" => "Invalid password."}
     end
 
     test "with proper permissions, valid password and invalid email", %{
       conn: conn
     } do
       conn =
-        post(conn, "/api/pleroma/change_email", %{
-          "password" => "test",
-          "email" => "foobar"
-        })
+        post(
+          conn,
+          "/api/pleroma/change_email?#{URI.encode_query(%{password: "test", email: "foobar"})}"
+        )
 
-      assert json_response(conn, 200) == %{"error" => "Email has invalid format."}
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "error" => "Email has invalid format."
+             }
     end
 
     test "with proper permissions, valid password and no email", %{
       conn: conn
     } do
-      conn =
-        post(conn, "/api/pleroma/change_email", %{
-          "password" => "test"
-        })
+      conn = post(conn, "/api/pleroma/change_email?#{URI.encode_query(%{password: "test"})}")
 
-      assert json_response(conn, 200) == %{"error" => "Email can't be blank."}
+      assert %{"error" => "Missing field: email."} = json_response_and_validate_schema(conn, 400)
     end
 
     test "with proper permissions, valid password and blank email", %{
       conn: conn
     } do
       conn =
-        post(conn, "/api/pleroma/change_email", %{
-          "password" => "test",
-          "email" => ""
-        })
+        post(
+          conn,
+          "/api/pleroma/change_email?#{URI.encode_query(%{password: "test", email: ""})}"
+        )
 
-      assert json_response(conn, 200) == %{"error" => "Email can't be blank."}
+      assert json_response_and_validate_schema(conn, 200) == %{"error" => "Email can't be blank."}
     end
 
     test "with proper permissions, valid password and non unique email", %{
@@ -308,24 +324,28 @@ test "with proper permissions, valid password and non unique email", %{
       user = insert(:user)
 
       conn =
-        post(conn, "/api/pleroma/change_email", %{
-          "password" => "test",
-          "email" => user.email
-        })
+        post(
+          conn,
+          "/api/pleroma/change_email?#{URI.encode_query(%{password: "test", email: user.email})}"
+        )
 
-      assert json_response(conn, 200) == %{"error" => "Email has already been taken."}
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "error" => "Email has already been taken."
+             }
     end
 
     test "with proper permissions, valid password and valid email", %{
       conn: conn
     } do
       conn =
-        post(conn, "/api/pleroma/change_email", %{
-          "password" => "test",
-          "email" => "cofe@foobar.com"
-        })
+        post(
+          conn,
+          "/api/pleroma/change_email?#{
+            URI.encode_query(%{password: "test", email: "cofe@foobar.com"})
+          }"
+        )
 
-      assert json_response(conn, 200) == %{"status" => "success"}
+      assert json_response_and_validate_schema(conn, 200) == %{"status" => "success"}
     end
   end
 
@@ -336,20 +356,35 @@ test "without permissions", %{conn: conn} do
       conn =
         conn
         |> assign(:token, nil)
-        |> post("/api/pleroma/change_password")
+        |> post(
+          "/api/pleroma/change_password?#{
+            URI.encode_query(%{
+              password: "hi",
+              new_password: "newpass",
+              new_password_confirmation: "newpass"
+            })
+          }"
+        )
 
-      assert json_response(conn, 403) == %{"error" => "Insufficient permissions: write:accounts."}
+      assert json_response_and_validate_schema(conn, 403) == %{
+               "error" => "Insufficient permissions: write:accounts."
+             }
     end
 
     test "with proper permissions and invalid password", %{conn: conn} do
       conn =
-        post(conn, "/api/pleroma/change_password", %{
-          "password" => "hi",
-          "new_password" => "newpass",
-          "new_password_confirmation" => "newpass"
-        })
+        post(
+          conn,
+          "/api/pleroma/change_password?#{
+            URI.encode_query(%{
+              password: "hi",
+              new_password: "newpass",
+              new_password_confirmation: "newpass"
+            })
+          }"
+        )
 
-      assert json_response(conn, 200) == %{"error" => "Invalid password."}
+      assert json_response_and_validate_schema(conn, 200) == %{"error" => "Invalid password."}
     end
 
     test "with proper permissions, valid password and new password and confirmation not matching",
@@ -357,13 +392,18 @@ test "with proper permissions, valid password and new password and confirmation
            conn: conn
          } do
       conn =
-        post(conn, "/api/pleroma/change_password", %{
-          "password" => "test",
-          "new_password" => "newpass",
-          "new_password_confirmation" => "notnewpass"
-        })
+        post(
+          conn,
+          "/api/pleroma/change_password?#{
+            URI.encode_query(%{
+              password: "test",
+              new_password: "newpass",
+              new_password_confirmation: "notnewpass"
+            })
+          }"
+        )
 
-      assert json_response(conn, 200) == %{
+      assert json_response_and_validate_schema(conn, 200) == %{
                "error" => "New password does not match confirmation."
              }
     end
@@ -372,13 +412,14 @@ test "with proper permissions, valid password and invalid new password", %{
       conn: conn
     } do
       conn =
-        post(conn, "/api/pleroma/change_password", %{
-          "password" => "test",
-          "new_password" => "",
-          "new_password_confirmation" => ""
-        })
+        post(
+          conn,
+          "/api/pleroma/change_password?#{
+            URI.encode_query(%{password: "test", new_password: "", new_password_confirmation: ""})
+          }"
+        )
 
-      assert json_response(conn, 200) == %{
+      assert json_response_and_validate_schema(conn, 200) == %{
                "error" => "New password can't be blank."
              }
     end
@@ -388,13 +429,18 @@ test "with proper permissions, valid password and matching new password and conf
       user: user
     } do
       conn =
-        post(conn, "/api/pleroma/change_password", %{
-          "password" => "test",
-          "new_password" => "newpass",
-          "new_password_confirmation" => "newpass"
-        })
+        post(
+          conn,
+          "/api/pleroma/change_password?#{
+            URI.encode_query(%{
+              password: "test",
+              new_password: "newpass",
+              new_password_confirmation: "newpass"
+            })
+          }"
+        )
 
-      assert json_response(conn, 200) == %{"status" => "success"}
+      assert json_response_and_validate_schema(conn, 200) == %{"status" => "success"}
       fetched_user = User.get_cached_by_id(user.id)
       assert Pleroma.Password.Pbkdf2.verify_pass("newpass", fetched_user.password_hash) == true
     end
@@ -409,7 +455,7 @@ test "without permissions", %{conn: conn} do
         |> assign(:token, nil)
         |> post("/api/pleroma/delete_account")
 
-      assert json_response(conn, 403) ==
+      assert json_response_and_validate_schema(conn, 403) ==
                %{"error" => "Insufficient permissions: write:accounts."}
     end
 
@@ -417,14 +463,16 @@ test "with proper permissions and wrong or missing password", %{conn: conn} do
       for params <- [%{"password" => "hi"}, %{}] do
         ret_conn = post(conn, "/api/pleroma/delete_account", params)
 
-        assert json_response(ret_conn, 200) == %{"error" => "Invalid password."}
+        assert json_response_and_validate_schema(ret_conn, 200) == %{
+                 "error" => "Invalid password."
+               }
       end
     end
 
     test "with proper permissions and valid password", %{conn: conn, user: user} do
-      conn = post(conn, "/api/pleroma/delete_account", %{"password" => "test"})
+      conn = post(conn, "/api/pleroma/delete_account?password=test")
       ObanHelpers.perform_all()
-      assert json_response(conn, 200) == %{"status" => "success"}
+      assert json_response_and_validate_schema(conn, 200) == %{"status" => "success"}
 
       user = User.get_by_id(user.id)
       refute user.is_active

From d7e51206a251b9da0180a4df3c879531ac302e1a Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Thu, 18 Mar 2021 13:49:03 +0300
Subject: [PATCH 090/339] respect content-type header in finger request

---
 lib/pleroma/web/web_finger.ex                 | 90 +++++++++++--------
 lib/pleroma/web/xml.ex                        |  2 +-
 .../fixtures/tesla_mock/xn--q9jyb4c_host_meta |  4 -
 test/pleroma/web/web_finger_test.exs          | 67 ++++++++++++++
 test/support/http_request_mock.ex             | 47 +++++-----
 5 files changed, 141 insertions(+), 69 deletions(-)
 delete mode 100644 test/fixtures/tesla_mock/xn--q9jyb4c_host_meta

diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex
index 15002b29f..21b10e654 100644
--- a/lib/pleroma/web/web_finger.ex
+++ b/lib/pleroma/web/web_finger.ex
@@ -94,52 +94,56 @@ def represent_user(user, "XML") do
     |> XmlBuilder.to_doc()
   end
 
-  defp webfinger_from_xml(doc) do
-    subject = XML.string_from_xpath("//Subject", doc)
+  defp webfinger_from_xml(body) do
+    with {:ok, doc} <- XML.parse_document(body) do
+      subject = XML.string_from_xpath("//Subject", doc)
 
-    subscribe_address =
-      ~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template}
-      |> XML.string_from_xpath(doc)
+      subscribe_address =
+        ~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template}
+        |> XML.string_from_xpath(doc)
 
-    ap_id =
-      ~s{//Link[@rel="self" and @type="application/activity+json"]/@href}
-      |> XML.string_from_xpath(doc)
+      ap_id =
+        ~s{//Link[@rel="self" and @type="application/activity+json"]/@href}
+        |> XML.string_from_xpath(doc)
 
-    data = %{
-      "subject" => subject,
-      "subscribe_address" => subscribe_address,
-      "ap_id" => ap_id
-    }
+      data = %{
+        "subject" => subject,
+        "subscribe_address" => subscribe_address,
+        "ap_id" => ap_id
+      }
 
-    {:ok, data}
+      {:ok, data}
+    end
   end
 
-  defp webfinger_from_json(doc) do
-    data =
-      Enum.reduce(doc["links"], %{"subject" => doc["subject"]}, fn link, data ->
-        case {link["type"], link["rel"]} do
-          {"application/activity+json", "self"} ->
-            Map.put(data, "ap_id", link["href"])
+  defp webfinger_from_json(body) do
+    with {:ok, doc} <- Jason.decode(body) do
+      data =
+        Enum.reduce(doc["links"], %{"subject" => doc["subject"]}, fn link, data ->
+          case {link["type"], link["rel"]} do
+            {"application/activity+json", "self"} ->
+              Map.put(data, "ap_id", link["href"])
 
-          {"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "self"} ->
-            Map.put(data, "ap_id", link["href"])
+            {"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "self"} ->
+              Map.put(data, "ap_id", link["href"])
 
-          {nil, "http://ostatus.org/schema/1.0/subscribe"} ->
-            Map.put(data, "subscribe_address", link["template"])
+            {nil, "http://ostatus.org/schema/1.0/subscribe"} ->
+              Map.put(data, "subscribe_address", link["template"])
 
-          _ ->
-            Logger.debug("Unhandled type: #{inspect(link["type"])}")
-            data
-        end
-      end)
+            _ ->
+              Logger.debug("Unhandled type: #{inspect(link["type"])}")
+              data
+          end
+        end)
 
-    {:ok, data}
+      {:ok, data}
+    end
   end
 
   def get_template_from_xml(body) do
     xpath = "//Link[@rel='lrdd']/@template"
 
-    with doc when doc != :error <- XML.parse_document(body),
+    with {:ok, doc} <- XML.parse_document(body),
          template when template != nil <- XML.string_from_xpath(xpath, doc) do
       {:ok, template}
     end
@@ -192,15 +196,23 @@ def finger(account) do
              address,
              [{"accept", "application/xrd+xml,application/jrd+json"}]
            ),
-         {:ok, %{status: status, body: body}} when status in 200..299 <- response do
-      doc = XML.parse_document(body)
+         {:ok, %{status: status, body: body, headers: headers}} when status in 200..299 <-
+           response do
+      case List.keyfind(headers, "content-type", 0) do
+        {_, content_type} ->
+          case Plug.Conn.Utils.media_type(content_type) do
+            {:ok, "application", subtype, _} when subtype in ~w(xrd+xml xml) ->
+              webfinger_from_xml(body)
 
-      if doc != :error do
-        webfinger_from_xml(doc)
-      else
-        with {:ok, doc} <- Jason.decode(body) do
-          webfinger_from_json(doc)
-        end
+            {:ok, "application", subtype, _} when subtype in ~w(jrd+json json) ->
+              webfinger_from_json(body)
+
+            _ ->
+              {:error, {:content_type, content_type}}
+          end
+
+        _ ->
+          {:error, {:content_type, nil}}
       end
     else
       e ->
diff --git a/lib/pleroma/web/xml.ex b/lib/pleroma/web/xml.ex
index 2b34611ac..0ab6e9d32 100644
--- a/lib/pleroma/web/xml.ex
+++ b/lib/pleroma/web/xml.ex
@@ -31,7 +31,7 @@ def parse_document(text) do
         |> :binary.bin_to_list()
         |> :xmerl_scan.string(quiet: true)
 
-      doc
+      {:ok, doc}
     rescue
       _e ->
         Logger.debug("Couldn't parse XML: #{inspect(text)}")
diff --git a/test/fixtures/tesla_mock/xn--q9jyb4c_host_meta b/test/fixtures/tesla_mock/xn--q9jyb4c_host_meta
deleted file mode 100644
index 45d260e55..000000000
--- a/test/fixtures/tesla_mock/xn--q9jyb4c_host_meta
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
-    <Link rel="lrdd" template="https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource={uri}" type="application/xrd+xml" />
-</XRD>
diff --git a/test/pleroma/web/web_finger_test.exs b/test/pleroma/web/web_finger_test.exs
index 84477d5a1..2d7b4a40b 100644
--- a/test/pleroma/web/web_finger_test.exs
+++ b/test/pleroma/web/web_finger_test.exs
@@ -45,6 +45,26 @@ test "returns error for nonsensical input" do
       assert {:error, _} = WebFinger.finger("pleroma.social")
     end
 
+    test "returns error when there is no content-type header" do
+      Tesla.Mock.mock(fn
+        %{url: "http://social.heldscal.la/.well-known/host-meta"} ->
+          {:ok,
+           %Tesla.Env{
+             status: 200,
+             body: File.read!("test/fixtures/tesla_mock/social.heldscal.la_host_meta")
+           }}
+
+        %{
+          url:
+            "https://social.heldscal.la/.well-known/webfinger?resource=acct:invalid_content@social.heldscal.la"
+        } ->
+          {:ok, %Tesla.Env{status: 200, body: ""}}
+      end)
+
+      user = "invalid_content@social.heldscal.la"
+      assert {:error, {:content_type, nil}} = WebFinger.finger(user)
+    end
+
     test "returns error when fails parse xml or json" do
       user = "invalid_content@social.heldscal.la"
       assert {:error, %Jason.DecodeError{}} = WebFinger.finger(user)
@@ -113,5 +133,52 @@ test "it works with idna domains as link" do
       ap_id = "https://" <> to_string(:idna.encode("zetsubou.みんな")) <> "/users/lain"
       {:ok, _data} = WebFinger.finger(ap_id)
     end
+
+    test "respects json content-type" do
+      Tesla.Mock.mock(fn
+        %{
+          url:
+            "https://mastodon.social/.well-known/webfinger?resource=acct:emelie@mastodon.social"
+        } ->
+          {:ok,
+           %Tesla.Env{
+             status: 200,
+             body: File.read!("test/fixtures/tesla_mock/webfinger_emelie.json"),
+             headers: [{"content-type", "application/jrd+json"}]
+           }}
+
+        %{url: "http://mastodon.social/.well-known/host-meta"} ->
+          {:ok,
+           %Tesla.Env{
+             status: 200,
+             body: File.read!("test/fixtures/tesla_mock/mastodon.social_host_meta")
+           }}
+      end)
+
+      {:ok, _data} = WebFinger.finger("emelie@mastodon.social")
+    end
+
+    test "respects xml content-type" do
+      Tesla.Mock.mock(fn
+        %{
+          url: "https://pawoo.net/.well-known/webfinger?resource=acct:pekorino@pawoo.net"
+        } ->
+          {:ok,
+           %Tesla.Env{
+             status: 200,
+             body: File.read!("test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.xml"),
+             headers: [{"content-type", "application/xrd+xml"}]
+           }}
+
+        %{url: "http://pawoo.net/.well-known/host-meta"} ->
+          {:ok,
+           %Tesla.Env{
+             status: 200,
+             body: File.read!("test/fixtures/tesla_mock/pawoo.net_host_meta")
+           }}
+      end)
+
+      {:ok, _data} = WebFinger.finger("pekorino@pawoo.net")
+    end
   end
 end
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index 1328d6225..1e98020f0 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -122,7 +122,7 @@ def get(
      %Tesla.Env{
        status: 200,
        body: File.read!("test/fixtures/tesla_mock/mike@osada.macgirvin.com.json"),
-       headers: activitypub_object_headers()
+       headers: [{"content-type", "application/jrd+json"}]
      }}
   end
 
@@ -187,7 +187,8 @@ def get(
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/tesla_mock/lain_squeet.me_webfinger.xml")
+       body: File.read!("test/fixtures/tesla_mock/lain_squeet.me_webfinger.xml"),
+       headers: [{"content-type", "application/xrd+xml"}]
      }}
   end
 
@@ -526,22 +527,6 @@ def get(
      }}
   end
 
-  def get("http://zetsubou.xn--q9jyb4c/.well-known/host-meta", _, _, _) do
-    {:ok,
-     %Tesla.Env{
-       status: 200,
-       body: File.read!("test/fixtures/tesla_mock/xn--q9jyb4c_host_meta")
-     }}
-  end
-
-  def get("https://zetsubou.xn--q9jyb4c/.well-known/host-meta", _, _, _) do
-    {:ok,
-     %Tesla.Env{
-       status: 200,
-       body: File.read!("test/fixtures/tesla_mock/xn--q9jyb4c_host_meta")
-     }}
-  end
-
   def get("http://pleroma.soykaf.com/.well-known/host-meta", _, _, _) do
     {:ok,
      %Tesla.Env{
@@ -786,7 +771,8 @@ def get(
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/tesla_mock/shp@social.heldscal.la.xml")
+       body: File.read!("test/fixtures/tesla_mock/shp@social.heldscal.la.xml"),
+       headers: [{"content-type", "application/xrd+xml"}]
      }}
   end
 
@@ -796,7 +782,7 @@ def get(
         _,
         [{"accept", "application/xrd+xml,application/jrd+json"}]
       ) do
-    {:ok, %Tesla.Env{status: 200, body: ""}}
+    {:ok, %Tesla.Env{status: 200, body: "", headers: [{"content-type", "application/jrd+json"}]}}
   end
 
   def get("http://framatube.org/.well-known/host-meta", _, _, _) do
@@ -816,7 +802,7 @@ def get(
     {:ok,
      %Tesla.Env{
        status: 200,
-       headers: [{"content-type", "application/json"}],
+       headers: [{"content-type", "application/jrd+json"}],
        body: File.read!("test/fixtures/tesla_mock/framasoft@framatube.org.json")
      }}
   end
@@ -876,7 +862,7 @@ def get(
     {:ok,
      %Tesla.Env{
        status: 200,
-       headers: [{"content-type", "application/json"}],
+       headers: [{"content-type", "application/jrd+json"}],
        body: File.read!("test/fixtures/tesla_mock/kaniini@gerzilla.de.json")
      }}
   end
@@ -1074,7 +1060,8 @@ def get(
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/lain.xml")
+       body: File.read!("test/fixtures/lain.xml"),
+       headers: [{"content-type", "application/xrd+xml"}]
      }}
   end
 
@@ -1087,7 +1074,16 @@ def get(
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/lain.xml")
+       body: File.read!("test/fixtures/lain.xml"),
+       headers: [{"content-type", "application/xrd+xml"}]
+     }}
+  end
+
+  def get("http://zetsubou.xn--q9jyb4c/.well-known/host-meta", _, _, _) do
+    {:ok,
+     %Tesla.Env{
+       status: 200,
+       body: File.read!("test/fixtures/host-meta-zetsubou.xn--q9jyb4c.xml")
      }}
   end
 
@@ -1153,7 +1149,8 @@ def get("https://mstdn.jp/.well-known/webfinger?resource=acct:kpherox@mstdn.jp",
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/tesla_mock/kpherox@mstdn.jp.xml")
+       body: File.read!("test/fixtures/tesla_mock/kpherox@mstdn.jp.xml"),
+       headers: [{"content-type", "application/xrd+xml"}]
      }}
   end
 

From ef5b0510eb3e2c77c94fc5a6168180141a73361f Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Sat, 20 Mar 2021 08:29:02 +0300
Subject: [PATCH 091/339] updating timex

---
 mix.lock | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/mix.lock b/mix.lock
index 6034ce5a8..19b90660b 100644
--- a/mix.lock
+++ b/mix.lock
@@ -52,7 +52,7 @@
   "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.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"},
+  "gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
   "gun": {:git, "https://github.com/ninenines/gun.git", "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", [ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327"]},
   "hackney": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/hackney.git", "7d7119f0651515d6d7669c78393fd90950a3ec6e", [ref: "7d7119f0651515d6d7669c78393fd90950a3ec6e"]},
   "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
@@ -117,9 +117,9 @@
   "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
   "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
   "tesla": {:hex, :tesla, "1.4.0", "1081bef0124b8bdec1c3d330bbe91956648fb008cf0d3950a369cda466a31a87", [: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", "bf1374a5569f5fca8e641363b63f7347d680d91388880979a33bc12a6eb3e0aa"},
-  "timex": {:hex, :timex, "3.6.2", "845cdeb6119e2fef10751c0b247b6c59d86d78554c83f78db612e3290f819bc2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "26030b46199d02a590be61c2394b37ea25a3664c02fafbeca0b24c972025d47a"},
+  "timex": {:hex, :timex, "3.7.3", "df8a2ea814749d700d6878ab9eacac9fdb498ecee2f507cb0002ec172bc24d0f", [: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", "8691c1d86ca3a7bc14a156e2199dc8927be95d1a8f0e3b69e4bb2d6262c53ac6"},
   "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.4", "a3baa4709ea8dba552dca165af6ae97c624a2d6ac14bd265165eaa8e8af94af6", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "b02637db3df1fd66dd2d3c4f194a81633d0e4b44308d36c1b2fdfd1e4e6f169b"},
+  "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": {:git, "https://github.com/benoitc/unicode_util_compat.git", "38d7bc105f51159e8ea3279c40121db9db1e652f", [tag: "0.3.1"]},
   "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},

From d3660b24d37862bb58cf309c582cfe7432fd7bb6 Mon Sep 17 00:00:00 2001
From: rinpatch <rin@patch.cx>
Date: Mon, 22 Mar 2021 20:07:07 +0300
Subject: [PATCH 092/339] Copy emoji in the subject from parent post

Sometimes people put emoji in the subject, which results in the subject
looking broken if someone replies to it from a server that does not
have the said emoji under the same shortcode. This patch solves the problem
by extending the emoji set available in the summary to that of the parent
post.
---
 lib/pleroma/web/common_api/activity_draft.ex  | 27 ++++++++++
 .../fixtures/tesla_mock/emoji-in-summary.json | 49 +++++++++++++++++++
 test/pleroma/web/common_api_test.exs          | 26 ++++++++++
 test/support/http_request_mock.ex             |  9 ++++
 4 files changed, 111 insertions(+)
 create mode 100644 test/fixtures/tesla_mock/emoji-in-summary.json

diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
index 8668b600e..80a9fa7bb 100644
--- a/lib/pleroma/web/common_api/activity_draft.ex
+++ b/lib/pleroma/web/common_api/activity_draft.ex
@@ -5,6 +5,7 @@
 defmodule Pleroma.Web.CommonAPI.ActivityDraft do
   alias Pleroma.Activity
   alias Pleroma.Conversation.Participation
+  alias Pleroma.Object
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.CommonAPI.Utils
 
@@ -186,6 +187,32 @@ defp sensitive(draft) do
   defp object(draft) do
     emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji)
 
+    # Sometimes people create posts with subject containing emoji,
+    # since subjects are usually copied this will result in a broken
+    # subject when someone replies from an instance that does not have
+    # the emoji or has it under different shortcode. This is an attempt
+    # to mitigate this by copying emoji from inReplyTo if they are present
+    # in the subject.
+    summary_emoji =
+      with %Activity{} <- draft.in_reply_to,
+           %Object{data: %{"tag" => [_ | _] = tag}} <- Object.normalize(draft.in_reply_to) do
+        Enum.reduce(tag, %{}, fn
+          %{"type" => "Emoji", "name" => name, "icon" => %{"url" => url}}, acc ->
+            if String.contains?(draft.summary, name) do
+              Map.put(acc, name, url)
+            else
+              acc
+            end
+
+          _, acc ->
+            acc
+        end)
+      else
+        _ -> %{}
+      end
+
+    emoji = Map.merge(emoji, summary_emoji)
+
     object =
       Utils.make_note_data(draft)
       |> Map.put("emoji", emoji)
diff --git a/test/fixtures/tesla_mock/emoji-in-summary.json b/test/fixtures/tesla_mock/emoji-in-summary.json
new file mode 100644
index 000000000..f77c6e2e8
--- /dev/null
+++ b/test/fixtures/tesla_mock/emoji-in-summary.json
@@ -0,0 +1,49 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    "https://patch.cx/schemas/litepub-0.1.jsonld",
+    {
+      "@language": "und"
+    }
+  ],
+  "actor": "https://patch.cx/users/rin",
+  "attachment": [],
+  "attributedTo": "https://patch.cx/users/rin",
+  "cc": [
+    "https://patch.cx/users/rin/followers"
+  ],
+  "content": ":joker_disapprove: <br><br>just grabbing a test fixture, nevermind me",
+  "context": "https://patch.cx/contexts/2c3ce4b4-18b1-4b1a-8965-3932027b5326",
+  "conversation": "https://patch.cx/contexts/2c3ce4b4-18b1-4b1a-8965-3932027b5326",
+  "id": "https://patch.cx/objects/a399c28e-c821-4820-bc3e-4afeb044c16f",
+  "published": "2021-03-22T16:54:46.461939Z",
+  "sensitive": null,
+  "source": ":joker_disapprove: \r\n\r\njust grabbing a test fixture, nevermind me",
+  "summary": ":joker_smile: ",
+  "tag": [
+    {
+      "icon": {
+        "type": "Image",
+        "url": "https://patch.cx/emoji/custom/joker_disapprove.png"
+      },
+      "id": "https://patch.cx/emoji/custom/joker_disapprove.png",
+      "name": ":joker_disapprove:",
+      "type": "Emoji",
+      "updated": "1970-01-01T00:00:00Z"
+    },
+    {
+      "icon": {
+        "type": "Image",
+        "url": "https://patch.cx/emoji/custom/joker_smile.png"
+      },
+      "id": "https://patch.cx/emoji/custom/joker_smile.png",
+      "name": ":joker_smile:",
+      "type": "Emoji",
+      "updated": "1970-01-01T00:00:00Z"
+    }
+  ],
+  "to": [
+    "https://www.w3.org/ns/activitystreams#Public"
+  ],
+  "type": "Note"
+}
diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs
index 9d005697c..6619f8fc8 100644
--- a/test/pleroma/web/common_api_test.exs
+++ b/test/pleroma/web/common_api_test.exs
@@ -25,6 +25,11 @@ defmodule Pleroma.Web.CommonAPITest do
 
   require Pleroma.Constants
 
+  setup_all do
+    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+    :ok
+  end
+
   setup do: clear_config([:instance, :safe_dm_mentions])
   setup do: clear_config([:instance, :limit])
   setup do: clear_config([:instance, :max_pinned_statuses])
@@ -517,6 +522,27 @@ test "it adds an emoji on an external site" do
       assert url == "#{Pleroma.Web.base_url()}/emoji/blank.png"
     end
 
+    test "it copies emoji from the subject of the parent post" do
+      %Object{} =
+        object =
+        Object.normalize("https://patch.cx/objects/a399c28e-c821-4820-bc3e-4afeb044c16f",
+          fetch: true
+        )
+
+      activity = Activity.get_create_by_object_ap_id(object.data["id"])
+      user = insert(:user)
+
+      {:ok, reply_activity} =
+        CommonAPI.post(user, %{
+          in_reply_to_id: activity.id,
+          status: ":joker_disapprove:",
+          spoiler_text: ":joker_smile:"
+        })
+
+      assert Object.normalize(reply_activity).data["emoji"][":joker_smile:"]
+      refute Object.normalize(reply_activity).data["emoji"][":joker_disapprove:"]
+    end
+
     test "deactivated users can't post" do
       user = insert(:user, is_active: false)
       assert {:error, _} = CommonAPI.post(user, %{status: "ye"})
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index 1e98020f0..eb692fab5 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -1278,6 +1278,15 @@ def get("https://osada.macgirvin.com/", _, "", [{"accept", "text/html"}]) do
      }}
   end
 
+  def get("https://patch.cx/objects/a399c28e-c821-4820-bc3e-4afeb044c16f", _, _, _) do
+    {:ok,
+     %Tesla.Env{
+       status: 200,
+       body: File.read!("test/fixtures/tesla_mock/emoji-in-summary.json"),
+       headers: activitypub_object_headers()
+     }}
+  end
+
   def get(url, query, body, headers) do
     {:error,
      "Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{

From 03843a53868860c0b6b2bebcf262bde746482f7e Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Tue, 23 Mar 2021 14:23:37 +0300
Subject: [PATCH 093/339] migrating config to tmp folder

---
 docs/administration/CLI_tasks/config.md | 10 +++--
 lib/mix/tasks/pleroma/config.ex         | 49 ++++++++++++++++++-------
 test/mix/tasks/pleroma/config_test.exs  | 38 +++++++++++++++++++
 3 files changed, 80 insertions(+), 17 deletions(-)

diff --git a/docs/administration/CLI_tasks/config.md b/docs/administration/CLI_tasks/config.md
index 000ed4d98..fc9f3cbd5 100644
--- a/docs/administration/CLI_tasks/config.md
+++ b/docs/administration/CLI_tasks/config.md
@@ -32,16 +32,20 @@
     config :pleroma, configurable_from_database: false
     ```
 
-To delete transferred settings from database optional flag `-d` can be used. `<env>` is `prod` by default.
+Options:
+
+- `<path>` - where to save migrated config. E.g. `--path=/tmp`. If file saved into non standart folder, you must manually copy file into directory where Pleroma can read it. For OTP install path will be `PLEROMA_CONFIG_PATH` or `/etc/pleroma`. For installation from source - `config` directory in the pleroma folder.
+- `<env>` - environment, for which is migrated config. By default is `prod`.
+- To delete transferred settings from database optional flag `-d` can be used
 
 === "OTP"
     ```sh
-     ./bin/pleroma_ctl config migrate_from_db [--env=<env>] [-d]
+     ./bin/pleroma_ctl config migrate_from_db [--env=<env>] [-d] [--path=<path>]
     ```
 
 === "From Source"
     ```sh
-    mix pleroma.config migrate_from_db [--env=<env>] [-d]
+    mix pleroma.config migrate_from_db [--env=<env>] [-d] [--path=<path>]
     ```
 
 ## Dump all of the config settings defined in the database
diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex
index 1962154b9..ac89702ae 100644
--- a/lib/mix/tasks/pleroma/config.ex
+++ b/lib/mix/tasks/pleroma/config.ex
@@ -27,7 +27,7 @@ def run(["migrate_from_db" | options]) do
 
       {opts, _} =
         OptionParser.parse!(options,
-          strict: [env: :string, delete: :boolean],
+          strict: [env: :string, delete: :boolean, path: :string],
           aliases: [d: :delete]
         )
 
@@ -259,18 +259,43 @@ defp create(group, settings) do
   defp migrate_from_db(opts) do
     env = opts[:env] || Pleroma.Config.get(:env)
 
+    filename = "#{env}.exported_from_db.secret.exs"
+
     config_path =
-      if Pleroma.Config.get(:release) do
-        :config_path
-        |> Pleroma.Config.get()
-        |> Path.dirname()
-      else
-        "config"
+      cond do
+        opts[:path] ->
+          opts[:path]
+
+        Pleroma.Config.get(:release) ->
+          :config_path
+          |> Pleroma.Config.get()
+          |> Path.dirname()
+
+        true ->
+          "config"
       end
-      |> Path.join("#{env}.exported_from_db.secret.exs")
+      |> Path.join(filename)
 
-    file = File.open!(config_path, [:write, :utf8])
+    with {:ok, file} <- File.open(config_path, [:write, :utf8]) do
+      write_config(file, config_path, opts)
+      shell_info("Database configuration settings have been exported to #{config_path}")
+    else
+      _ ->
+        shell_error("Impossible to save settings to this directory #{Path.dirname(config_path)}")
+        tmp_config_path = Path.join("/tmp", filename)
+        file = File.open!(tmp_config_path)
 
+        shell_info(
+          "Saving database configuration settings to #{tmp_config_path}. Copy it to the #{
+            Path.dirname(config_path)
+          } manually."
+        )
+
+        write_config(file, tmp_config_path, opts)
+    end
+  end
+
+  defp write_config(file, path, opts) do
     IO.write(file, config_header())
 
     ConfigDB
@@ -278,11 +303,7 @@ defp migrate_from_db(opts) do
     |> Enum.each(&write_and_delete(&1, file, opts[:delete]))
 
     :ok = File.close(file)
-    System.cmd("mix", ["format", config_path])
-
-    shell_info(
-      "Database configuration settings have been exported to config/#{env}.exported_from_db.secret.exs"
-    )
+    System.cmd("mix", ["format", path])
   end
 
   if Code.ensure_loaded?(Config.Reader) do
diff --git a/test/mix/tasks/pleroma/config_test.exs b/test/mix/tasks/pleroma/config_test.exs
index 21f8f2286..3ed1e94b8 100644
--- a/test/mix/tasks/pleroma/config_test.exs
+++ b/test/mix/tasks/pleroma/config_test.exs
@@ -200,6 +200,44 @@ test "load a settings with large values and pass to file", %{temp_file: temp_fil
     end
   end
 
+  describe "migrate_from_db/1" do
+    setup do: clear_config(:configurable_from_database, true)
+
+    setup do
+      insert_config_record(:pleroma, :setting_first, key: "value", key2: ["Activity"])
+      insert_config_record(:pleroma, :setting_second, key: "value2", key2: [Repo])
+      insert_config_record(:quack, :level, :info)
+
+      path = "test/instance_static"
+      file_path = Path.join(path, "temp.exported_from_db.secret.exs")
+
+      on_exit(fn -> File.rm!(file_path) end)
+
+      [file_path: file_path]
+    end
+
+    test "with path parameter", %{file_path: file_path} do
+      MixTask.run(["migrate_from_db", "--env", "temp", "--path", Path.dirname(file_path)])
+
+      file = File.read!(file_path)
+      assert file =~ "config :pleroma, :setting_first,"
+      assert file =~ "config :pleroma, :setting_second,"
+      assert file =~ "config :quack, :level, :info"
+    end
+
+    test "release", %{file_path: file_path} do
+      clear_config(:release, true)
+      clear_config(:config_path, file_path)
+
+      MixTask.run(["migrate_from_db", "--env", "temp"])
+
+      file = File.read!(file_path)
+      assert file =~ "config :pleroma, :setting_first,"
+      assert file =~ "config :pleroma, :setting_second,"
+      assert file =~ "config :quack, :level, :info"
+    end
+  end
+
   describe "operations on database config" do
     setup do: clear_config(:configurable_from_database, true)
 

From 4cd34d019764fdd68829ebd4282118abc4534133 Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Tue, 23 Mar 2021 17:27:02 +0300
Subject: [PATCH 094/339] suggestion

---
 lib/mix/tasks/pleroma/config.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex
index ac89702ae..22502a522 100644
--- a/lib/mix/tasks/pleroma/config.ex
+++ b/lib/mix/tasks/pleroma/config.ex
@@ -282,7 +282,7 @@ defp migrate_from_db(opts) do
     else
       _ ->
         shell_error("Impossible to save settings to this directory #{Path.dirname(config_path)}")
-        tmp_config_path = Path.join("/tmp", filename)
+        tmp_config_path = Path.join(System.tmp_dir!(), filename)
         file = File.open!(tmp_config_path)
 
         shell_info(

From ad907254fb47764869fecd5928bd863182421c8c Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Tue, 23 Mar 2021 19:37:25 +0300
Subject: [PATCH 095/339] changelog entry

---
 CHANGELOG.md | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a1fa22398..fb26c7a73 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## Unreleased (Patch)
 
+### Fixed
+
+- Try to save exported ConfigDB settings (migrate_from_db) in the system temp directory if default location is not writable.
+
 ## [2.3.0] - 2020-03-01
 
 ### Security
@@ -51,7 +55,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Pleroma API: Reroute `/api/pleroma/*` to `/api/v1/pleroma/*`
 
 </details>
-- Improved hashtag timeline performance (requires a background migration). 
+- Improved hashtag timeline performance (requires a background migration).
 
 ### Added
 

From b6a69b5efda5f75ad716252c69ae658a4e885b0a Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Wed, 24 Mar 2021 12:50:05 -0500
Subject: [PATCH 096/339] Return token's primary key with POST /oauth/token

---
 .../API/differences_in_mastoapi_responses.md  | 24 +++++++++++++++++--
 lib/pleroma/web/o_auth/o_auth_view.ex         |  1 +
 .../web/o_auth/o_auth_controller_test.exs     |  6 +++--
 3 files changed, 27 insertions(+), 4 deletions(-)

diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md
index a14fcb416..3552b12fb 100644
--- a/docs/development/API/differences_in_mastoapi_responses.md
+++ b/docs/development/API/differences_in_mastoapi_responses.md
@@ -255,9 +255,29 @@ This information is returned in the `/api/v1/accounts/verify_credentials` endpoi
 
 *Pleroma supports refreshing tokens.*
 
-`POST /oauth/token`
+### POST `/oauth/token`
 
-Post here request with `grant_type=refresh_token` to obtain new access token. Returns an access token.
+You can obtain access tokens for a user in a few additional ways.
+
+#### Refreshing a token
+
+To obtain a new access token from a refresh token, pass `grant_type=refresh_token` with the following extra parameters:
+
+- `refresh_token`: The refresh token.
+
+#### Getting a token with a password
+
+To obtain a token from a user's password, pass `grant_type=password` with the following extra parameters:
+
+- `username`: Username to authenticate.
+- `password`: The user's password.
+
+#### Response body
+
+Additional fields are returned in the response:
+
+- `id`: The primary key of this token in Pleroma's database.
+- `me` (user tokens only): The ActivityPub ID of the user who owns the token.
 
 ## Account Registration
 
diff --git a/lib/pleroma/web/o_auth/o_auth_view.ex b/lib/pleroma/web/o_auth/o_auth_view.ex
index 281bbcc3c..1419c96a2 100644
--- a/lib/pleroma/web/o_auth/o_auth_view.ex
+++ b/lib/pleroma/web/o_auth/o_auth_view.ex
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.OAuth.OAuthView do
 
   def render("token.json", %{token: token} = opts) do
     response = %{
+      id: token.id,
       token_type: "Bearer",
       access_token: token.token,
       refresh_token: token.refresh_token,
diff --git a/test/pleroma/web/o_auth/o_auth_controller_test.exs b/test/pleroma/web/o_auth/o_auth_controller_test.exs
index 312500feb..0fdd5b8e9 100644
--- a/test/pleroma/web/o_auth/o_auth_controller_test.exs
+++ b/test/pleroma/web/o_auth/o_auth_controller_test.exs
@@ -805,10 +805,12 @@ test "issues a token for `password` grant_type with valid credentials, with full
           "client_secret" => app.client_secret
         })
 
-      assert %{"access_token" => token} = json_response(conn, 200)
+      assert %{"id" => id, "access_token" => access_token} = json_response(conn, 200)
 
-      token = Repo.get_by(Token, token: token)
+      token = Repo.get_by(Token, token: access_token)
       assert token
+      assert token.id == id
+      assert token.token == access_token
       assert token.scopes == app.scopes
     end
 

From 3ec1dbd9223aa44205e90967175f07cc532501ab Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Wed, 3 Feb 2021 16:09:28 +0300
Subject: [PATCH 097/339] Let pins federate

- save object ids on pin, instead of activity ids
- pins federation
- removed pinned_activities field from the users table
- activityPub endpoint for user pins
- pulling remote users pins
---
 .../API/differences_in_mastoapi_responses.md  |   1 +
 lib/pleroma/activity.ex                       |  79 +++++++------
 lib/pleroma/activity/queries.ex               |   5 +
 lib/pleroma/user.ex                           |  77 +++++++------
 lib/pleroma/web/activity_pub/activity_pub.ex  |  60 +++++++++-
 .../activity_pub/activity_pub_controller.ex   |   8 ++
 lib/pleroma/web/activity_pub/builder.ex       |  32 ++++++
 .../web/activity_pub/object_validator.ex      |  11 ++
 .../object_validators/pin_validator.ex        |  42 +++++++
 lib/pleroma/web/activity_pub/side_effects.ex  |  56 +++++++++-
 .../web/activity_pub/transmogrifier.ex        |   9 ++
 .../web/activity_pub/views/user_view.ex       |  21 ++++
 .../api_spec/operations/status_operation.ex   |  46 +++++++-
 lib/pleroma/web/api_spec/schemas/status.ex    |   7 ++
 lib/pleroma/web/common_api.ex                 |  57 +++++++---
 .../controllers/fallback_controller.ex        |   6 +
 .../controllers/status_controller.ex          |  12 ++
 .../web/mastodon_api/views/status_view.ex     |  23 +++-
 lib/pleroma/web/router.ex                     |   1 +
 ...0202110641_add_pinned_objects_to_users.exs |   9 ++
 ...03141144_add_featured_address_to_users.exs |  23 ++++
 ..._pinned_activities_into_pinned_objects.exs |  28 +++++
 ...21_remove_pinned_activities_from_users.exs |  15 +++
 test/fixtures/collections/featured.json       |  39 +++++++
 test/fixtures/masto_pin.json                  |  41 +++++++
 test/fixtures/statuses/note.json              |  27 +++++
 test/fixtures/users_mock/masto_featured.json  |  18 +++
 test/fixtures/users_mock/user.json            |  41 +++++++
 test/pleroma/user_test.exs                    |  45 ++++++++
 .../activity_pub_controller_test.exs          | 105 ++++++++++++++++++
 .../web/activity_pub/activity_pub_test.exs    |  77 +++++++++++++
 .../web/activity_pub/transmogrifier_test.exs  |  74 ++++++++++++
 test/pleroma/web/common_api_test.exs          |  60 ++++++++--
 .../controllers/status_controller_test.exs    |  32 ++++--
 .../mastodon_api/views/status_view_test.exs   |   3 +-
 .../remote_follow_controller_test.exs         |  30 +++++
 test/support/factory.ex                       |   6 +-
 test/support/http_request_mock.ex             |  23 ++++
 38 files changed, 1127 insertions(+), 122 deletions(-)
 create mode 100644 lib/pleroma/web/activity_pub/object_validators/pin_validator.ex
 create mode 100644 priv/repo/migrations/20210202110641_add_pinned_objects_to_users.exs
 create mode 100644 priv/repo/migrations/20210203141144_add_featured_address_to_users.exs
 create mode 100644 priv/repo/migrations/20210205145000_move_pinned_activities_into_pinned_objects.exs
 create mode 100644 priv/repo/migrations/20210206045221_remove_pinned_activities_from_users.exs
 create mode 100644 test/fixtures/collections/featured.json
 create mode 100644 test/fixtures/masto_pin.json
 create mode 100644 test/fixtures/statuses/note.json
 create mode 100644 test/fixtures/users_mock/masto_featured.json
 create mode 100644 test/fixtures/users_mock/user.json

diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md
index a14fcb416..2ff56d3ca 100644
--- a/docs/development/API/differences_in_mastoapi_responses.md
+++ b/docs/development/API/differences_in_mastoapi_responses.md
@@ -38,6 +38,7 @@ Has these additional fields under the `pleroma` object:
 - `thread_muted`: true if the thread the post belongs to is muted
 - `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
 - `parent_visible`: If the parent of this post is visible to the user or not.
+- `pinned_at`: a datetime (iso8601) when status was pinned, `null` otherwise.
 
 ## Scheduled statuses
 
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index d59403884..a4cfca4c5 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -184,40 +184,48 @@ def get_by_ap_id_with_object(ap_id) do
     |> Repo.one()
   end
 
-  @spec get_by_id(String.t()) :: Activity.t() | nil
-  def get_by_id(id) do
-    case FlakeId.flake_id?(id) do
-      true ->
-        Activity
-        |> where([a], a.id == ^id)
-        |> restrict_deactivated_users()
-        |> Repo.one()
+  @doc """
+  Gets activity by ID, doesn't load activities from deactivated actors by default.
+  """
+  @spec get_by_id(String.t(), keyword()) :: t() | nil
+  def get_by_id(id, opts \\ [filter: [:restrict_deactivated]]), do: get_by_id_with_opts(id, opts)
 
-      _ ->
-        nil
+  @spec get_by_id_with_user_actor(String.t()) :: t() | nil
+  def get_by_id_with_user_actor(id), do: get_by_id_with_opts(id, preload: [:user_actor])
+
+  @spec get_by_id_with_object(String.t()) :: t() | nil
+  def get_by_id_with_object(id), do: get_by_id_with_opts(id, preload: [:object])
+
+  defp get_by_id_with_opts(id, opts) do
+    if FlakeId.flake_id?(id) do
+      query = Queries.by_id(id)
+
+      with_filters_query =
+        if is_list(opts[:filter]) do
+          Enum.reduce(opts[:filter], query, fn
+            {:type, type}, acc -> Queries.by_type(acc, type)
+            :restrict_deactivated, acc -> restrict_deactivated_users(acc)
+            _, acc -> acc
+          end)
+        else
+          query
+        end
+
+      with_preloads_query =
+        if is_list(opts[:preload]) do
+          Enum.reduce(opts[:preload], with_filters_query, fn
+            :user_actor, acc -> with_preloaded_user_actor(acc)
+            :object, acc -> with_preloaded_object(acc)
+            _, acc -> acc
+          end)
+        else
+          with_filters_query
+        end
+
+      Repo.one(with_preloads_query)
     end
   end
 
-  def get_by_id_with_user_actor(id) do
-    case FlakeId.flake_id?(id) do
-      true ->
-        Activity
-        |> where([a], a.id == ^id)
-        |> with_preloaded_user_actor()
-        |> Repo.one()
-
-      _ ->
-        nil
-    end
-  end
-
-  def get_by_id_with_object(id) do
-    Activity
-    |> where(id: ^id)
-    |> with_preloaded_object()
-    |> Repo.one()
-  end
-
   def all_by_ids_with_object(ids) do
     Activity
     |> where([a], a.id in ^ids)
@@ -269,6 +277,11 @@ def get_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
 
   def get_create_by_object_ap_id_with_object(_), do: nil
 
+  @spec create_by_id_with_object(String.t()) :: t() | nil
+  def create_by_id_with_object(id) do
+    get_by_id_with_opts(id, preload: [:object], filter: [type: "Create"])
+  end
+
   defp get_in_reply_to_activity_from_object(%Object{data: %{"inReplyTo" => ap_id}}) do
     get_create_by_object_ap_id_with_object(ap_id)
   end
@@ -368,12 +381,6 @@ def direct_conversation_id(activity, for_user) do
     end
   end
 
-  @spec pinned_by_actor?(Activity.t()) :: boolean()
-  def pinned_by_actor?(%Activity{} = activity) do
-    actor = user_actor(activity)
-    activity.id in actor.pinned_activities
-  end
-
   @spec get_by_object_ap_id_with_object(String.t()) :: t() | nil
   def get_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
     ap_id
diff --git a/lib/pleroma/activity/queries.ex b/lib/pleroma/activity/queries.ex
index a6b02a889..4632651b0 100644
--- a/lib/pleroma/activity/queries.ex
+++ b/lib/pleroma/activity/queries.ex
@@ -14,6 +14,11 @@ defmodule Pleroma.Activity.Queries do
   alias Pleroma.Activity
   alias Pleroma.User
 
+  @spec by_id(query(), String.t()) :: query()
+  def by_id(query \\ Activity, id) do
+    from(a in query, where: a.id == ^id)
+  end
+
   @spec by_ap_id(query, String.t()) :: query
   def by_ap_id(query \\ Activity, ap_id) do
     from(
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index c1aa0f716..b78777141 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -99,6 +99,7 @@ defmodule Pleroma.User do
     field(:local, :boolean, default: true)
     field(:follower_address, :string)
     field(:following_address, :string)
+    field(:featured_address, :string)
     field(:search_rank, :float, virtual: true)
     field(:search_type, :integer, virtual: true)
     field(:tags, {:array, :string}, default: [])
@@ -130,7 +131,6 @@ defmodule Pleroma.User do
     field(:hide_followers, :boolean, default: false)
     field(:hide_follows, :boolean, default: false)
     field(:hide_favorites, :boolean, default: true)
-    field(:pinned_activities, {:array, :string}, default: [])
     field(:email_notifications, :map, default: %{"digest" => false})
     field(:mascot, :map, default: nil)
     field(:emoji, :map, default: %{})
@@ -148,6 +148,7 @@ defmodule Pleroma.User do
     field(:accepts_chat_messages, :boolean, default: nil)
     field(:last_active_at, :naive_datetime)
     field(:disclose_client, :boolean, default: true)
+    field(:pinned_objects, :map, default: %{})
 
     embeds_one(
       :notification_settings,
@@ -372,8 +373,10 @@ def banner_url(user, options \\ []) do
   end
 
   # Should probably be renamed or removed
+  @spec ap_id(User.t()) :: String.t()
   def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
 
+  @spec ap_followers(User.t()) :: String.t()
   def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
   def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
 
@@ -381,6 +384,11 @@ def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
   def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
   def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
 
+  @spec ap_featured_collection(User.t()) :: String.t()
+  def ap_featured_collection(%User{featured_address: fa}) when is_binary(fa), do: fa
+
+  def ap_featured_collection(%User{} = user), do: "#{ap_id(user)}/collections/featured"
+
   defp truncate_fields_param(params) do
     if Map.has_key?(params, :fields) do
       Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
@@ -443,6 +451,7 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
         :uri,
         :follower_address,
         :following_address,
+        :featured_address,
         :hide_followers,
         :hide_follows,
         :hide_followers_count,
@@ -454,7 +463,8 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
         :invisible,
         :actor_type,
         :also_known_as,
-        :accepts_chat_messages
+        :accepts_chat_messages,
+        :pinned_objects
       ]
     )
     |> cast(params, [:name], empty_values: [])
@@ -686,7 +696,7 @@ def register_changeset_ldap(struct, params = %{password: password})
     |> validate_format(:nickname, local_nickname_regex())
     |> put_ap_id()
     |> unique_constraint(:ap_id)
-    |> put_following_and_follower_address()
+    |> put_following_and_follower_and_featured_address()
   end
 
   def register_changeset(struct, params \\ %{}, opts \\ []) do
@@ -747,7 +757,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
     |> put_password_hash
     |> put_ap_id()
     |> unique_constraint(:ap_id)
-    |> put_following_and_follower_address()
+    |> put_following_and_follower_and_featured_address()
   end
 
   def maybe_validate_required_email(changeset, true), do: changeset
@@ -765,11 +775,16 @@ defp put_ap_id(changeset) do
     put_change(changeset, :ap_id, ap_id)
   end
 
-  defp put_following_and_follower_address(changeset) do
-    followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
+  defp put_following_and_follower_and_featured_address(changeset) do
+    user = %User{nickname: get_field(changeset, :nickname)}
+    followers = ap_followers(user)
+    following = ap_following(user)
+    featured = ap_featured_collection(user)
 
     changeset
     |> put_change(:follower_address, followers)
+    |> put_change(:following_address, following)
+    |> put_change(:featured_address, featured)
   end
 
   defp autofollow_users(user) do
@@ -2343,45 +2358,35 @@ def approval_changeset(user, set_approval: approved?) do
     cast(user, %{is_approved: approved?}, [:is_approved])
   end
 
-  def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
-    if id not in user.pinned_activities do
-      max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
-      params = %{pinned_activities: user.pinned_activities ++ [id]}
-
-      # if pinned activity was scheduled for deletion, we remove job
-      if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(id) do
-        Oban.cancel_job(expiration.id)
-      end
+  @spec add_pinned_object_id(User.t(), String.t()) :: {:ok, User.t()} | {:error, term()}
+  def add_pinned_object_id(%User{} = user, object_id) do
+    if !user.pinned_objects[object_id] do
+      params = %{pinned_objects: Map.put(user.pinned_objects, object_id, NaiveDateTime.utc_now())}
 
       user
-      |> cast(params, [:pinned_activities])
-      |> validate_length(:pinned_activities,
-        max: max_pinned_statuses,
-        message: "You have already pinned the maximum number of statuses"
-      )
+      |> cast(params, [:pinned_objects])
+      |> validate_change(:pinned_objects, fn :pinned_objects, pinned_objects ->
+        max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
+
+        if Enum.count(pinned_objects) <= max_pinned_statuses do
+          []
+        else
+          [pinned_objects: "You have already pinned the maximum number of statuses"]
+        end
+      end)
     else
       change(user)
     end
     |> update_and_set_cache()
   end
 
-  def remove_pinnned_activity(user, %Pleroma.Activity{id: id, data: data}) do
-    params = %{pinned_activities: List.delete(user.pinned_activities, id)}
-
-    # if pinned activity was scheduled for deletion, we reschedule it for deletion
-    if data["expires_at"] do
-      # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation
-      {:ok, expires_at} =
-        data["expires_at"] |> Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast()
-
-      Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
-        activity_id: id,
-        expires_at: expires_at
-      })
-    end
-
+  @spec remove_pinned_object_id(User.t(), String.t()) :: {:ok, t()} | {:error, term()}
+  def remove_pinned_object_id(%User{} = user, object_id) do
     user
-    |> cast(params, [:pinned_activities])
+    |> cast(
+      %{pinned_objects: Map.delete(user.pinned_objects, object_id)},
+      [:pinned_objects]
+    )
     |> update_and_set_cache()
   end
 
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index efbf92c70..d0051d1cb 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -630,7 +630,7 @@ defp fetch_activities_for_user(user, reading_user, params) do
       |> Map.put(:type, ["Create", "Announce"])
       |> Map.put(:user, reading_user)
       |> Map.put(:actor_id, user.ap_id)
-      |> Map.put(:pinned_activity_ids, user.pinned_activities)
+      |> Map.put(:pinned_object_ids, Map.keys(user.pinned_objects))
 
     params =
       if User.blocks?(reading_user, user) do
@@ -1075,8 +1075,18 @@ defp restrict_unlisted(query, %{restrict_unlisted: true}) do
 
   defp restrict_unlisted(query, _), do: query
 
-  defp restrict_pinned(query, %{pinned: true, pinned_activity_ids: ids}) do
-    from(activity in query, where: activity.id in ^ids)
+  defp restrict_pinned(query, %{pinned: true, pinned_object_ids: ids}) do
+    from(
+      [activity, object: o] in query,
+      where:
+        fragment(
+          "(?)->>'type' = 'Create' and coalesce((?)->'object'->>'id', (?)->>'object') = any (?)",
+          activity.data,
+          activity.data,
+          activity.data,
+          ^ids
+        )
+    )
   end
 
   defp restrict_pinned(query, _), do: query
@@ -1419,6 +1429,9 @@ defp object_to_user_data(data) do
     invisible = data["invisible"] || false
     actor_type = data["type"] || "Person"
 
+    featured_address = data["featured"]
+    {:ok, pinned_objects} = fetch_and_prepare_featured_from_ap_id(featured_address)
+
     public_key =
       if is_map(data["publicKey"]) && is_binary(data["publicKey"]["publicKeyPem"]) do
         data["publicKey"]["publicKeyPem"]
@@ -1447,13 +1460,15 @@ defp object_to_user_data(data) do
       name: data["name"],
       follower_address: data["followers"],
       following_address: data["following"],
+      featured_address: featured_address,
       bio: data["summary"] || "",
       actor_type: actor_type,
       also_known_as: Map.get(data, "alsoKnownAs", []),
       public_key: public_key,
       inbox: data["inbox"],
       shared_inbox: shared_inbox,
-      accepts_chat_messages: accepts_chat_messages
+      accepts_chat_messages: accepts_chat_messages,
+      pinned_objects: pinned_objects
     }
 
     # nickname can be nil because of virtual actors
@@ -1591,6 +1606,41 @@ def maybe_handle_clashing_nickname(data) do
     end
   end
 
+  def pin_data_from_featured_collection(%{
+        "type" => type,
+        "orderedItems" => objects
+      })
+      when type in ["OrderedCollection", "Collection"] do
+    Map.new(objects, fn %{"id" => object_ap_id} -> {object_ap_id, NaiveDateTime.utc_now()} end)
+  end
+
+  def fetch_and_prepare_featured_from_ap_id(nil) do
+    {:ok, %{}}
+  end
+
+  def fetch_and_prepare_featured_from_ap_id(ap_id) do
+    with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do
+      {:ok, pin_data_from_featured_collection(data)}
+    else
+      e ->
+        Logger.error("Could not decode featured collection at fetch #{ap_id}, #{inspect(e)}")
+        {:ok, %{}}
+    end
+  end
+
+  def pinned_fetch_task(nil), do: nil
+
+  def pinned_fetch_task(%{pinned_objects: pins}) do
+    if Enum.all?(pins, fn {ap_id, _} ->
+         Object.get_cached_by_ap_id(ap_id) ||
+           match?({:ok, _object}, Fetcher.fetch_object_from_id(ap_id))
+       end) do
+      :ok
+    else
+      :error
+    end
+  end
+
   def make_user_from_ap_id(ap_id) do
     user = User.get_cached_by_ap_id(ap_id)
 
@@ -1598,6 +1648,8 @@ def make_user_from_ap_id(ap_id) do
       Transmogrifier.upgrade_user_from_ap_id(ap_id)
     else
       with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
+        {:ok, _pid} = Task.start(fn -> pinned_fetch_task(data) end)
+
         if user do
           user
           |> User.remote_user_changeset(data)
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 9d3dcc7f9..5aa3b281a 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -543,4 +543,12 @@ def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} =
       |> json(object.data)
     end
   end
+
+  def pinned(conn, %{"nickname" => nickname}) do
+    with %User{} = user <- User.get_cached_by_nickname(nickname) do
+      conn
+      |> put_resp_header("content-type", "application/activity+json")
+      |> json(UserView.render("featured.json", %{user: user}))
+    end
+  end
 end
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index f56bfc600..91a45836f 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -273,4 +273,36 @@ defp object_action(actor, object) do
        "context" => object.data["context"]
      }, []}
   end
+
+  @spec pin(User.t(), Object.t()) :: {:ok, map(), keyword()}
+  def pin(%User{} = user, object) do
+    {:ok,
+     %{
+       "id" => Utils.generate_activity_id(),
+       "target" => pinned_url(user.nickname),
+       "object" => object.data["id"],
+       "actor" => user.ap_id,
+       "type" => "Add",
+       "to" => [Pleroma.Constants.as_public()],
+       "cc" => [user.follower_address]
+     }, []}
+  end
+
+  @spec unpin(User.t(), Object.t()) :: {:ok, map, keyword()}
+  def unpin(%User{} = user, object) do
+    {:ok,
+     %{
+       "id" => Utils.generate_activity_id(),
+       "target" => pinned_url(user.nickname),
+       "object" => object.data["id"],
+       "actor" => user.ap_id,
+       "type" => "Remove",
+       "to" => [Pleroma.Constants.as_public()],
+       "cc" => [user.follower_address]
+     }, []}
+  end
+
+  defp pinned_url(nickname) when is_binary(nickname) do
+    Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname)
+  end
 end
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 297c19cc0..11432ef38 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -30,6 +30,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
   alias Pleroma.Web.ActivityPub.ObjectValidators.EventValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
+  alias Pleroma.Web.ActivityPub.ObjectValidators.PinValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
@@ -234,6 +235,16 @@ def validate(%{"type" => "Announce"} = object, meta) do
     end
   end
 
+  def validate(%{"type" => type} = object, meta) when type in ~w(Add Remove) do
+    with {:ok, object} <-
+           object
+           |> PinValidator.cast_and_validate()
+           |> Ecto.Changeset.apply_action(:insert) do
+      object = stringify_keys(object)
+      {:ok, object, meta}
+    end
+  end
+
   def cast_and_apply(%{"type" => "ChatMessage"} = object) do
     ChatMessageValidator.cast_and_apply(object)
   end
diff --git a/lib/pleroma/web/activity_pub/object_validators/pin_validator.ex b/lib/pleroma/web/activity_pub/object_validators/pin_validator.ex
new file mode 100644
index 000000000..dca8cba6f
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/pin_validator.ex
@@ -0,0 +1,42 @@
+# 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.PinValidator do
+  use Ecto.Schema
+
+  import Ecto.Changeset
+  import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+  alias Pleroma.EctoType.ActivityPub.ObjectValidators
+
+  @primary_key false
+
+  embedded_schema do
+    field(:id, ObjectValidators.ObjectID, primary_key: true)
+    field(:target)
+    field(:object, ObjectValidators.ObjectID)
+    field(:actor, ObjectValidators.ObjectID)
+    field(:type)
+    field(:to, ObjectValidators.Recipients, default: [])
+    field(:cc, ObjectValidators.Recipients, default: [])
+  end
+
+  def cast_and_validate(data) do
+    data
+    |> cast_data()
+    |> validate_data()
+  end
+
+  defp cast_data(data) do
+    cast(%__MODULE__{}, data, __schema__(:fields))
+  end
+
+  defp validate_data(changeset) do
+    changeset
+    |> validate_required([:id, :target, :object, :actor, :type, :to, :cc])
+    |> validate_inclusion(:type, ~w(Add Remove))
+    |> validate_actor_presence()
+    |> validate_object_presence()
+  end
+end
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 0b9a9f0c5..9d22f9d3c 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -276,10 +276,10 @@ def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object,
     result =
       case deleted_object do
         %Object{} ->
-          with {:ok, deleted_object, activity} <- Object.delete(deleted_object),
+          with {:ok, deleted_object, _activity} <- Object.delete(deleted_object),
                {_, actor} when is_binary(actor) <- {:actor, deleted_object.data["actor"]},
                %User{} = user <- User.get_cached_by_ap_id(actor) do
-            User.remove_pinnned_activity(user, activity)
+            User.remove_pinned_object_id(user, deleted_object.data["id"])
 
             {:ok, user} = ActivityPub.decrease_note_count_if_public(user, deleted_object)
 
@@ -312,6 +312,58 @@ def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object,
     end
   end
 
+  # Tasks this handles:
+  # - adds pin to user
+  # - removes expiration job for pinned activity, if was set for expiration
+  @impl true
+  def handle(%{data: %{"type" => "Add"} = data} = object, meta) do
+    with %User{} = user <- User.get_cached_by_ap_id(data["actor"]),
+         {:ok, _user} <- User.add_pinned_object_id(user, data["object"]) do
+      # if pinned activity was scheduled for deletion, we remove job
+      if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(meta[:activity_id]) do
+        Oban.cancel_job(expiration.id)
+      end
+
+      {:ok, object, meta}
+    else
+      nil ->
+        {:error, :user_not_found}
+
+      {:error, changeset} ->
+        if changeset.errors[:pinned_objects] do
+          {:error, :pinned_statuses_limit_reached}
+        else
+          changeset.errors
+        end
+    end
+  end
+
+  # Tasks this handles:
+  # - removes pin from user
+  # - if activity had expiration, recreates activity expiration job
+  @impl true
+  def handle(%{data: %{"type" => "Remove"} = data} = object, meta) do
+    with %User{} = user <- User.get_cached_by_ap_id(data["actor"]),
+         {:ok, _user} <- User.remove_pinned_object_id(user, data["object"]) do
+      # if pinned activity was scheduled for deletion, we reschedule it for deletion
+      if meta[:expires_at] do
+        # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation
+        {:ok, expires_at} =
+          Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast(meta[:expires_at])
+
+        Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
+          activity_id: meta[:activity_id],
+          expires_at: expires_at
+        })
+      end
+
+      {:ok, object, meta}
+    else
+      nil -> {:error, :user_not_found}
+      error -> error
+    end
+  end
+
   # Nothing to do
   @impl true
   def handle(object, meta) do
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 8c7d6a747..270cea6dc 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -556,6 +556,14 @@ def handle_incoming(
     end
   end
 
+  def handle_incoming(%{"type" => type} = data, _options) when type in ~w(Add Remove) do
+    with :ok <- ObjectValidator.fetch_actor_and_object(data),
+         %Object{} <- Object.normalize(data["object"], fetch: true),
+         {:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do
+      {:ok, activity}
+    end
+  end
+
   def handle_incoming(
         %{"type" => "Delete"} = data,
         _options
@@ -1000,6 +1008,7 @@ def upgrade_user_from_ap_id(ap_id) do
     with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),
          {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
          {:ok, user} <- update_user(user, data) do
+      {:ok, _pid} = Task.start(fn -> ActivityPub.pinned_fetch_task(user) end)
       TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
       {:ok, user}
     else
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 8adc9878a..462f3b4a7 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -6,8 +6,10 @@ defmodule Pleroma.Web.ActivityPub.UserView do
   use Pleroma.Web, :view
 
   alias Pleroma.Keys
+  alias Pleroma.Object
   alias Pleroma.Repo
   alias Pleroma.User
+  alias Pleroma.Web.ActivityPub.ObjectView
   alias Pleroma.Web.ActivityPub.Transmogrifier
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.Endpoint
@@ -97,6 +99,7 @@ def render("user.json", %{user: user}) do
       "followers" => "#{user.ap_id}/followers",
       "inbox" => "#{user.ap_id}/inbox",
       "outbox" => "#{user.ap_id}/outbox",
+      "featured" => "#{user.ap_id}/collections/featured",
       "preferredUsername" => user.nickname,
       "name" => user.name,
       "summary" => user.bio,
@@ -245,6 +248,24 @@ def render("activity_collection_page.json", %{
     |> Map.merge(pagination)
   end
 
+  def render("featured.json", %{
+        user: %{featured_address: featured_address, pinned_objects: pinned_objects}
+      }) do
+    objects =
+      pinned_objects
+      |> Enum.sort_by(fn {_, pinned_at} -> pinned_at end, &>=/2)
+      |> Enum.map(fn {id, _} ->
+        ObjectView.render("object.json", %{object: Object.get_cached_by_ap_id(id)})
+      end)
+
+    %{
+      "id" => featured_address,
+      "type" => "OrderedCollection",
+      "orderedItems" => objects
+    }
+    |> Map.merge(Utils.make_json_ld_header())
+  end
+
   defp maybe_put_total_items(map, false, _total), do: map
 
   defp maybe_put_total_items(map, true, total) do
diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex
index 4bdb8e281..802fbef3e 100644
--- a/lib/pleroma/web/api_spec/operations/status_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/status_operation.ex
@@ -182,7 +182,34 @@ def pin_operation do
       parameters: [id_param()],
       responses: %{
         200 => status_response(),
-        400 => Operation.response("Error", "application/json", ApiError)
+        400 =>
+          Operation.response("Bad Request", "application/json", %Schema{
+            allOf: [ApiError],
+            title: "Unprocessable Entity",
+            example: %{
+              "error" => "You have already pinned the maximum number of statuses"
+            }
+          }),
+        404 =>
+          Operation.response("Not found", "application/json", %Schema{
+            allOf: [ApiError],
+            title: "Unprocessable Entity",
+            example: %{
+              "error" => "Record not found"
+            }
+          }),
+        422 =>
+          Operation.response(
+            "Unprocessable Entity",
+            "application/json",
+            %Schema{
+              allOf: [ApiError],
+              title: "Unprocessable Entity",
+              example: %{
+                "error" => "Someone else's status cannot be pinned"
+              }
+            }
+          )
       }
     }
   end
@@ -197,7 +224,22 @@ def unpin_operation do
       parameters: [id_param()],
       responses: %{
         200 => status_response(),
-        400 => Operation.response("Error", "application/json", ApiError)
+        400 =>
+          Operation.response("Bad Request", "application/json", %Schema{
+            allOf: [ApiError],
+            title: "Unprocessable Entity",
+            example: %{
+              "error" => "You have already pinned the maximum number of statuses"
+            }
+          }),
+        404 =>
+          Operation.response("Not found", "application/json", %Schema{
+            allOf: [ApiError],
+            title: "Unprocessable Entity",
+            example: %{
+              "error" => "Record not found"
+            }
+          })
       }
     }
   end
diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex
index 42fa98718..3d042dc19 100644
--- a/lib/pleroma/web/api_spec/schemas/status.ex
+++ b/lib/pleroma/web/api_spec/schemas/status.ex
@@ -194,6 +194,13 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
           parent_visible: %Schema{
             type: :boolean,
             description: "`true` if the parent post is visible to the user"
+          },
+          pinned_at: %Schema{
+            type: :string,
+            format: "date-time",
+            nullable: true,
+            description:
+              "A datetime (ISO 8601) that states when the post was pinned or `null` if the post is not pinned"
           }
         }
       },
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
index b003e30c7..d35a0f219 100644
--- a/lib/pleroma/web/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -411,29 +411,54 @@ def post(user, %{status: _} = data) do
     end
   end
 
-  def pin(id, %{ap_id: user_ap_id} = user) do
-    with %Activity{
-           actor: ^user_ap_id,
-           data: %{"type" => "Create"},
-           object: %Object{data: %{"type" => object_type}}
-         } = activity <- Activity.get_by_id_with_object(id),
-         true <- object_type in ["Note", "Article", "Question"],
-         true <- Visibility.is_public?(activity),
-         {:ok, _user} <- User.add_pinnned_activity(user, activity) do
+  @spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()}
+  def pin(id, %User{ap_id: actor} = user) do
+    with %Activity{} = activity <- create_activity_by_id(id),
+         true <- activity_belongs_to_actor(activity, actor),
+         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, %{errors: [pinned_activities: {err, _}]}} -> {:error, err}
-      _ -> {:error, dgettext("errors", "Could not pin")}
+      {:error, {:execute_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{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
-         {:ok, _user} <- User.remove_pinnned_activity(user, activity) 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"]
+           ) do
       {:ok, activity}
-    else
-      {:error, %{errors: [pinned_activities: {err, _}]}} -> {:error, err}
-      _ -> {:error, dgettext("errors", "Could not unpin")}
     end
   end
 
diff --git a/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex b/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex
index d25f84837..84621500e 100644
--- a/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex
@@ -30,6 +30,12 @@ def call(conn, {:error, error_message}) do
     |> json(%{error: error_message})
   end
 
+  def call(conn, {:error, status, message}) do
+    conn
+    |> put_status(status)
+    |> json(%{error: message})
+  end
+
   def call(conn, _) do
     conn
     |> put_status(:internal_server_error)
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index b051fca74..724dc5c5d 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -260,6 +260,18 @@ def unfavourite(%{assigns: %{user: user}} = conn, %{id: activity_id}) do
   def pin(%{assigns: %{user: user}} = conn, %{id: ap_id_or_id}) do
     with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do
       try_render(conn, "show.json", activity: activity, for: user, as: :activity)
+    else
+      {:error, :pinned_statuses_limit_reached} ->
+        {:error, "You have already pinned the maximum number of statuses"}
+
+      {:error, :ownership_error} ->
+        {:error, :unprocessable_entity, "Someone else's status cannot be pinned"}
+
+      {:error, :visibility_error} ->
+        {:error, :unprocessable_entity, "Non-public status cannot be pinned"}
+
+      error ->
+        error
     end
   end
 
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 3753588f2..d0247fa4a 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -152,6 +152,8 @@ def render(
       |> Enum.filter(& &1)
       |> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
 
+    {pinned?, pinned_at} = pin_data(activity_object, user)
+
     %{
       id: to_string(activity.id),
       uri: object.data["id"],
@@ -173,7 +175,7 @@ def render(
       favourited: present?(favorited),
       bookmarked: present?(bookmarked),
       muted: false,
-      pinned: pinned?(activity, user),
+      pinned: pinned?,
       sensitive: false,
       spoiler_text: "",
       visibility: get_visibility(activity),
@@ -184,7 +186,8 @@ def render(
       language: nil,
       emojis: [],
       pleroma: %{
-        local: activity.local
+        local: activity.local,
+        pinned_at: pinned_at
       }
     }
   end
@@ -316,6 +319,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
           fn for_user, user -> User.mutes?(for_user, user) end
         )
 
+    {pinned?, pinned_at} = pin_data(object, user)
+
     %{
       id: to_string(activity.id),
       uri: object.data["id"],
@@ -339,7 +344,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
       favourited: present?(favorited),
       bookmarked: present?(bookmarked),
       muted: muted,
-      pinned: pinned?(activity, user),
+      pinned: pinned?,
       sensitive: sensitive,
       spoiler_text: summary,
       visibility: get_visibility(object),
@@ -360,7 +365,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
         direct_conversation_id: direct_conversation_id,
         thread_muted: thread_muted?,
         emoji_reactions: emoji_reactions,
-        parent_visible: visible_for_user?(reply_to, opts[:for])
+        parent_visible: visible_for_user?(reply_to, opts[:for]),
+        pinned_at: pinned_at
       }
     }
   end
@@ -529,8 +535,13 @@ defp present?(nil), do: false
   defp present?(false), do: false
   defp present?(_), do: true
 
-  defp pinned?(%Activity{id: id}, %User{pinned_activities: pinned_activities}),
-    do: id in pinned_activities
+  defp pin_data(%Object{data: %{"id" => object_id}}, %User{pinned_objects: pinned_objects}) do
+    if pinned_at = pinned_objects[object_id] do
+      {true, Utils.to_masto_date(pinned_at)}
+    else
+      {false, nil}
+    end
+  end
 
   defp build_emoji_map(emoji, users, current_user) do
     %{
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index de0bd27d7..ccf2ef796 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -704,6 +704,7 @@ defmodule Pleroma.Web.Router do
     # The following two are S2S as well, see `ActivityPub.fetch_follow_information_for_user/1`:
     get("/users/:nickname/followers", ActivityPubController, :followers)
     get("/users/:nickname/following", ActivityPubController, :following)
+    get("/users/:nickname/collections/featured", ActivityPubController, :pinned)
   end
 
   scope "/", Pleroma.Web.ActivityPub do
diff --git a/priv/repo/migrations/20210202110641_add_pinned_objects_to_users.exs b/priv/repo/migrations/20210202110641_add_pinned_objects_to_users.exs
new file mode 100644
index 000000000..644527246
--- /dev/null
+++ b/priv/repo/migrations/20210202110641_add_pinned_objects_to_users.exs
@@ -0,0 +1,9 @@
+defmodule Pleroma.Repo.Migrations.AddPinnedObjectsToUsers do
+  use Ecto.Migration
+
+  def change do
+    alter table(:users) do
+      add(:pinned_objects, :map)
+    end
+  end
+end
diff --git a/priv/repo/migrations/20210203141144_add_featured_address_to_users.exs b/priv/repo/migrations/20210203141144_add_featured_address_to_users.exs
new file mode 100644
index 000000000..0f6a21611
--- /dev/null
+++ b/priv/repo/migrations/20210203141144_add_featured_address_to_users.exs
@@ -0,0 +1,23 @@
+defmodule Pleroma.Repo.Migrations.AddFeaturedAddressToUsers do
+  use Ecto.Migration
+
+  def up do
+    alter table(:users) do
+      add(:featured_address, :string)
+    end
+
+    create(index(:users, [:featured_address]))
+
+    execute("""
+
+    update users set featured_address = concat(ap_id, '/collections/featured') where local = true and featured_address is null;
+
+    """)
+  end
+
+  def down do
+    alter table(:users) do
+      remove(:featured_address)
+    end
+  end
+end
diff --git a/priv/repo/migrations/20210205145000_move_pinned_activities_into_pinned_objects.exs b/priv/repo/migrations/20210205145000_move_pinned_activities_into_pinned_objects.exs
new file mode 100644
index 000000000..9aee545e3
--- /dev/null
+++ b/priv/repo/migrations/20210205145000_move_pinned_activities_into_pinned_objects.exs
@@ -0,0 +1,28 @@
+defmodule Pleroma.Repo.Migrations.MovePinnedActivitiesIntoPinnedObjects do
+  use Ecto.Migration
+
+  import Ecto.Query
+
+  alias Pleroma.Repo
+  alias Pleroma.User
+
+  def up do
+    from(u in User)
+    |> select([u], {u.id, fragment("?.pinned_activities", u)})
+    |> Repo.stream()
+    |> Stream.each(fn {user_id, pinned_activities_ids} ->
+      pinned_activities = Pleroma.Activity.all_by_ids_with_object(pinned_activities_ids)
+
+      pins =
+        Map.new(pinned_activities, fn %{object: %{data: %{"id" => object_id}}} ->
+          {object_id, NaiveDateTime.utc_now()}
+        end)
+
+      from(u in User, where: u.id == ^user_id)
+      |> Repo.update_all(set: [pinned_objects: pins])
+    end)
+    |> Stream.run()
+  end
+
+  def down, do: :noop
+end
diff --git a/priv/repo/migrations/20210206045221_remove_pinned_activities_from_users.exs b/priv/repo/migrations/20210206045221_remove_pinned_activities_from_users.exs
new file mode 100644
index 000000000..a3ee93f48
--- /dev/null
+++ b/priv/repo/migrations/20210206045221_remove_pinned_activities_from_users.exs
@@ -0,0 +1,15 @@
+defmodule Pleroma.Repo.Migrations.RemovePinnedActivitiesFromUsers do
+  use Ecto.Migration
+
+  def up do
+    alter table(:users) do
+      remove(:pinned_activities)
+    end
+  end
+
+  def down do
+    alter table(:users) do
+      add(:pinned_activities, {:array, :string}, default: [])
+    end
+  end
+end
diff --git a/test/fixtures/collections/featured.json b/test/fixtures/collections/featured.json
new file mode 100644
index 000000000..56f8f56fa
--- /dev/null
+++ b/test/fixtures/collections/featured.json
@@ -0,0 +1,39 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    "https://{{domain}}/schemas/litepub-0.1.jsonld",
+    {
+      "@language": "und"
+    }
+  ],
+  "id": "https://{{domain}}/users/{{nickname}}/collections/featured",
+  "orderedItems": [
+    {
+      "@context": [
+        "https://www.w3.org/ns/activitystreams",
+        "https://{{domain}}/schemas/litepub-0.1.jsonld",
+        {
+          "@language": "und"
+        }
+      ],
+      "actor": "https://{{domain}}/users/{{nickname}}",
+      "attachment": [],
+      "attributedTo": "https://{{domain}}/users/{{nickname}}",
+      "cc": [
+        "https://{{domain}}/users/{{nickname}}/followers"
+      ],
+      "content": "",
+      "id": "https://{{domain}}/objects/{{object_id}}",
+      "published": "2021-02-12T15:13:43.915429Z",
+      "sensitive": false,
+      "source": "",
+      "summary": "",
+      "tag": [],
+      "to": [
+        "https://www.w3.org/ns/activitystreams#Public"
+      ],
+      "type": "Note"
+    }
+  ],
+  "type": "OrderedCollection"
+}
diff --git a/test/fixtures/masto_pin.json b/test/fixtures/masto_pin.json
new file mode 100644
index 000000000..e57a34375
--- /dev/null
+++ b/test/fixtures/masto_pin.json
@@ -0,0 +1,41 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    "https://w3id.org/security/v1",
+    {
+      "Emoji": "toot:Emoji",
+      "Hashtag": "as:Hashtag",
+      "PropertyValue": "schema:PropertyValue",
+      "alsoKnownAs": {
+        "@id": "as:alsoKnownAs",
+        "@type": "@id"
+      },
+      "atomUri": "ostatus:atomUri",
+      "conversation": "ostatus:conversation",
+      "featured": {
+        "@id": "toot:featured",
+        "@type": "@id"
+      },
+      "focalPoint": {
+        "@container": "@list",
+        "@id": "toot:focalPoint"
+      },
+      "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+      "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+      "movedTo": {
+        "@id": "as:movedTo",
+        "@type": "@id"
+      },
+      "ostatus": "http://ostatus.org#",
+      "schema": "http://schema.org#",
+      "sensitive": "as:sensitive",
+      "toot": "http://joinmastodon.org/ns#",
+      "value": "schema:value"
+    }
+  ],
+  "id": "https://example.com/users/nickname/statuses/{{id}}",
+  "actor": "https://example.com/users/nickname",
+  "object": "https://example.com/users/nickname/statuses/101355175004496751",
+  "target": "https://example.com/users/nickname/collections/featured",
+  "type": "{{type}}"
+}
diff --git a/test/fixtures/statuses/note.json b/test/fixtures/statuses/note.json
new file mode 100644
index 000000000..41735cbc5
--- /dev/null
+++ b/test/fixtures/statuses/note.json
@@ -0,0 +1,27 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    "https://example.com/schemas/litepub-0.1.jsonld",
+    {
+      "@language": "und"
+    }
+  ],
+  "actor": "https://example.com/users/{{nickname}}",
+  "attachment": [],
+  "attributedTo": "https://example.com/users/{{nickname}}",
+  "cc": [
+    "https://example.com/users/{{nickname}}/followers"
+  ],
+  "content": "Content",
+  "context": "https://example.com/contexts/e4b180e1-7403-477f-aeb4-de57e7a3fe7f",
+  "conversation": "https://example.com/contexts/e4b180e1-7403-477f-aeb4-de57e7a3fe7f",
+  "id": "https://example.com/objects/{{object_id}}",
+  "published": "2019-12-15T22:00:05.279583Z",
+  "sensitive": false,
+  "summary": "",
+  "tag": [],
+  "to": [
+    "https://www.w3.org/ns/activitystreams#Public"
+  ],
+  "type": "Note"
+}
diff --git a/test/fixtures/users_mock/masto_featured.json b/test/fixtures/users_mock/masto_featured.json
new file mode 100644
index 000000000..646a343ad
--- /dev/null
+++ b/test/fixtures/users_mock/masto_featured.json
@@ -0,0 +1,18 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    {
+      "ostatus": "http://ostatus.org#",
+      "atomUri": "ostatus:atomUri",
+      "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+      "conversation": "ostatus:conversation",
+      "sensitive": "as:sensitive",
+      "toot": "http://joinmastodon.org/ns#",
+      "votersCount": "toot:votersCount"
+    }
+  ],
+  "id": "https://{{domain}}/users/{{nickname}}/collections/featured",
+  "type": "OrderedCollection",
+  "totalItems": 0,
+  "orderedItems": []
+}
diff --git a/test/fixtures/users_mock/user.json b/test/fixtures/users_mock/user.json
new file mode 100644
index 000000000..da2483d02
--- /dev/null
+++ b/test/fixtures/users_mock/user.json
@@ -0,0 +1,41 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    "https://example.com/schemas/litepub-0.1.jsonld",
+    {
+      "@language": "und"
+    }
+  ],
+  "attachment": [],
+  "endpoints": {
+    "oauthAuthorizationEndpoint": "https://example.com/oauth/authorize",
+    "oauthRegistrationEndpoint": "https://example.com/api/v1/apps",
+    "oauthTokenEndpoint": "https://example.com/oauth/token",
+    "sharedInbox": "https://example.com/inbox"
+  },
+  "followers": "https://example.com/users/{{nickname}}/followers",
+  "following": "https://example.com/users/{{nickname}}/following",
+  "icon": {
+    "type": "Image",
+    "url": "https://example.com/media/4e914f5b84e4a259a3f6c2d2edc9ab642f2ab05f3e3d9c52c81fc2d984b3d51e.jpg"
+  },
+  "id": "https://example.com/users/{{nickname}}",
+  "image": {
+    "type": "Image",
+    "url": "https://example.com/media/f739efddefeee49c6e67e947c4811fdc911785c16ae43da4c3684051fbf8da6a.jpg?name=f739efddefeee49c6e67e947c4811fdc911785c16ae43da4c3684051fbf8da6a.jpg"
+  },
+  "inbox": "https://example.com/users/{{nickname}}/inbox",
+  "manuallyApprovesFollowers": false,
+  "name": "{{nickname}}",
+  "outbox": "https://example.com/users/{{nickname}}/outbox",
+  "preferredUsername": "{{nickname}}",
+  "publicKey": {
+    "id": "https://example.com/users/{{nickname}}#main-key",
+    "owner": "https://example.com/users/{{nickname}}",
+    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5DLtwGXNZElJyxFGfcVc\nXANhaMadj/iYYQwZjOJTV9QsbtiNBeIK54PJrYuU0/0YIdrvS1iqheX5IwXRhcwa\nhm3ZyLz7XeN9st7FBni4BmZMBtMpxAuYuu5p/jbWy13qAiYOhPreCx0wrWgm/lBD\n9mkgaxIxPooBE0S4ZWEJIDIV1Vft3AWcRUyWW1vIBK0uZzs6GYshbQZB952S0yo4\nFzI1hABGHncH8UvuFauh4EZ8tY7/X5I0pGRnDOcRN1dAht5w5yTA+6r5kebiFQjP\nIzN/eCO/a9Flrj9YGW7HDNtjSOH0A31PLRGlJtJO3yK57dnf5ppyCZGfL4emShQo\ncQIDAQAB\n-----END PUBLIC KEY-----\n\n"
+  },
+  "summary": "your friendly neighborhood pleroma developer<br>I like cute things and distributed systems, and really hate delete and redrafts",
+  "tag": [],
+  "type": "Person",
+  "url": "https://example.com/users/{{nickname}}"
+}
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index 6f5bcab57..d81c1b8eb 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -2338,4 +2338,49 @@ test "active_user_count/1" do
     assert User.active_user_count(6) == 3
     assert User.active_user_count(1) == 1
   end
+
+  describe "pins" do
+    setup do
+      user = insert(:user)
+
+      [user: user, object_id: object_id_from_created_activity(user)]
+    end
+
+    test "unique pins", %{user: user, object_id: object_id} do
+      assert {:ok, %{pinned_objects: %{^object_id => pinned_at1} = pins} = updated_user} =
+               User.add_pinned_object_id(user, object_id)
+
+      assert Enum.count(pins) == 1
+
+      assert {:ok, %{pinned_objects: %{^object_id => pinned_at2} = pins}} =
+               User.add_pinned_object_id(updated_user, object_id)
+
+      assert pinned_at1 == pinned_at2
+
+      assert Enum.count(pins) == 1
+    end
+
+    test "respects max_pinned_statuses limit", %{user: user, object_id: object_id} do
+      clear_config([:instance, :max_pinned_statuses], 1)
+      {:ok, updated} = User.add_pinned_object_id(user, object_id)
+
+      object_id2 = object_id_from_created_activity(user)
+
+      {:error, %{errors: errors}} = User.add_pinned_object_id(updated, object_id2)
+      assert Keyword.has_key?(errors, :pinned_objects)
+    end
+
+    test "remove_pinned_object_id/2", %{user: user, object_id: object_id} do
+      assert {:ok, updated} = User.add_pinned_object_id(user, object_id)
+
+      {:ok, after_remove} = User.remove_pinned_object_id(updated, object_id)
+      assert after_remove.pinned_objects == %{}
+    end
+  end
+
+  defp object_id_from_created_activity(user) do
+    %{id: id} = insert(:note_activity, user: user)
+    %{object: %{data: %{"id" => object_id}}} = Activity.get_by_id_with_object(id)
+    object_id
+  end
 end
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index 19e04d472..a9cbf90c3 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -636,6 +636,86 @@ test "without valid signature, " <>
       |> post("/inbox", non_create_data)
       |> json_response(400)
     end
+
+    test "accepts Add/Remove activities", %{conn: conn} do
+      object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
+
+      status =
+        File.read!("test/fixtures/statuses/note.json")
+        |> String.replace("{{nickname}}", "lain")
+        |> String.replace("{{object_id}}", object_id)
+
+      object_url = "https://example.com/objects/#{object_id}"
+
+      user =
+        File.read!("test/fixtures/users_mock/user.json")
+        |> String.replace("{{nickname}}", "lain")
+
+      actor = "https://example.com/users/lain"
+
+      Tesla.Mock.mock(fn
+        %{
+          method: :get,
+          url: ^object_url
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: status,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+
+        %{
+          method: :get,
+          url: ^actor
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: user,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+      end)
+
+      data = %{
+        "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
+        "actor" => actor,
+        "object" => object_url,
+        "target" => "https://example.com/users/lain/collections/featured",
+        "type" => "Add",
+        "to" => [Pleroma.Constants.as_public()]
+      }
+
+      assert "ok" ==
+               conn
+               |> assign(:valid_signature, true)
+               |> put_req_header("content-type", "application/activity+json")
+               |> post("/inbox", data)
+               |> json_response(200)
+
+      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+      assert Activity.get_by_ap_id(data["id"])
+      user = User.get_cached_by_ap_id(data["actor"])
+      assert user.pinned_objects[data["object"]]
+
+      data = %{
+        "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
+        "actor" => actor,
+        "object" => object_url,
+        "target" => "https://example.com/users/lain/collections/featured",
+        "type" => "Remove",
+        "to" => [Pleroma.Constants.as_public()]
+      }
+
+      assert "ok" ==
+               conn
+               |> assign(:valid_signature, true)
+               |> put_req_header("content-type", "application/activity+json")
+               |> post("/inbox", data)
+               |> json_response(200)
+
+      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+      user = refresh_record(user)
+      refute user.pinned_objects[data["object"]]
+    end
   end
 
   describe "/users/:nickname/inbox" do
@@ -1772,4 +1852,29 @@ test "POST /api/ap/upload_media", %{conn: conn} do
       |> json_response(403)
     end
   end
+
+  test "pinned collection", %{conn: conn} do
+    clear_config([:instance, :max_pinned_statuses], 2)
+    user = insert(:user)
+    objects = insert_list(2, :note, user: user)
+
+    Enum.reduce(objects, user, fn %{data: %{"id" => object_id}}, user ->
+      {:ok, updated} = User.add_pinned_object_id(user, object_id)
+      updated
+    end)
+
+    %{nickname: nickname, featured_address: featured_address, pinned_objects: pinned_objects} =
+      refresh_record(user)
+
+    %{"id" => ^featured_address, "orderedItems" => items} =
+      conn
+      |> get("/users/#{nickname}/collections/featured")
+      |> json_response(200)
+
+    object_ids = Enum.map(items, & &1["id"])
+
+    assert Enum.all?(pinned_objects, fn {obj_id, _} ->
+             obj_id in object_ids
+           end)
+  end
 end
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index c7fa452f7..081d00d45 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -235,6 +235,83 @@ test "works for bridgy actors" do
                "url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}]
              }
     end
+
+    test "fetches user featured collection" do
+      ap_id = "https://example.com/users/lain"
+
+      featured_url = "https://example.com/users/lain/collections/featured"
+
+      user_data =
+        "test/fixtures/users_mock/user.json"
+        |> File.read!()
+        |> String.replace("{{nickname}}", "lain")
+        |> Jason.decode!()
+        |> Map.put("featured", featured_url)
+        |> Jason.encode!()
+
+      object_id = Ecto.UUID.generate()
+
+      featured_data =
+        "test/fixtures/collections/featured.json"
+        |> File.read!()
+        |> String.replace("{{domain}}", "example.com")
+        |> String.replace("{{nickname}}", "lain")
+        |> String.replace("{{object_id}}", object_id)
+
+      object_url = "https://example.com/objects/#{object_id}"
+
+      object_data =
+        "test/fixtures/statuses/note.json"
+        |> File.read!()
+        |> String.replace("{{object_id}}", object_id)
+        |> String.replace("{{nickname}}", "lain")
+
+      Tesla.Mock.mock(fn
+        %{
+          method: :get,
+          url: ^ap_id
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: user_data,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+
+        %{
+          method: :get,
+          url: ^featured_url
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: featured_data,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+      end)
+
+      Tesla.Mock.mock_global(fn
+        %{
+          method: :get,
+          url: ^object_url
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: object_data,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+      end)
+
+      {:ok, user} = ActivityPub.make_user_from_ap_id(ap_id)
+      Process.sleep(50)
+
+      assert user.featured_address == featured_url
+      assert Map.has_key?(user.pinned_objects, object_url)
+
+      in_db = Pleroma.User.get_by_ap_id(ap_id)
+      assert in_db.featured_address == featured_url
+      assert Map.has_key?(user.pinned_objects, object_url)
+
+      assert %{data: %{"id" => ^object_url}} = Object.get_by_ap_id(object_url)
+    end
   end
 
   test "it fetches the appropriate tag-restricted posts" do
diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs
index 4c3fcb44a..28d7e1e3c 100644
--- a/test/pleroma/web/activity_pub/transmogrifier_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs
@@ -6,6 +6,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
   use Oban.Testing, repo: Pleroma.Repo
   use Pleroma.DataCase
 
+  require Pleroma.Constants
+
   alias Pleroma.Activity
   alias Pleroma.Object
   alias Pleroma.Tests.ObanHelpers
@@ -106,6 +108,78 @@ test "it accepts Move activities" do
       assert activity.data["target"] == new_user.ap_id
       assert activity.data["type"] == "Move"
     end
+
+    test "it accepts Add/Remove activities" do
+      user =
+        "test/fixtures/users_mock/user.json"
+        |> File.read!()
+        |> String.replace("{{nickname}}", "lain")
+
+      object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
+
+      object =
+        "test/fixtures/statuses/note.json"
+        |> File.read!()
+        |> String.replace("{{nickname}}", "lain")
+        |> String.replace("{{object_id}}", object_id)
+
+      object_url = "https://example.com/objects/#{object_id}"
+
+      actor = "https://example.com/users/lain"
+
+      Tesla.Mock.mock(fn
+        %{
+          method: :get,
+          url: ^actor
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: user,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+
+        %{
+          method: :get,
+          url: ^object_url
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: object,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+      end)
+
+      message = %{
+        "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
+        "actor" => actor,
+        "object" => object_url,
+        "target" => "https://example.com/users/lain/collections/featured",
+        "type" => "Add",
+        "to" => [Pleroma.Constants.as_public()],
+        "cc" => ["https://example.com/users/lain/followers"]
+      }
+
+      assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+      assert activity.data == message
+      user = User.get_cached_by_ap_id(actor)
+      assert user.pinned_objects[object_url]
+
+      remove = %{
+        "id" => "http://localhost:400/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
+        "actor" => actor,
+        "object" => object_url,
+        "target" => "http://example.com/users/lain/collections/featured",
+        "type" => "Remove",
+        "to" => [Pleroma.Constants.as_public()],
+        "cc" => ["https://example.com/users/lain/followers"]
+      }
+
+      assert {:ok, activity} = Transmogrifier.handle_incoming(remove)
+      assert activity.data == remove
+
+      user = refresh_record(user)
+      refute user.pinned_objects[object_url]
+    end
   end
 
   describe "prepare outgoing" do
diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs
index 6619f8fc8..fa55c2832 100644
--- a/test/pleroma/web/common_api_test.exs
+++ b/test/pleroma/web/common_api_test.exs
@@ -827,13 +827,17 @@ test "favoriting a status twice returns ok, but without the like activity" do
       [user: user, activity: activity]
     end
 
+    test "activity not found error", %{user: user} do
+      assert {:error, :not_found} = CommonAPI.pin("id", user)
+    end
+
     test "pin status", %{user: user, activity: activity} do
       assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
 
-      id = activity.id
+      %{data: %{"id" => object_id}} = Object.normalize(activity)
       user = refresh_record(user)
 
-      assert %User{pinned_activities: [^id]} = user
+      assert user.pinned_objects |> Map.keys() == [object_id]
     end
 
     test "pin poll", %{user: user} do
@@ -845,10 +849,11 @@ test "pin poll", %{user: user} do
 
       assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
 
-      id = activity.id
+      %{data: %{"id" => object_id}} = Object.normalize(activity)
+
       user = refresh_record(user)
 
-      assert %User{pinned_activities: [^id]} = user
+      assert user.pinned_objects |> Map.keys() == [object_id]
     end
 
     test "unlisted statuses can be pinned", %{user: user} do
@@ -859,7 +864,7 @@ test "unlisted statuses can be pinned", %{user: user} do
     test "only self-authored can be pinned", %{activity: activity} do
       user = insert(:user)
 
-      assert {:error, "Could not pin"} = CommonAPI.pin(activity.id, user)
+      assert {:error, :ownership_error} = CommonAPI.pin(activity.id, user)
     end
 
     test "max pinned statuses", %{user: user, activity: activity_one} do
@@ -869,8 +874,12 @@ test "max pinned statuses", %{user: user, activity: activity_one} do
 
       user = refresh_record(user)
 
-      assert {:error, "You have already pinned the maximum number of statuses"} =
-               CommonAPI.pin(activity_two.id, user)
+      assert {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity_two.id, user)
+    end
+
+    test "only public can be pinned", %{user: user} do
+      {:ok, activity} = CommonAPI.post(user, %{status: "private status", visibility: "private"})
+      {:error, :visibility_error} = CommonAPI.pin(activity.id, user)
     end
 
     test "unpin status", %{user: user, activity: activity} do
@@ -884,7 +893,7 @@ test "unpin status", %{user: user, activity: activity} do
 
       user = refresh_record(user)
 
-      assert %User{pinned_activities: []} = user
+      assert user.pinned_objects == %{}
     end
 
     test "should unpin when deleting a status", %{user: user, activity: activity} do
@@ -896,7 +905,40 @@ test "should unpin when deleting a status", %{user: user, activity: activity} do
 
       user = refresh_record(user)
 
-      assert %User{pinned_activities: []} = user
+      assert user.pinned_objects == %{}
+    end
+
+    test "ephemeral activity won't be deleted if was pinned", %{user: user} do
+      {:ok, activity} = CommonAPI.post(user, %{status: "Hello!", expires_in: 601})
+
+      assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
+
+      {:ok, _activity} = CommonAPI.pin(activity.id, user)
+      refute Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
+
+      user = refresh_record(user)
+      {:ok, _} = CommonAPI.unpin(activity.id, user)
+
+      # recreates expiration job on unpin
+      assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
+    end
+
+    test "ephemeral activity deletion job won't be deleted on pinning error", %{
+      user: user,
+      activity: activity
+    } do
+      clear_config([:instance, :max_pinned_statuses], 1)
+
+      {:ok, _activity} = CommonAPI.pin(activity.id, user)
+
+      {:ok, activity2} = CommonAPI.post(user, %{status: "another status", expires_in: 601})
+
+      assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
+
+      user = refresh_record(user)
+      {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity2.id, user)
+
+      assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
     end
   end
 
diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
index f616f405e..e0d642910 100644
--- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
@@ -1223,6 +1223,13 @@ test "pin status", %{conn: conn, user: user, activity: activity} do
                |> json_response_and_validate_schema(200)
     end
 
+    test "non authenticated user", %{activity: activity} do
+      assert build_conn()
+             |> put_req_header("content-type", "application/json")
+             |> post("/api/v1/statuses/#{activity.id}/pin")
+             |> json_response(403) == %{"error" => "Invalid credentials."}
+    end
+
     test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
       {:ok, dm} = CommonAPI.post(user, %{status: "test", visibility: "direct"})
 
@@ -1231,7 +1238,18 @@ test "/pin: returns 400 error when activity is not public", %{conn: conn, user:
         |> put_req_header("content-type", "application/json")
         |> post("/api/v1/statuses/#{dm.id}/pin")
 
-      assert json_response_and_validate_schema(conn, 400) == %{"error" => "Could not pin"}
+      assert json_response_and_validate_schema(conn, 422) == %{
+               "error" => "Non-public status cannot be pinned"
+             }
+    end
+
+    test "pin by another user", %{activity: activity} do
+      %{conn: conn} = oauth_access(["write:accounts"])
+
+      assert conn
+             |> put_req_header("content-type", "application/json")
+             |> post("/api/v1/statuses/#{activity.id}/pin")
+             |> json_response(422) == %{"error" => "Someone else's status cannot be pinned"}
     end
 
     test "unpin status", %{conn: conn, user: user, activity: activity} do
@@ -1252,13 +1270,11 @@ test "unpin status", %{conn: conn, user: user, activity: activity} do
                |> json_response_and_validate_schema(200)
     end
 
-    test "/unpin: returns 400 error when activity is not exist", %{conn: conn} do
-      conn =
-        conn
-        |> put_req_header("content-type", "application/json")
-        |> post("/api/v1/statuses/1/unpin")
-
-      assert json_response_and_validate_schema(conn, 400) == %{"error" => "Could not unpin"}
+    test "/unpin: returns 404 error when activity doesn't exist", %{conn: conn} do
+      assert conn
+             |> put_req_header("content-type", "application/json")
+             |> post("/api/v1/statuses/1/unpin")
+             |> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
     end
 
     test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs
index 4172cc294..fbea25079 100644
--- a/test/pleroma/web/mastodon_api/views/status_view_test.exs
+++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs
@@ -286,7 +286,8 @@ test "a note activity" do
         direct_conversation_id: nil,
         thread_muted: false,
         emoji_reactions: [],
-        parent_visible: false
+        parent_visible: false,
+        pinned_at: nil
       }
     }
 
diff --git a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
index f389c272b..fa3b29006 100644
--- a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
+++ b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
@@ -27,6 +27,16 @@ test "adds status to pleroma instance if the `acct` is a status", %{conn: conn}
             body: File.read!("test/fixtures/tesla_mock/status.emelie.json")
           }
 
+        %{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} ->
+          %Tesla.Env{
+            status: 200,
+            headers: [{"content-type", "application/activity+json"}],
+            body:
+              File.read!("test/fixtures/users_mock/masto_featured.json")
+              |> String.replace("{{domain}}", "mastodon.social")
+              |> String.replace("{{nickname}}", "emelie")
+          }
+
         %{method: :get, url: "https://mastodon.social/users/emelie"} ->
           %Tesla.Env{
             status: 200,
@@ -52,6 +62,16 @@ test "show follow account page if the `acct` is a account link", %{conn: conn} d
             headers: [{"content-type", "application/activity+json"}],
             body: File.read!("test/fixtures/tesla_mock/emelie.json")
           }
+
+        %{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} ->
+          %Tesla.Env{
+            status: 200,
+            headers: [{"content-type", "application/activity+json"}],
+            body:
+              File.read!("test/fixtures/users_mock/masto_featured.json")
+              |> String.replace("{{domain}}", "mastodon.social")
+              |> String.replace("{{nickname}}", "emelie")
+          }
       end)
 
       response =
@@ -70,6 +90,16 @@ test "show follow page if the `acct` is a account link", %{conn: conn} do
             headers: [{"content-type", "application/activity+json"}],
             body: File.read!("test/fixtures/tesla_mock/emelie.json")
           }
+
+        %{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} ->
+          %Tesla.Env{
+            status: 200,
+            headers: [{"content-type", "application/activity+json"}],
+            body:
+              File.read!("test/fixtures/users_mock/masto_featured.json")
+              |> String.replace("{{domain}}", "mastodon.social")
+              |> String.replace("{{nickname}}", "emelie")
+          }
       end)
 
       user = insert(:user)
diff --git a/test/support/factory.ex b/test/support/factory.ex
index af4fff45b..883cedf3c 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -48,13 +48,15 @@ def user_factory(attrs \\ %{}) do
         %{
           ap_id: ap_id,
           follower_address: ap_id <> "/followers",
-          following_address: ap_id <> "/following"
+          following_address: ap_id <> "/following",
+          featured_address: ap_id <> "/collections/featured"
         }
       else
         %{
           ap_id: User.ap_id(user),
           follower_address: User.ap_followers(user),
-          following_address: User.ap_following(user)
+          following_address: User.ap_following(user),
+          featured_address: User.ap_featured_collection(user)
         }
       end
 
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index eb692fab5..9e9f1c86c 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -89,6 +89,18 @@ def get("https://mastodon.sdf.org/users/rinpatch", _, _, _) do
      }}
   end
 
+  def get("https://mastodon.sdf.org/users/rinpatch/collections/featured", _, _, _) do
+    {:ok,
+     %Tesla.Env{
+       status: 200,
+       body:
+         File.read!("test/fixtures/users_mock/masto_featured.json")
+         |> String.replace("{{domain}}", "mastodon.sdf.org")
+         |> String.replace("{{nickname}}", "rinpatch"),
+       headers: [{"content-type", "application/activity+json"}]
+     }}
+  end
+
   def get("https://patch.cx/objects/tesla_mock/poll_attachment", _, _, _) do
     {:ok,
      %Tesla.Env{
@@ -905,6 +917,17 @@ def get("https://mastodon.social/users/lambadalambda", _, _, _) do
      }}
   end
 
+  def get("https://mastodon.social/users/lambadalambda/collections/featured", _, _, _) do
+    {:ok,
+     %Tesla.Env{
+       status: 200,
+       body:
+         File.read!("test/fixtures/users_mock/masto_featured.json")
+         |> String.replace("{{domain}}", "mastodon.social")
+         |> String.replace("{{nickname}}", "lambadalambda")
+     }}
+  end
+
   def get("https://apfed.club/channel/indio", _, _, _) do
     {:ok,
      %Tesla.Env{

From 17f28c0507e3c34ce75e63747eed9abb66713e6e Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Thu, 25 Feb 2021 14:00:44 +0300
Subject: [PATCH 098/339] mastodon pins

---
 lib/pleroma/object/containment.ex             |  8 ++
 .../web/activity_pub/transmogrifier.ex        | 17 +++-
 test/fixtures/statuses/masto-note.json        | 47 +++++++++++
 .../activity_pub_controller_test.exs          | 78 +++++++++++++++++++
 4 files changed, 146 insertions(+), 4 deletions(-)
 create mode 100644 test/fixtures/statuses/masto-note.json

diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex
index fb0398f92..040537acf 100644
--- a/lib/pleroma/object/containment.ex
+++ b/lib/pleroma/object/containment.ex
@@ -71,6 +71,14 @@ def contain_origin_from_id(id, %{"id" => other_id} = _params) when is_binary(oth
     compare_uris(id_uri, other_uri)
   end
 
+  # Mastodon pin activities don't have an id, so we check the object field, which will be pinned.
+  def contain_origin_from_id(id, %{"object" => object}) when is_binary(object) do
+    id_uri = URI.parse(id)
+    object_uri = URI.parse(object)
+
+    compare_uris(id_uri, object_uri)
+  end
+
   def contain_origin_from_id(_id, _data), do: :error
 
   def contain_child(%{"object" => %{"id" => id, "attributedTo" => _} = object}),
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 270cea6dc..b662f5379 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -557,10 +557,19 @@ def handle_incoming(
   end
 
   def handle_incoming(%{"type" => type} = data, _options) when type in ~w(Add Remove) do
-    with :ok <- ObjectValidator.fetch_actor_and_object(data),
-         %Object{} <- Object.normalize(data["object"], fetch: true),
-         {:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do
-      {:ok, activity}
+    with {:ok, user} <- ObjectValidator.fetch_actor(data),
+         %Object{} <- Object.normalize(data["object"], fetch: true) do
+      # Mastodon sends pin/unpin objects without id, to, cc fields
+      data =
+        data
+        |> Map.put_new("id", Utils.generate_activity_id())
+        |> Map.put_new("to", [Pleroma.Constants.as_public()])
+        |> Map.put_new("cc", [user.follower_address])
+
+      case Pipeline.common_pipeline(data, local: false) do
+        {:ok, activity, _meta} -> {:ok, activity}
+        error -> error
+      end
     end
   end
 
diff --git a/test/fixtures/statuses/masto-note.json b/test/fixtures/statuses/masto-note.json
new file mode 100644
index 000000000..6b96de473
--- /dev/null
+++ b/test/fixtures/statuses/masto-note.json
@@ -0,0 +1,47 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    {
+      "ostatus": "http://ostatus.org#",
+      "atomUri": "ostatus:atomUri",
+      "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+      "conversation": "ostatus:conversation",
+      "sensitive": "as:sensitive",
+      "toot": "http://joinmastodon.org/ns#",
+      "votersCount": "toot:votersCount"
+    }
+  ],
+  "id": "https://example.com/users/{{nickname}}/statuses/{{status_id}}",
+  "type": "Note",
+  "summary": null,
+  "inReplyTo": null,
+  "published": "2021-02-24T12:40:49Z",
+  "url": "https://example.com/@{{nickname}}/{{status_id}}",
+  "attributedTo": "https://example.com/users/{{nickname}}",
+  "to": [
+    "https://www.w3.org/ns/activitystreams#Public"
+  ],
+  "cc": [
+    "https://example.com/users/{{nickname}}/followers"
+  ],
+  "sensitive": false,
+  "atomUri": "https://example.com/users/{{nickname}}/statuses/{{status_id}}",
+  "inReplyToAtomUri": null,
+  "conversation": "tag:example.com,2021-02-24:objectId=15:objectType=Conversation",
+  "content": "<p></p>",
+  "contentMap": {
+    "en": "<p></p>"
+  },
+  "attachment": [],
+  "tag": [],
+  "replies": {
+    "id": "https://example.com/users/{{nickname}}/statuses/{{status_id}}/replies",
+    "type": "Collection",
+    "first": {
+      "type": "CollectionPage",
+      "next": "https://example.com/users/{{nickname}}/statuses/{{status_id}}/replies?only_other_accounts=true&page=true",
+      "partOf": "https://example.com/users/{{nickname}}/statuses/{{status_id}}/replies",
+      "items": []
+    }
+  }
+}
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index a9cbf90c3..d9fa25d94 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -716,6 +716,84 @@ test "accepts Add/Remove activities", %{conn: conn} do
       user = refresh_record(user)
       refute user.pinned_objects[data["object"]]
     end
+
+    test "mastodon pin/unpin", %{conn: conn} do
+      status_id = "105786274556060421"
+
+      status =
+        File.read!("test/fixtures/statuses/masto-note.json")
+        |> String.replace("{{nickname}}", "lain")
+        |> String.replace("{{status_id}}", status_id)
+
+      status_url = "https://example.com/users/lain/statuses/#{status_id}"
+
+      user =
+        File.read!("test/fixtures/users_mock/user.json")
+        |> String.replace("{{nickname}}", "lain")
+
+      actor = "https://example.com/users/lain"
+
+      Tesla.Mock.mock(fn
+        %{
+          method: :get,
+          url: ^status_url
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: status,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+
+        %{
+          method: :get,
+          url: ^actor
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: user,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+      end)
+
+      data = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "actor" => actor,
+        "object" => status_url,
+        "target" => "https://example.com/users/lain/collections/featured",
+        "type" => "Add"
+      }
+
+      assert "ok" ==
+               conn
+               |> assign(:valid_signature, true)
+               |> put_req_header("content-type", "application/activity+json")
+               |> post("/inbox", data)
+               |> json_response(200)
+
+      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+      assert Activity.get_by_object_ap_id_with_object(data["object"])
+      user = User.get_cached_by_ap_id(data["actor"])
+      assert user.pinned_objects[data["object"]]
+
+      data = %{
+        "actor" => actor,
+        "object" => status_url,
+        "target" => "https://example.com/users/lain/collections/featured",
+        "type" => "Remove"
+      }
+
+      assert "ok" ==
+               conn
+               |> assign(:valid_signature, true)
+               |> put_req_header("content-type", "application/activity+json")
+               |> post("/inbox", data)
+               |> json_response(200)
+
+      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+      assert Activity.get_by_object_ap_id_with_object(data["object"])
+      user = refresh_record(user)
+      refute user.pinned_objects[data["object"]]
+    end
   end
 
   describe "/users/:nickname/inbox" do

From ff612750b1bae5223bca76b34a39e7d2bd05770c Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Tue, 2 Mar 2021 17:24:06 +0300
Subject: [PATCH 099/339] validator renaming & add validation for target

---
 lib/pleroma/web/activity_pub/object_validator.ex    |  4 ++--
 .../{pin_validator.ex => add_remove_validator.ex}   | 13 ++++++++++++-
 .../object_validators/common_validations.ex         |  8 ++++++++
 .../web/activity_pub/transmogrifier_test.exs        |  2 +-
 .../controllers/status_controller_test.exs          |  6 +++---
 test/support/http_request_mock.ex                   |  3 ++-
 6 files changed, 28 insertions(+), 8 deletions(-)
 rename lib/pleroma/web/activity_pub/object_validators/{pin_validator.ex => add_remove_validator.ex} (73%)

diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 11432ef38..14c3e8531 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -17,6 +17,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
   alias Pleroma.Object.Containment
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator
+  alias Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator
@@ -30,7 +31,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
   alias Pleroma.Web.ActivityPub.ObjectValidators.EventValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
-  alias Pleroma.Web.ActivityPub.ObjectValidators.PinValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
@@ -238,7 +238,7 @@ def validate(%{"type" => "Announce"} = object, meta) do
   def validate(%{"type" => type} = object, meta) when type in ~w(Add Remove) do
     with {:ok, object} <-
            object
-           |> PinValidator.cast_and_validate()
+           |> AddRemoveValidator.cast_and_validate()
            |> Ecto.Changeset.apply_action(:insert) do
       object = stringify_keys(object)
       {:ok, object, meta}
diff --git a/lib/pleroma/web/activity_pub/object_validators/pin_validator.ex b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
similarity index 73%
rename from lib/pleroma/web/activity_pub/object_validators/pin_validator.ex
rename to lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
index dca8cba6f..73d1c03f0 100644
--- a/lib/pleroma/web/activity_pub/object_validators/pin_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
@@ -2,7 +2,7 @@
 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.PinValidator do
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do
   use Ecto.Schema
 
   import Ecto.Changeset
@@ -37,6 +37,17 @@ defp validate_data(changeset) do
     |> validate_required([:id, :target, :object, :actor, :type, :to, :cc])
     |> validate_inclusion(:type, ~w(Add Remove))
     |> validate_actor_presence()
+    |> validate_collection_belongs_to_actor()
     |> validate_object_presence()
   end
+
+  defp validate_collection_belongs_to_actor(changeset) do
+    validate_change(changeset, :target, fn :target, target ->
+      if String.starts_with?(target, changeset.changes[:actor]) do
+        []
+      else
+        [target: "collection doesn't belong to actor"]
+      end
+    end)
+  end
 end
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
index 093549a45..940430588 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
   alias Pleroma.Object
   alias Pleroma.User
 
+  @spec validate_any_presence(Ecto.Changeset.t(), [atom()]) :: Ecto.Changeset.t()
   def validate_any_presence(cng, fields) do
     non_empty =
       fields
@@ -29,6 +30,7 @@ def validate_any_presence(cng, fields) do
     end
   end
 
+  @spec validate_actor_presence(Ecto.Changeset.t(), keyword()) :: Ecto.Changeset.t()
   def validate_actor_presence(cng, options \\ []) do
     field_name = Keyword.get(options, :field_name, :actor)
 
@@ -47,6 +49,7 @@ def validate_actor_presence(cng, options \\ []) do
     end)
   end
 
+  @spec validate_object_presence(Ecto.Changeset.t(), keyword()) :: Ecto.Changeset.t()
   def validate_object_presence(cng, options \\ []) do
     field_name = Keyword.get(options, :field_name, :object)
     allowed_types = Keyword.get(options, :allowed_types, false)
@@ -68,6 +71,7 @@ def validate_object_presence(cng, options \\ []) do
     end)
   end
 
+  @spec validate_object_or_user_presence(Ecto.Changeset.t(), keyword()) :: Ecto.Changeset.t()
   def validate_object_or_user_presence(cng, options \\ []) do
     field_name = Keyword.get(options, :field_name, :object)
     options = Keyword.put(options, :field_name, field_name)
@@ -83,6 +87,7 @@ def validate_object_or_user_presence(cng, options \\ []) do
     if actor_cng.valid?, do: actor_cng, else: object_cng
   end
 
+  @spec validate_host_match(Ecto.Changeset.t(), [atom()]) :: Ecto.Changeset.t()
   def validate_host_match(cng, fields \\ [:id, :actor]) do
     if same_domain?(cng, fields) do
       cng
@@ -95,6 +100,7 @@ def validate_host_match(cng, fields \\ [:id, :actor]) do
     end
   end
 
+  @spec validate_fields_match(Ecto.Changeset.t(), [atom()]) :: Ecto.Changeset.t()
   def validate_fields_match(cng, fields) do
     if map_unique?(cng, fields) do
       cng
@@ -122,12 +128,14 @@ defp map_unique?(cng, fields, func \\ & &1) do
     end)
   end
 
+  @spec same_domain?(Ecto.Changeset.t(), [atom()]) :: boolean()
   def same_domain?(cng, fields \\ [:actor, :object]) do
     map_unique?(cng, fields, fn value -> URI.parse(value).host end)
   end
 
   # This figures out if a user is able to create, delete or modify something
   # based on the domain and superuser status
+  @spec validate_modification_rights(Ecto.Changeset.t()) :: Ecto.Changeset.t()
   def validate_modification_rights(cng) do
     actor = User.get_cached_by_ap_id(get_field(cng, :actor))
 
diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs
index 28d7e1e3c..9bc27f89e 100644
--- a/test/pleroma/web/activity_pub/transmogrifier_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs
@@ -168,7 +168,7 @@ test "it accepts Add/Remove activities" do
         "id" => "http://localhost:400/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
         "actor" => actor,
         "object" => object_url,
-        "target" => "http://example.com/users/lain/collections/featured",
+        "target" => "https://example.com/users/lain/collections/featured",
         "type" => "Remove",
         "to" => [Pleroma.Constants.as_public()],
         "cc" => ["https://example.com/users/lain/followers"]
diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
index e0d642910..99ad87d05 100644
--- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
@@ -1209,15 +1209,15 @@ test "returns 404 error for a wrong id", %{conn: conn} do
     setup do: clear_config([:instance, :max_pinned_statuses], 1)
 
     test "pin status", %{conn: conn, user: user, activity: activity} do
-      id_str = to_string(activity.id)
+      id = activity.id
 
-      assert %{"id" => ^id_str, "pinned" => true} =
+      assert %{"id" => ^id, "pinned" => true} =
                conn
                |> put_req_header("content-type", "application/json")
                |> post("/api/v1/statuses/#{activity.id}/pin")
                |> json_response_and_validate_schema(200)
 
-      assert [%{"id" => ^id_str, "pinned" => true}] =
+      assert [%{"id" => ^id, "pinned" => true}] =
                conn
                |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
                |> json_response_and_validate_schema(200)
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index 9e9f1c86c..8807c2d14 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -924,7 +924,8 @@ def get("https://mastodon.social/users/lambadalambda/collections/featured", _, _
        body:
          File.read!("test/fixtures/users_mock/masto_featured.json")
          |> String.replace("{{domain}}", "mastodon.social")
-         |> String.replace("{{nickname}}", "lambadalambda")
+         |> String.replace("{{nickname}}", "lambadalambda"),
+       headers: activitypub_object_headers()
      }}
   end
 

From d1d2744ee3e6015064cf50ac5725bfe45b682466 Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Wed, 3 Mar 2021 15:41:05 +0300
Subject: [PATCH 100/339] featured_address valition in AddRemoveValidator

---
 .../web/activity_pub/object_validator.ex      |  2 +-
 .../object_validators/add_remove_validator.ex | 12 +++++-----
 .../web/activity_pub/transmogrifier.ex        |  7 ++++--
 lib/pleroma/web/common_api.ex                 | 13 +++++++----
 test/fixtures/users_mock/user.json            |  1 +
 .../activity_pub_controller_test.exs          | 22 +++++++++++++++++++
 .../web/activity_pub/transmogrifier_test.exs  | 11 ++++++++++
 7 files changed, 55 insertions(+), 13 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 14c3e8531..3ca9136aa 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -238,7 +238,7 @@ def validate(%{"type" => "Announce"} = object, meta) do
   def validate(%{"type" => type} = object, meta) when type in ~w(Add Remove) do
     with {:ok, object} <-
            object
-           |> AddRemoveValidator.cast_and_validate()
+           |> AddRemoveValidator.cast_and_validate(meta)
            |> Ecto.Changeset.apply_action(:insert) do
       object = stringify_keys(object)
       {:ok, object, meta}
diff --git a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
index 73d1c03f0..885282f32 100644
--- a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
@@ -22,28 +22,28 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do
     field(:cc, ObjectValidators.Recipients, default: [])
   end
 
-  def cast_and_validate(data) do
+  def cast_and_validate(data, meta) do
     data
     |> cast_data()
-    |> validate_data()
+    |> validate_data(meta)
   end
 
   defp cast_data(data) do
     cast(%__MODULE__{}, data, __schema__(:fields))
   end
 
-  defp validate_data(changeset) do
+  defp validate_data(changeset, meta) do
     changeset
     |> validate_required([:id, :target, :object, :actor, :type, :to, :cc])
     |> validate_inclusion(:type, ~w(Add Remove))
     |> validate_actor_presence()
-    |> validate_collection_belongs_to_actor()
+    |> validate_collection_belongs_to_actor(meta)
     |> validate_object_presence()
   end
 
-  defp validate_collection_belongs_to_actor(changeset) do
+  defp validate_collection_belongs_to_actor(changeset, meta) do
     validate_change(changeset, :target, fn :target, target ->
-      if String.starts_with?(target, changeset.changes[:actor]) do
+      if target == meta[:featured_address] do
         []
       else
         [target: "collection doesn't belong to actor"]
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index b662f5379..fa62e0db2 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -557,7 +557,7 @@ def handle_incoming(
   end
 
   def handle_incoming(%{"type" => type} = data, _options) when type in ~w(Add Remove) do
-    with {:ok, user} <- ObjectValidator.fetch_actor(data),
+    with {:ok, %User{} = user} <- ObjectValidator.fetch_actor(data),
          %Object{} <- Object.normalize(data["object"], fetch: true) do
       # Mastodon sends pin/unpin objects without id, to, cc fields
       data =
@@ -566,7 +566,10 @@ def handle_incoming(%{"type" => type} = data, _options) when type in ~w(Add Remo
         |> Map.put_new("to", [Pleroma.Constants.as_public()])
         |> Map.put_new("cc", [user.follower_address])
 
-      case Pipeline.common_pipeline(data, local: false) do
+      case Pipeline.common_pipeline(data,
+             local: false,
+             featured_address: user.featured_address
+           ) do
         {:ok, activity, _meta} -> {:ok, activity}
         error -> error
       end
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
index d35a0f219..175d690cc 100644
--- a/lib/pleroma/web/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -412,14 +412,18 @@ def post(user, %{status: _} = data) do
   end
 
   @spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()}
-  def pin(id, %User{ap_id: actor} = user) do
+  def pin(id, %User{} = user) do
     with %Activity{} = activity <- create_activity_by_id(id),
-         true <- activity_belongs_to_actor(activity, actor),
+         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
+           Pipeline.common_pipeline(pin_data,
+             local: true,
+             activity_id: id,
+             featured_address: user.featured_address
+           ) do
       {:ok, activity}
     else
       {:error, {:execute_side_effects, error}} -> error
@@ -456,7 +460,8 @@ def unpin(id, user) do
            Pipeline.common_pipeline(unpin_data,
              local: true,
              activity_id: activity.id,
-             expires_at: activity.data["expires_at"]
+             expires_at: activity.data["expires_at"],
+             featured_address: user.featured_address
            ) do
       {:ok, activity}
     end
diff --git a/test/fixtures/users_mock/user.json b/test/fixtures/users_mock/user.json
index da2483d02..c722a1145 100644
--- a/test/fixtures/users_mock/user.json
+++ b/test/fixtures/users_mock/user.json
@@ -34,6 +34,7 @@
     "owner": "https://example.com/users/{{nickname}}",
     "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5DLtwGXNZElJyxFGfcVc\nXANhaMadj/iYYQwZjOJTV9QsbtiNBeIK54PJrYuU0/0YIdrvS1iqheX5IwXRhcwa\nhm3ZyLz7XeN9st7FBni4BmZMBtMpxAuYuu5p/jbWy13qAiYOhPreCx0wrWgm/lBD\n9mkgaxIxPooBE0S4ZWEJIDIV1Vft3AWcRUyWW1vIBK0uZzs6GYshbQZB952S0yo4\nFzI1hABGHncH8UvuFauh4EZ8tY7/X5I0pGRnDOcRN1dAht5w5yTA+6r5kebiFQjP\nIzN/eCO/a9Flrj9YGW7HDNtjSOH0A31PLRGlJtJO3yK57dnf5ppyCZGfL4emShQo\ncQIDAQAB\n-----END PUBLIC KEY-----\n\n"
   },
+  "featured": "https://example.com/users/{{nickname}}/collections/featured",
   "summary": "your friendly neighborhood pleroma developer<br>I like cute things and distributed systems, and really hate delete and redrafts",
   "tag": [],
   "type": "Person",
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index d9fa25d94..cea4b3a97 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -673,6 +673,17 @@ test "accepts Add/Remove activities", %{conn: conn} do
             body: user,
             headers: [{"content-type", "application/activity+json"}]
           }
+
+        %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
+          %Tesla.Env{
+            status: 200,
+            body:
+              "test/fixtures/users_mock/masto_featured.json"
+              |> File.read!()
+              |> String.replace("{{domain}}", "example.com")
+              |> String.replace("{{nickname}}", "lain"),
+            headers: [{"content-type", "application/activity+json"}]
+          }
       end)
 
       data = %{
@@ -753,6 +764,17 @@ test "mastodon pin/unpin", %{conn: conn} do
             body: user,
             headers: [{"content-type", "application/activity+json"}]
           }
+
+        %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
+          %Tesla.Env{
+            status: 200,
+            body:
+              "test/fixtures/users_mock/masto_featured.json"
+              |> File.read!()
+              |> String.replace("{{domain}}", "example.com")
+              |> String.replace("{{nickname}}", "lain"),
+            headers: [{"content-type", "application/activity+json"}]
+          }
       end)
 
       data = %{
diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs
index 9bc27f89e..fb8284aaf 100644
--- a/test/pleroma/web/activity_pub/transmogrifier_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs
@@ -147,6 +147,17 @@ test "it accepts Add/Remove activities" do
             body: object,
             headers: [{"content-type", "application/activity+json"}]
           }
+
+        %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
+          %Tesla.Env{
+            status: 200,
+            body:
+              "test/fixtures/users_mock/masto_featured.json"
+              |> File.read!()
+              |> String.replace("{{domain}}", "example.com")
+              |> String.replace("{{nickname}}", "lain"),
+            headers: [{"content-type", "application/activity+json"}]
+          }
       end)
 
       message = %{

From 3adb43cc20751540ea590645b31b985807684202 Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Wed, 3 Mar 2021 18:04:06 +0300
Subject: [PATCH 101/339] refetch user on incoming add/remove activity

if featured_address is nil
---
 .../web/activity_pub/transmogrifier.ex        |  8 ++
 .../web/mastodon_api/views/status_view.ex     |  2 +-
 .../web/activity_pub/transmogrifier_test.exs  | 78 +++++++++++++++++++
 test/support/factory.ex                       |  4 +-
 4 files changed, 90 insertions(+), 2 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index fa62e0db2..c4b11a655 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -558,6 +558,8 @@ def handle_incoming(
 
   def handle_incoming(%{"type" => type} = data, _options) when type in ~w(Add Remove) do
     with {:ok, %User{} = user} <- ObjectValidator.fetch_actor(data),
+         # maybe locally user doesn't have featured_address
+         {:ok, user} <- maybe_refetch_user(user),
          %Object{} <- Object.normalize(data["object"], fetch: true) do
       # Mastodon sends pin/unpin objects without id, to, cc fields
       data =
@@ -669,6 +671,12 @@ def handle_incoming(
 
   def handle_incoming(_, _), do: :error
 
+  defp maybe_refetch_user(%User{featured_address: address} = user) when is_binary(address) do
+    {:ok, user}
+  end
+
+  defp maybe_refetch_user(%User{ap_id: ap_id}), do: upgrade_user_from_ap_id(ap_id)
+
   @spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
   def get_obj_helper(id, options \\ []) do
     options = Keyword.put(options, :fetch, true)
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index d0247fa4a..814b3d142 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -152,7 +152,7 @@ def render(
       |> Enum.filter(& &1)
       |> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
 
-    {pinned?, pinned_at} = pin_data(activity_object, user)
+    {pinned?, pinned_at} = pin_data(object, user)
 
     %{
       id: to_string(activity.id),
diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs
index fb8284aaf..07ed3920f 100644
--- a/test/pleroma/web/activity_pub/transmogrifier_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs
@@ -191,6 +191,84 @@ test "it accepts Add/Remove activities" do
       user = refresh_record(user)
       refute user.pinned_objects[object_url]
     end
+
+    test "Add/Remove activities for remote users without featured address" do
+      user = insert(:user, local: false, domain: "example.com")
+
+      user =
+        user
+        |> Ecto.Changeset.change(featured_address: nil)
+        |> Repo.update!()
+
+      %{host: host} = URI.parse(user.ap_id)
+
+      user_data =
+        "test/fixtures/users_mock/user.json"
+        |> File.read!()
+        |> String.replace("{{nickname}}", user.nickname)
+
+      object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
+
+      object =
+        "test/fixtures/statuses/note.json"
+        |> File.read!()
+        |> String.replace("{{nickname}}", user.nickname)
+        |> String.replace("{{object_id}}", object_id)
+
+      object_url = "https://#{host}/objects/#{object_id}"
+
+      actor = "https://#{host}/users/#{user.nickname}"
+
+      featured = "https://#{host}/users/#{user.nickname}/collections/featured"
+
+      Tesla.Mock.mock(fn
+        %{
+          method: :get,
+          url: ^actor
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: user_data,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+
+        %{
+          method: :get,
+          url: ^object_url
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: object,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+
+        %{method: :get, url: ^featured} ->
+          %Tesla.Env{
+            status: 200,
+            body:
+              "test/fixtures/users_mock/masto_featured.json"
+              |> File.read!()
+              |> String.replace("{{domain}}", "#{host}")
+              |> String.replace("{{nickname}}", user.nickname),
+            headers: [{"content-type", "application/activity+json"}]
+          }
+      end)
+
+      message = %{
+        "id" => "https://#{host}/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
+        "actor" => actor,
+        "object" => object_url,
+        "target" => "https://#{host}/users/#{user.nickname}/collections/featured",
+        "type" => "Add",
+        "to" => [Pleroma.Constants.as_public()],
+        "cc" => ["https://#{host}/users/#{user.nickname}/followers"]
+      }
+
+      assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+      assert activity.data == message
+      user = User.get_cached_by_ap_id(actor)
+      assert user.pinned_objects[object_url]
+    end
   end
 
   describe "prepare outgoing" do
diff --git a/test/support/factory.ex b/test/support/factory.ex
index 883cedf3c..867076d6a 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -41,7 +41,7 @@ def user_factory(attrs \\ %{}) do
 
     urls =
       if attrs[:local] == false do
-        base_domain = Enum.random(["domain1.com", "domain2.com", "domain3.com"])
+        base_domain = attrs[:domain] || Enum.random(["domain1.com", "domain2.com", "domain3.com"])
 
         ap_id = "https://#{base_domain}/users/#{user.nickname}"
 
@@ -60,6 +60,8 @@ def user_factory(attrs \\ %{}) do
         }
       end
 
+    attrs = Map.delete(attrs, :domain)
+
     user
     |> Map.put(:raw_bio, user.bio)
     |> Map.merge(urls)

From 16c96966e9f7a039a969c06bdd6c4e18ab8d432c Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Tue, 9 Mar 2021 08:59:50 +0300
Subject: [PATCH 102/339] not needed

---
 test/fixtures/masto_pin.json | 41 ------------------------------------
 1 file changed, 41 deletions(-)
 delete mode 100644 test/fixtures/masto_pin.json

diff --git a/test/fixtures/masto_pin.json b/test/fixtures/masto_pin.json
deleted file mode 100644
index e57a34375..000000000
--- a/test/fixtures/masto_pin.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
-  "@context": [
-    "https://www.w3.org/ns/activitystreams",
-    "https://w3id.org/security/v1",
-    {
-      "Emoji": "toot:Emoji",
-      "Hashtag": "as:Hashtag",
-      "PropertyValue": "schema:PropertyValue",
-      "alsoKnownAs": {
-        "@id": "as:alsoKnownAs",
-        "@type": "@id"
-      },
-      "atomUri": "ostatus:atomUri",
-      "conversation": "ostatus:conversation",
-      "featured": {
-        "@id": "toot:featured",
-        "@type": "@id"
-      },
-      "focalPoint": {
-        "@container": "@list",
-        "@id": "toot:focalPoint"
-      },
-      "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
-      "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
-      "movedTo": {
-        "@id": "as:movedTo",
-        "@type": "@id"
-      },
-      "ostatus": "http://ostatus.org#",
-      "schema": "http://schema.org#",
-      "sensitive": "as:sensitive",
-      "toot": "http://joinmastodon.org/ns#",
-      "value": "schema:value"
-    }
-  ],
-  "id": "https://example.com/users/nickname/statuses/{{id}}",
-  "actor": "https://example.com/users/nickname",
-  "object": "https://example.com/users/nickname/statuses/101355175004496751",
-  "target": "https://example.com/users/nickname/collections/featured",
-  "type": "{{type}}"
-}

From 8f0778166c2e7c76975d14937ef61c05d399b560 Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Tue, 9 Mar 2021 09:00:20 +0300
Subject: [PATCH 103/339] moving fixture into mastodon folder

---
 test/fixtures/{ => mastodon}/collections/featured.json | 0
 test/pleroma/web/activity_pub/activity_pub_test.exs    | 2 +-
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename test/fixtures/{ => mastodon}/collections/featured.json (100%)

diff --git a/test/fixtures/collections/featured.json b/test/fixtures/mastodon/collections/featured.json
similarity index 100%
rename from test/fixtures/collections/featured.json
rename to test/fixtures/mastodon/collections/featured.json
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index 081d00d45..64e12066e 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -252,7 +252,7 @@ test "fetches user featured collection" do
       object_id = Ecto.UUID.generate()
 
       featured_data =
-        "test/fixtures/collections/featured.json"
+        "test/fixtures/mastodon/collections/featured.json"
         |> File.read!()
         |> String.replace("{{domain}}", "example.com")
         |> String.replace("{{nickname}}", "lain")

From 5ae9b05600dd3dffc628ba25fe01b271f7bc0122 Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Tue, 9 Mar 2021 09:00:44 +0300
Subject: [PATCH 104/339] separate test file for featured collection

---
 .../add_remove_handling_test.exs              | 172 ++++++++++++++++++
 .../web/activity_pub/transmogrifier_test.exs  | 163 -----------------
 2 files changed, 172 insertions(+), 163 deletions(-)
 create mode 100644 test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs

diff --git a/test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs
new file mode 100644
index 000000000..fc7757125
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs
@@ -0,0 +1,172 @@
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.AddRemoveHandlingTest do
+  use Oban.Testing, repo: Pleroma.Repo
+  use Pleroma.DataCase, async: true
+
+  require Pleroma.Constants
+
+  import Pleroma.Factory
+
+  alias Pleroma.User
+  alias Pleroma.Web.ActivityPub.Transmogrifier
+
+  test "it accepts Add/Remove activities" do
+    user =
+      "test/fixtures/users_mock/user.json"
+      |> File.read!()
+      |> String.replace("{{nickname}}", "lain")
+
+    object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
+
+    object =
+      "test/fixtures/statuses/note.json"
+      |> File.read!()
+      |> String.replace("{{nickname}}", "lain")
+      |> String.replace("{{object_id}}", object_id)
+
+    object_url = "https://example.com/objects/#{object_id}"
+
+    actor = "https://example.com/users/lain"
+
+    Tesla.Mock.mock(fn
+      %{
+        method: :get,
+        url: ^actor
+      } ->
+        %Tesla.Env{
+          status: 200,
+          body: user,
+          headers: [{"content-type", "application/activity+json"}]
+        }
+
+      %{
+        method: :get,
+        url: ^object_url
+      } ->
+        %Tesla.Env{
+          status: 200,
+          body: object,
+          headers: [{"content-type", "application/activity+json"}]
+        }
+
+      %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
+        %Tesla.Env{
+          status: 200,
+          body:
+            "test/fixtures/users_mock/masto_featured.json"
+            |> File.read!()
+            |> String.replace("{{domain}}", "example.com")
+            |> String.replace("{{nickname}}", "lain"),
+          headers: [{"content-type", "application/activity+json"}]
+        }
+    end)
+
+    message = %{
+      "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
+      "actor" => actor,
+      "object" => object_url,
+      "target" => "https://example.com/users/lain/collections/featured",
+      "type" => "Add",
+      "to" => [Pleroma.Constants.as_public()],
+      "cc" => ["https://example.com/users/lain/followers"]
+    }
+
+    assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+    assert activity.data == message
+    user = User.get_cached_by_ap_id(actor)
+    assert user.pinned_objects[object_url]
+
+    remove = %{
+      "id" => "http://localhost:400/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
+      "actor" => actor,
+      "object" => object_url,
+      "target" => "https://example.com/users/lain/collections/featured",
+      "type" => "Remove",
+      "to" => [Pleroma.Constants.as_public()],
+      "cc" => ["https://example.com/users/lain/followers"]
+    }
+
+    assert {:ok, activity} = Transmogrifier.handle_incoming(remove)
+    assert activity.data == remove
+
+    user = refresh_record(user)
+    refute user.pinned_objects[object_url]
+  end
+
+  test "Add/Remove activities for remote users without featured address" do
+    user = insert(:user, local: false, domain: "example.com")
+
+    user =
+      user
+      |> Ecto.Changeset.change(featured_address: nil)
+      |> Repo.update!()
+
+    %{host: host} = URI.parse(user.ap_id)
+
+    user_data =
+      "test/fixtures/users_mock/user.json"
+      |> File.read!()
+      |> String.replace("{{nickname}}", user.nickname)
+
+    object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
+
+    object =
+      "test/fixtures/statuses/note.json"
+      |> File.read!()
+      |> String.replace("{{nickname}}", user.nickname)
+      |> String.replace("{{object_id}}", object_id)
+
+    object_url = "https://#{host}/objects/#{object_id}"
+
+    actor = "https://#{host}/users/#{user.nickname}"
+
+    featured = "https://#{host}/users/#{user.nickname}/collections/featured"
+
+    Tesla.Mock.mock(fn
+      %{
+        method: :get,
+        url: ^actor
+      } ->
+        %Tesla.Env{
+          status: 200,
+          body: user_data,
+          headers: [{"content-type", "application/activity+json"}]
+        }
+
+      %{
+        method: :get,
+        url: ^object_url
+      } ->
+        %Tesla.Env{
+          status: 200,
+          body: object,
+          headers: [{"content-type", "application/activity+json"}]
+        }
+
+      %{method: :get, url: ^featured} ->
+        %Tesla.Env{
+          status: 200,
+          body:
+            "test/fixtures/users_mock/masto_featured.json"
+            |> File.read!()
+            |> String.replace("{{domain}}", "#{host}")
+            |> String.replace("{{nickname}}", user.nickname),
+          headers: [{"content-type", "application/activity+json"}]
+        }
+    end)
+
+    message = %{
+      "id" => "https://#{host}/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
+      "actor" => actor,
+      "object" => object_url,
+      "target" => "https://#{host}/users/#{user.nickname}/collections/featured",
+      "type" => "Add",
+      "to" => [Pleroma.Constants.as_public()],
+      "cc" => ["https://#{host}/users/#{user.nickname}/followers"]
+    }
+
+    assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+    assert activity.data == message
+    user = User.get_cached_by_ap_id(actor)
+    assert user.pinned_objects[object_url]
+  end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs
index 07ed3920f..4c3fcb44a 100644
--- a/test/pleroma/web/activity_pub/transmogrifier_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs
@@ -6,8 +6,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
   use Oban.Testing, repo: Pleroma.Repo
   use Pleroma.DataCase
 
-  require Pleroma.Constants
-
   alias Pleroma.Activity
   alias Pleroma.Object
   alias Pleroma.Tests.ObanHelpers
@@ -108,167 +106,6 @@ test "it accepts Move activities" do
       assert activity.data["target"] == new_user.ap_id
       assert activity.data["type"] == "Move"
     end
-
-    test "it accepts Add/Remove activities" do
-      user =
-        "test/fixtures/users_mock/user.json"
-        |> File.read!()
-        |> String.replace("{{nickname}}", "lain")
-
-      object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
-
-      object =
-        "test/fixtures/statuses/note.json"
-        |> File.read!()
-        |> String.replace("{{nickname}}", "lain")
-        |> String.replace("{{object_id}}", object_id)
-
-      object_url = "https://example.com/objects/#{object_id}"
-
-      actor = "https://example.com/users/lain"
-
-      Tesla.Mock.mock(fn
-        %{
-          method: :get,
-          url: ^actor
-        } ->
-          %Tesla.Env{
-            status: 200,
-            body: user,
-            headers: [{"content-type", "application/activity+json"}]
-          }
-
-        %{
-          method: :get,
-          url: ^object_url
-        } ->
-          %Tesla.Env{
-            status: 200,
-            body: object,
-            headers: [{"content-type", "application/activity+json"}]
-          }
-
-        %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
-          %Tesla.Env{
-            status: 200,
-            body:
-              "test/fixtures/users_mock/masto_featured.json"
-              |> File.read!()
-              |> String.replace("{{domain}}", "example.com")
-              |> String.replace("{{nickname}}", "lain"),
-            headers: [{"content-type", "application/activity+json"}]
-          }
-      end)
-
-      message = %{
-        "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
-        "actor" => actor,
-        "object" => object_url,
-        "target" => "https://example.com/users/lain/collections/featured",
-        "type" => "Add",
-        "to" => [Pleroma.Constants.as_public()],
-        "cc" => ["https://example.com/users/lain/followers"]
-      }
-
-      assert {:ok, activity} = Transmogrifier.handle_incoming(message)
-      assert activity.data == message
-      user = User.get_cached_by_ap_id(actor)
-      assert user.pinned_objects[object_url]
-
-      remove = %{
-        "id" => "http://localhost:400/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
-        "actor" => actor,
-        "object" => object_url,
-        "target" => "https://example.com/users/lain/collections/featured",
-        "type" => "Remove",
-        "to" => [Pleroma.Constants.as_public()],
-        "cc" => ["https://example.com/users/lain/followers"]
-      }
-
-      assert {:ok, activity} = Transmogrifier.handle_incoming(remove)
-      assert activity.data == remove
-
-      user = refresh_record(user)
-      refute user.pinned_objects[object_url]
-    end
-
-    test "Add/Remove activities for remote users without featured address" do
-      user = insert(:user, local: false, domain: "example.com")
-
-      user =
-        user
-        |> Ecto.Changeset.change(featured_address: nil)
-        |> Repo.update!()
-
-      %{host: host} = URI.parse(user.ap_id)
-
-      user_data =
-        "test/fixtures/users_mock/user.json"
-        |> File.read!()
-        |> String.replace("{{nickname}}", user.nickname)
-
-      object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
-
-      object =
-        "test/fixtures/statuses/note.json"
-        |> File.read!()
-        |> String.replace("{{nickname}}", user.nickname)
-        |> String.replace("{{object_id}}", object_id)
-
-      object_url = "https://#{host}/objects/#{object_id}"
-
-      actor = "https://#{host}/users/#{user.nickname}"
-
-      featured = "https://#{host}/users/#{user.nickname}/collections/featured"
-
-      Tesla.Mock.mock(fn
-        %{
-          method: :get,
-          url: ^actor
-        } ->
-          %Tesla.Env{
-            status: 200,
-            body: user_data,
-            headers: [{"content-type", "application/activity+json"}]
-          }
-
-        %{
-          method: :get,
-          url: ^object_url
-        } ->
-          %Tesla.Env{
-            status: 200,
-            body: object,
-            headers: [{"content-type", "application/activity+json"}]
-          }
-
-        %{method: :get, url: ^featured} ->
-          %Tesla.Env{
-            status: 200,
-            body:
-              "test/fixtures/users_mock/masto_featured.json"
-              |> File.read!()
-              |> String.replace("{{domain}}", "#{host}")
-              |> String.replace("{{nickname}}", user.nickname),
-            headers: [{"content-type", "application/activity+json"}]
-          }
-      end)
-
-      message = %{
-        "id" => "https://#{host}/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
-        "actor" => actor,
-        "object" => object_url,
-        "target" => "https://#{host}/users/#{user.nickname}/collections/featured",
-        "type" => "Add",
-        "to" => [Pleroma.Constants.as_public()],
-        "cc" => ["https://#{host}/users/#{user.nickname}/followers"]
-      }
-
-      assert {:ok, activity} = Transmogrifier.handle_incoming(message)
-      assert activity.data == message
-      user = User.get_cached_by_ap_id(actor)
-      assert user.pinned_objects[object_url]
-    end
   end
 
   describe "prepare outgoing" do

From 8857242c952dcac0bc5363e1c80160efaf7a1638 Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Tue, 9 Mar 2021 11:57:20 +0300
Subject: [PATCH 105/339] removeing corresponding add activity

---
 lib/pleroma/activity.ex                      |  9 +++++
 lib/pleroma/web/activity_pub/side_effects.ex |  5 +++
 test/pleroma/activity_test.exs               | 22 ++++++++++
 test/support/factory.ex                      | 42 ++++++++++++++++++++
 4 files changed, 78 insertions(+)

diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index a4cfca4c5..53beca5e6 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -391,4 +391,13 @@ def get_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
   end
 
   def get_by_object_ap_id_with_object(_), do: nil
+
+  @spec add_by_params_query(String.t(), String.t(), String.t()) :: Ecto.Query.t()
+  def add_by_params_query(object_id, actor, target) do
+    object_id
+    |> Queries.by_object_id()
+    |> Queries.by_type("Add")
+    |> Queries.by_actor(actor)
+    |> where([a], fragment("?->>'target' = ?", a.data, ^target))
+  end
 end
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 9d22f9d3c..5fe143c2b 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -340,11 +340,16 @@ def handle(%{data: %{"type" => "Add"} = data} = object, meta) do
 
   # Tasks this handles:
   # - removes pin from user
+  # - removes corresponding Add activity
   # - if activity had expiration, recreates activity expiration job
   @impl true
   def handle(%{data: %{"type" => "Remove"} = data} = object, meta) do
     with %User{} = user <- User.get_cached_by_ap_id(data["actor"]),
          {:ok, _user} <- User.remove_pinned_object_id(user, data["object"]) do
+      data["object"]
+      |> Activity.add_by_params_query(user.ap_id, user.featured_address)
+      |> Repo.delete_all()
+
       # if pinned activity was scheduled for deletion, we reschedule it for deletion
       if meta[:expires_at] do
         # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation
diff --git a/test/pleroma/activity_test.exs b/test/pleroma/activity_test.exs
index 390a06344..962bc7e45 100644
--- a/test/pleroma/activity_test.exs
+++ b/test/pleroma/activity_test.exs
@@ -254,4 +254,26 @@ test "get_by_object_ap_id_with_object/1" do
 
     assert %{id: ^id} = Activity.get_by_object_ap_id_with_object(obj_id)
   end
+
+  test "add_by_params_query/3" do
+    user = insert(:user)
+
+    note = insert(:note_activity, user: user)
+
+    insert(:add_activity, user: user, note: note)
+    insert(:add_activity, user: user, note: note)
+    insert(:add_activity, user: user)
+
+    assert Repo.aggregate(Activity, :count, :id) == 4
+
+    add_query =
+      Activity.add_by_params_query(note.data["object"], user.ap_id, user.featured_address)
+
+    assert Repo.aggregate(add_query, :count, :id) == 2
+
+    Repo.delete_all(add_query)
+    assert Repo.aggregate(add_query, :count, :id) == 0
+
+    assert Repo.aggregate(Activity, :count, :id) == 2
+  end
 end
diff --git a/test/support/factory.ex b/test/support/factory.ex
index 867076d6a..5c4e65c81 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -4,6 +4,9 @@
 
 defmodule Pleroma.Factory do
   use ExMachina.Ecto, repo: Pleroma.Repo
+
+  require Pleroma.Constants
+
   alias Pleroma.Object
   alias Pleroma.User
 
@@ -225,6 +228,45 @@ def direct_note_activity_factory do
     }
   end
 
+  def add_activity_factory(attrs \\ %{}) do
+    featured_collection_activity(attrs, "Add")
+  end
+
+  def remove_activity_factor(attrs \\ %{}) do
+    featured_collection_activity(attrs, "Remove")
+  end
+
+  defp featured_collection_activity(attrs, type) do
+    user = attrs[:user] || insert(:user)
+    note = attrs[:note] || insert(:note, user: user)
+
+    data_attrs =
+      attrs
+      |> Map.get(:data_attrs, %{})
+      |> Map.put(:type, type)
+
+    attrs = Map.drop(attrs, [:user, :note, :data_attrs])
+
+    data =
+      %{
+        "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
+        "target" => user.featured_address,
+        "object" => note.data["object"],
+        "actor" => note.data["actor"],
+        "type" => "Add",
+        "to" => [Pleroma.Constants.as_public()],
+        "cc" => [user.follower_address]
+      }
+      |> Map.merge(data_attrs)
+
+    %Pleroma.Activity{
+      data: data,
+      actor: data["actor"],
+      recipients: data["to"]
+    }
+    |> Map.merge(attrs)
+  end
+
   def note_activity_factory(attrs \\ %{}) do
     user = attrs[:user] || insert(:user)
     note = attrs[:note] || insert(:note, user: user)

From 2a520ba008f432e7e1fa297954966e0181245f01 Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Fri, 19 Mar 2021 17:25:12 +0300
Subject: [PATCH 106/339] expanding AddRemoveValidator

---
 .../web/activity_pub/object_validator.ex      |  2 +-
 .../object_validators/add_remove_validator.ex | 26 ++++++++++++++-----
 .../web/activity_pub/transmogrifier.ex        | 22 ++++------------
 lib/pleroma/web/common_api.ex                 |  3 +--
 4 files changed, 27 insertions(+), 26 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 3ca9136aa..14c3e8531 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -238,7 +238,7 @@ def validate(%{"type" => "Announce"} = object, meta) do
   def validate(%{"type" => type} = object, meta) when type in ~w(Add Remove) do
     with {:ok, object} <-
            object
-           |> AddRemoveValidator.cast_and_validate(meta)
+           |> AddRemoveValidator.cast_and_validate()
            |> Ecto.Changeset.apply_action(:insert) do
       object = stringify_keys(object)
       {:ok, object, meta}
diff --git a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
index 885282f32..c38f86a0e 100644
--- a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
@@ -8,6 +8,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do
   import Ecto.Changeset
   import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
 
+  require Pleroma.Constants
+
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
 
   @primary_key false
@@ -22,28 +24,40 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do
     field(:cc, ObjectValidators.Recipients, default: [])
   end
 
-  def cast_and_validate(data, meta) do
+  def cast_and_validate(data) do
     data
+    |> maybe_fix_data_for_mastodon()
     |> cast_data()
-    |> validate_data(meta)
+    |> validate_data()
+  end
+
+  defp maybe_fix_data_for_mastodon(data) do
+    {:ok, actor} = Pleroma.User.get_or_fetch_by_ap_id(data["actor"])
+    # Mastodon sends pin/unpin objects without id, to, cc fields
+    data
+    |> Map.put_new("id", Pleroma.Web.ActivityPub.Utils.generate_activity_id())
+    |> Map.put_new("to", [Pleroma.Constants.as_public()])
+    |> Map.put_new("cc", [actor.follower_address])
   end
 
   defp cast_data(data) do
     cast(%__MODULE__{}, data, __schema__(:fields))
   end
 
-  defp validate_data(changeset, meta) do
+  defp validate_data(changeset) do
     changeset
     |> validate_required([:id, :target, :object, :actor, :type, :to, :cc])
     |> validate_inclusion(:type, ~w(Add Remove))
     |> validate_actor_presence()
-    |> validate_collection_belongs_to_actor(meta)
+    |> validate_collection_belongs_to_actor()
     |> validate_object_presence()
   end
 
-  defp validate_collection_belongs_to_actor(changeset, meta) do
+  defp validate_collection_belongs_to_actor(changeset) do
+    {:ok, actor} = Pleroma.User.get_or_fetch_by_ap_id(changeset.changes[:actor])
+
     validate_change(changeset, :target, fn :target, target ->
-      if target == meta[:featured_address] do
+      if target == actor.featured_address do
         []
       else
         [target: "collection doesn't belong to actor"]
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index c4b11a655..2172e7736 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -557,24 +557,12 @@ def handle_incoming(
   end
 
   def handle_incoming(%{"type" => type} = data, _options) when type in ~w(Add Remove) do
-    with {:ok, %User{} = user} <- ObjectValidator.fetch_actor(data),
+    with :ok <- ObjectValidator.fetch_actor_and_object(data),
+         {:ok, actor} <- Pleroma.User.get_or_fetch_by_ap_id(data["actor"]),
          # maybe locally user doesn't have featured_address
-         {:ok, user} <- maybe_refetch_user(user),
-         %Object{} <- Object.normalize(data["object"], fetch: true) do
-      # Mastodon sends pin/unpin objects without id, to, cc fields
-      data =
-        data
-        |> Map.put_new("id", Utils.generate_activity_id())
-        |> Map.put_new("to", [Pleroma.Constants.as_public()])
-        |> Map.put_new("cc", [user.follower_address])
-
-      case Pipeline.common_pipeline(data,
-             local: false,
-             featured_address: user.featured_address
-           ) do
-        {:ok, activity, _meta} -> {:ok, activity}
-        error -> error
-      end
+         {:ok, _} <- maybe_refetch_user(actor),
+         {:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do
+      {:ok, activity}
     end
   end
 
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
index 175d690cc..b36be4d2a 100644
--- a/lib/pleroma/web/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -421,8 +421,7 @@ def pin(id, %User{} = user) do
          {:ok, _pin, _} <-
            Pipeline.common_pipeline(pin_data,
              local: true,
-             activity_id: id,
-             featured_address: user.featured_address
+             activity_id: id
            ) do
       {:ok, activity}
     else

From 1885268c9c242aca2a51bd15ed839bd65d6a52dc Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Thu, 25 Mar 2021 13:26:54 +0300
Subject: [PATCH 107/339] expanding validator

---
 .../object_validators/add_remove_validator.ex | 28 +++++++++++++------
 .../web/activity_pub/transmogrifier.ex        | 18 +-----------
 2 files changed, 20 insertions(+), 26 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
index c38f86a0e..f885aabe4 100644
--- a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
@@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do
   require Pleroma.Constants
 
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
+  alias Pleroma.User
 
   @primary_key false
 
@@ -25,14 +26,17 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do
   end
 
   def cast_and_validate(data) do
+    {:ok, actor} = User.get_or_fetch_by_ap_id(data["actor"])
+
+    {:ok, actor} = maybe_refetch_user(actor)
+
     data
-    |> maybe_fix_data_for_mastodon()
+    |> maybe_fix_data_for_mastodon(actor)
     |> cast_data()
-    |> validate_data()
+    |> validate_data(actor)
   end
 
-  defp maybe_fix_data_for_mastodon(data) do
-    {:ok, actor} = Pleroma.User.get_or_fetch_by_ap_id(data["actor"])
+  defp maybe_fix_data_for_mastodon(data, actor) do
     # Mastodon sends pin/unpin objects without id, to, cc fields
     data
     |> Map.put_new("id", Pleroma.Web.ActivityPub.Utils.generate_activity_id())
@@ -44,18 +48,16 @@ defp cast_data(data) do
     cast(%__MODULE__{}, data, __schema__(:fields))
   end
 
-  defp validate_data(changeset) do
+  defp validate_data(changeset, actor) do
     changeset
     |> validate_required([:id, :target, :object, :actor, :type, :to, :cc])
     |> validate_inclusion(:type, ~w(Add Remove))
     |> validate_actor_presence()
-    |> validate_collection_belongs_to_actor()
+    |> validate_collection_belongs_to_actor(actor)
     |> validate_object_presence()
   end
 
-  defp validate_collection_belongs_to_actor(changeset) do
-    {:ok, actor} = Pleroma.User.get_or_fetch_by_ap_id(changeset.changes[:actor])
-
+  defp validate_collection_belongs_to_actor(changeset, actor) do
     validate_change(changeset, :target, fn :target, target ->
       if target == actor.featured_address do
         []
@@ -64,4 +66,12 @@ defp validate_collection_belongs_to_actor(changeset) do
       end
     end)
   end
+
+  defp maybe_refetch_user(%User{featured_address: address} = user) when is_binary(address) do
+    {:ok, user}
+  end
+
+  defp maybe_refetch_user(%User{ap_id: ap_id}) do
+    Pleroma.Web.ActivityPub.Transmogrifier.upgrade_user_from_ap_id(ap_id)
+  end
 end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 2172e7736..c4caeff0a 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -534,7 +534,7 @@ def handle_incoming(
   end
 
   def handle_incoming(%{"type" => type} = data, _options)
-      when type in ~w{Like EmojiReact Announce} do
+      when type in ~w{Like EmojiReact Announce Add Remove} do
     with :ok <- ObjectValidator.fetch_actor_and_object(data),
          {:ok, activity, _meta} <-
            Pipeline.common_pipeline(data, local: false) do
@@ -556,16 +556,6 @@ def handle_incoming(
     end
   end
 
-  def handle_incoming(%{"type" => type} = data, _options) when type in ~w(Add Remove) do
-    with :ok <- ObjectValidator.fetch_actor_and_object(data),
-         {:ok, actor} <- Pleroma.User.get_or_fetch_by_ap_id(data["actor"]),
-         # maybe locally user doesn't have featured_address
-         {:ok, _} <- maybe_refetch_user(actor),
-         {:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do
-      {:ok, activity}
-    end
-  end
-
   def handle_incoming(
         %{"type" => "Delete"} = data,
         _options
@@ -659,12 +649,6 @@ def handle_incoming(
 
   def handle_incoming(_, _), do: :error
 
-  defp maybe_refetch_user(%User{featured_address: address} = user) when is_binary(address) do
-    {:ok, user}
-  end
-
-  defp maybe_refetch_user(%User{ap_id: ap_id}), do: upgrade_user_from_ap_id(ap_id)
-
   @spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
   def get_obj_helper(id, options \\ []) do
     options = Keyword.put(options, :fetch, true)

From 6e108b8603de45d489d4aef7e3e271bc5e8c431d Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Fri, 26 Mar 2021 19:19:19 +0300
Subject: [PATCH 108/339] reading the file, instead of config keyword

---
 lib/pleroma/config/release_runtime_provider.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/config/release_runtime_provider.ex b/lib/pleroma/config/release_runtime_provider.ex
index 8227195dc..70ef3bcc1 100644
--- a/lib/pleroma/config/release_runtime_provider.ex
+++ b/lib/pleroma/config/release_runtime_provider.ex
@@ -39,7 +39,7 @@ def load(config, _opts) do
 
     with_exported =
       if File.exists?(exported_config_path) do
-        exported_config = Config.Reader.read!(with_runtime_config)
+        exported_config = Config.Reader.read!(exported_config_path)
         Config.Reader.merge(with_runtime_config, exported_config)
       else
         with_runtime_config

From 4d046afd2769cfdc16b2ee48e8c1d8f7f8e8ffa7 Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Sat, 27 Mar 2021 09:05:33 +0300
Subject: [PATCH 109/339] tests for release config provider

---
 .../config/release_runtime_provider.ex        | 17 +++----
 mix.exs                                       | 13 +++++-
 .../config/temp.exported_from_db.secret.exs   |  5 ++
 .../config/release_runtime_provider_test.exs  | 46 +++++++++++++++++++
 4 files changed, 70 insertions(+), 11 deletions(-)
 create mode 100644 test/fixtures/config/temp.exported_from_db.secret.exs
 create mode 100644 test/pleroma/config/release_runtime_provider_test.exs

diff --git a/lib/pleroma/config/release_runtime_provider.ex b/lib/pleroma/config/release_runtime_provider.ex
index 70ef3bcc1..46fa35559 100644
--- a/lib/pleroma/config/release_runtime_provider.ex
+++ b/lib/pleroma/config/release_runtime_provider.ex
@@ -1,6 +1,6 @@
 defmodule Pleroma.Config.ReleaseRuntimeProvider do
   @moduledoc """
-  Imports `runtime.exs` and `{env}.exported_from_db.secret.exs` for elixir releases.
+  Imports runtime config and `{env}.exported_from_db.secret.exs` for releases.
   """
   @behaviour Config.Provider
 
@@ -8,13 +8,13 @@ defmodule Pleroma.Config.ReleaseRuntimeProvider do
   def init(opts), do: opts
 
   @impl true
-  def load(config, _opts) do
+  def load(config, opts) do
     with_defaults = Config.Reader.merge(config, Pleroma.Config.Holder.release_defaults())
 
-    config_path = System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs"
+    config_path = opts[:config_path]
 
     with_runtime_config =
-      if File.exists?(config_path) do
+      if config_path && File.exists?(config_path) do
         runtime_config = Config.Reader.read!(config_path)
 
         with_defaults
@@ -24,7 +24,7 @@ def load(config, _opts) do
         warning = [
           IO.ANSI.red(),
           IO.ANSI.bright(),
-          "!!! #{config_path} not found! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file",
+          "!!! Config path is not declared! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file",
           IO.ANSI.reset()
         ]
 
@@ -32,13 +32,10 @@ def load(config, _opts) do
         with_defaults
       end
 
-    exported_config_path =
-      config_path
-      |> Path.dirname()
-      |> Path.join("prod.exported_from_db.secret.exs")
+    exported_config_path = opts[:exported_config_path]
 
     with_exported =
-      if File.exists?(exported_config_path) do
+      if exported_config_path && File.exists?(exported_config_path) do
         exported_config = Config.Reader.read!(exported_config_path)
         Config.Reader.merge(with_runtime_config, exported_config)
       else
diff --git a/mix.exs b/mix.exs
index ae74f50a3..7328b533b 100644
--- a/mix.exs
+++ b/mix.exs
@@ -38,7 +38,7 @@ def project do
           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, nil}]
+          config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, release_config_paths()}]
         ]
       ]
     ]
@@ -67,6 +67,17 @@ def copy_nginx_config(%{path: target_path} = release) do
     release
   end
 
+  defp release_config_paths do
+    config_path = System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs"
+
+    exported_config_path =
+      config_path
+      |> Path.dirname()
+      |> Path.join("#{Mix.env()}.exported_from_db.secret.exs")
+
+    [config_path: config_path, exported_config_path: exported_config_path]
+  end
+
   # Configuration for the OTP application.
   #
   # Type `mix help compile.app` for more information.
diff --git a/test/fixtures/config/temp.exported_from_db.secret.exs b/test/fixtures/config/temp.exported_from_db.secret.exs
new file mode 100644
index 000000000..64bee7f32
--- /dev/null
+++ b/test/fixtures/config/temp.exported_from_db.secret.exs
@@ -0,0 +1,5 @@
+use Mix.Config
+
+config :pleroma, exported_config_merged: true
+
+config :pleroma, :first_setting, key: "new value"
diff --git a/test/pleroma/config/release_runtime_provider_test.exs b/test/pleroma/config/release_runtime_provider_test.exs
new file mode 100644
index 000000000..1921698c5
--- /dev/null
+++ b/test/pleroma/config/release_runtime_provider_test.exs
@@ -0,0 +1,46 @@
+defmodule Pleroma.Config.ReleaseRuntimeProviderTest do
+  use ExUnit.Case, async: true
+
+  alias Pleroma.Config.ReleaseRuntimeProvider
+
+  describe "load/2" do
+    test "loads release defaults config and warns about non-existent runtime config" do
+      ExUnit.CaptureIO.capture_io(fn ->
+        merged = ReleaseRuntimeProvider.load([], [])
+        assert merged == Pleroma.Config.Holder.release_defaults()
+        IO.inspect(merged)
+      end) =~
+        "!!! Config path is not declared! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file"
+    end
+
+    test "merged runtime config" do
+      merged =
+        ReleaseRuntimeProvider.load([], config_path: "test/fixtures/config/temp.secret.exs")
+
+      assert merged[:pleroma][:first_setting] == [key: "value", key2: [Pleroma.Repo]]
+      assert merged[:pleroma][:second_setting] == [key: "value2", key2: ["Activity"]]
+    end
+
+    test "merged exported config" do
+      ExUnit.CaptureIO.capture_io(fn ->
+        merged =
+          ReleaseRuntimeProvider.load([],
+            exported_config_path: "test/fixtures/config/temp.exported_from_db.secret.exs"
+          )
+
+        assert merged[:pleroma][:exported_config_merged]
+      end) =~
+        "!!! Config path is not declared! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file"
+    end
+
+    test "runtime config is merged with exported config" do
+      merged =
+        ReleaseRuntimeProvider.load([],
+          config_path: "test/fixtures/config/temp.secret.exs",
+          exported_config_path: "test/fixtures/config/temp.exported_from_db.secret.exs"
+        )
+
+      assert merged[:pleroma][:first_setting] == [key2: [Pleroma.Repo], key: "new value"]
+    end
+  end
+end

From 8b81d6222773180c9632b7b53ebe7f5ee19f4f65 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Thu, 8 Oct 2020 11:55:35 -0500
Subject: [PATCH 110/339] Upstream original followbot implementation

---
 config/config.exs                             |  2 +
 .../web/activity_pub/mrf/follow_bot_policy.ex | 41 +++++++++++++++++++
 2 files changed, 43 insertions(+)
 create mode 100644 lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex

diff --git a/config/config.exs b/config/config.exs
index 8d1e17b42..4381068ac 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -409,6 +409,8 @@
   threshold: 604_800,
   actions: [:delist, :strip_followers]
 
+config :pleroma, :mrf_follow_bot, follower_nickname: nil
+
 config :pleroma, :rich_media,
   enabled: true,
   ignore_hosts: [],
diff --git a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
new file mode 100644
index 000000000..fb123dbd3
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
@@ -0,0 +1,41 @@
+defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
+  @behaviour Pleroma.Web.ActivityPub.MRF
+  alias Pleroma.User
+  alias Pleroma.Web.CommonAPI
+  require Logger
+
+  @impl true
+  def filter(message) do
+    Task.start(fn ->
+      follower_nickname = Pleroma.Config.get([:mrf_follow_bot, :follower_nickname])
+
+      with %User{} = follower <- User.get_cached_by_nickname(follower_nickname),
+           %{"type" => "Create", "object" => %{"type" => "Note"}} <- message do
+        to = Map.get(message, "to", [])
+        cc = Map.get(message, "cc", [])
+        actor = [message["actor"]]
+
+        Enum.concat([to, cc, actor])
+        |> List.flatten()
+        |> User.get_all_by_ap_id()
+        |> Enum.each(fn user ->
+          Logger.info("Checking if #{user.nickname} can be followed")
+
+          with false <- User.following?(follower, user),
+               false <- user.locked,
+               false <- (user.bio || "") |> String.downcase() |> String.contains?("nobot") do
+            Logger.info("Following #{user.nickname}")
+            CommonAPI.follow(follower, user)
+          end
+        end)
+      end
+    end)
+
+    {:ok, message}
+  end
+
+  @impl true
+  def describe do
+    {:ok, %{}}
+  end
+end

From fba770b3ea861d0fdf7811b61a297278a617136b Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Thu, 8 Oct 2020 12:09:31 -0500
Subject: [PATCH 111/339] Try to handle misconfiguration scenarios gracefully

---
 .../web/activity_pub/mrf/follow_bot_policy.ex | 55 ++++++++++++-------
 1 file changed, 35 insertions(+), 20 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
index fb123dbd3..52ac9aef7 100644
--- a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
@@ -1,34 +1,49 @@
 defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
   @behaviour Pleroma.Web.ActivityPub.MRF
+  alias Pleroma.Config
   alias Pleroma.User
   alias Pleroma.Web.CommonAPI
   require Logger
 
   @impl true
   def filter(message) do
+    with follower_nickname <- Config.get([:mrf_follow_bot, :follower_nickname]),
+         %User{} = follower <- User.get_cached_by_nickname(follower_nickname),
+         %{"type" => "Create", "object" => %{"type" => "Note"}} <- message do
+      try_follow(follower, message)
+    else
+      nil ->
+        Logger.warn(
+          "#{__MODULE__} skipped because of missing :mrf_follow_bot, :follower_nickname configuration or the account
+            does not exist."
+        )
+
+        {:ok, message}
+
+      _ ->
+        {:ok, message}
+    end
+  end
+
+  defp try_follow(follower, message) do
     Task.start(fn ->
-      follower_nickname = Pleroma.Config.get([:mrf_follow_bot, :follower_nickname])
+      to = Map.get(message, "to", [])
+      cc = Map.get(message, "cc", [])
+      actor = [message["actor"]]
 
-      with %User{} = follower <- User.get_cached_by_nickname(follower_nickname),
-           %{"type" => "Create", "object" => %{"type" => "Note"}} <- message do
-        to = Map.get(message, "to", [])
-        cc = Map.get(message, "cc", [])
-        actor = [message["actor"]]
+      Enum.concat([to, cc, actor])
+      |> List.flatten()
+      |> User.get_all_by_ap_id()
+      |> Enum.each(fn user ->
+        Logger.info("Checking if #{user.nickname} can be followed")
 
-        Enum.concat([to, cc, actor])
-        |> List.flatten()
-        |> User.get_all_by_ap_id()
-        |> Enum.each(fn user ->
-          Logger.info("Checking if #{user.nickname} can be followed")
-
-          with false <- User.following?(follower, user),
-               false <- user.locked,
-               false <- (user.bio || "") |> String.downcase() |> String.contains?("nobot") do
-            Logger.info("Following #{user.nickname}")
-            CommonAPI.follow(follower, user)
-          end
-        end)
-      end
+        with false <- User.following?(follower, user),
+             false <- user.locked,
+             false <- (user.bio || "") |> String.downcase() |> String.contains?("nobot") do
+          Logger.info("Following #{user.nickname}")
+          CommonAPI.follow(follower, user)
+        end
+      end)
     end)
 
     {:ok, message}

From 840dc4b44ba3ea2613b1a8dc110a9008ffc618c3 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 30 Mar 2021 11:10:34 -0500
Subject: [PATCH 112/339] Document :mrf_follow_bot

---
 docs/configuration/cheatsheet.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index 8f2c4347e..6e52cd181 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -124,6 +124,7 @@ To add configuration to your config file, you can copy it from the base config.
     * `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)).
     * `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled delections.
     * `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines.
+    * `Pleroma.Web.ActivityPub.MRF.FollowBotPolicy`: Automatically follows newly discovered users from the specified bot account.
 * `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
 * `transparency_exclusions`: Exclude specific instance names from MRF transparency.  The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
 
@@ -220,6 +221,11 @@ Notes:
 - The hashtags in the configuration do not have a leading `#`.
 - This MRF Policy is always enabled, if you want to disable it you have to set empty lists
 
+#### :mrf_follow_bot
+
+* `follower_nickname`: The name of the bot account to use for following newly discovered users.
+
+
 ### :activitypub
 * `unfollow_blocked`: Whether blocks result in people getting unfollowed
 * `outgoing_blocks`: Whether to federate blocks to other instances

From e78738173aefd512bbce33c12b4ee3372bdc904b Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Thu, 8 Oct 2020 12:41:01 -0500
Subject: [PATCH 113/339] Enforce that the followbot must be marked as a bot.

---
 lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
index 52ac9aef7..d10b7b480 100644
--- a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
@@ -8,14 +8,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
   @impl true
   def filter(message) do
     with follower_nickname <- Config.get([:mrf_follow_bot, :follower_nickname]),
-         %User{} = follower <- User.get_cached_by_nickname(follower_nickname),
+         %User{actor_type: "Service"} = follower <-
+           User.get_cached_by_nickname(follower_nickname),
          %{"type" => "Create", "object" => %{"type" => "Note"}} <- message do
       try_follow(follower, message)
     else
       nil ->
         Logger.warn(
-          "#{__MODULE__} skipped because of missing :mrf_follow_bot, :follower_nickname configuration or the account
-            does not exist."
+          "#{__MODULE__} skipped because of missing `:mrf_follow_bot, :follower_nickname` configuration, the :follower_nickname
+            account does not exist, or the account is not correctly configured as a bot."
         )
 
         {:ok, message}

From 2557e805a3034f363f0dfaa38cb8b174d89e3d1b Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Thu, 8 Oct 2020 12:46:27 -0500
Subject: [PATCH 114/339] Support for configuration via AdminFE

---
 config/description.exs | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/config/description.exs b/config/description.exs
index 41e5e4056..bb1f43305 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -2942,6 +2942,23 @@
       }
     ]
   },
+  %{
+    group: :pleroma,
+    key: :mrf_follow_bot,
+    tab: :mrf,
+    related_policy: "Pleroma.Web.ActivityPub.MRF.FollowBotPolicy",
+    label: "MRF FollowBot Policy",
+    type: :group,
+    description: "Automatically follows newly discovered accounts.",
+    children: [
+      %{
+        key: :follower_nickname,
+        type: :string,
+        description: "The name of the bot account to use for following newly discovered users.",
+        suggestions: ["followbot"]
+      }
+    ]
+  },
   %{
     group: :pleroma,
     key: :modules,

From 2689463c7e8e99f25964072360b4c6955b7fcea0 Mon Sep 17 00:00:00 2001
From: feld <feld@feld.me>
Date: Thu, 8 Oct 2020 19:48:09 +0000
Subject: [PATCH 115/339] Apply 1 suggestion(s) to 1 file(s)

---
 docs/configuration/cheatsheet.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index 6e52cd181..d30f4cbdd 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -223,7 +223,7 @@ Notes:
 
 #### :mrf_follow_bot
 
-* `follower_nickname`: The name of the bot account to use for following newly discovered users.
+* `follower_nickname`: The name of the bot account to use for following newly discovered users. Using `followbot` or similar is strongly suggested.
 
 
 ### :activitypub

From 3949cfdc249bb508c1171851fa2ec076126003cc Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 19 Feb 2021 09:47:25 -0600
Subject: [PATCH 116/339] Make the followbot only dispatch follow requests once
 per 30 day period

---
 .../web/activity_pub/mrf/follow_bot_policy.ex | 27 ++++++++++++++++---
 1 file changed, 23 insertions(+), 4 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
index d10b7b480..044febe0c 100644
--- a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
@@ -1,10 +1,14 @@
 defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
   @behaviour Pleroma.Web.ActivityPub.MRF
+  alias Pleroma.Activity.Queries
   alias Pleroma.Config
+  alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web.CommonAPI
   require Logger
 
+  import Ecto.Query
+
   @impl true
   def filter(message) do
     with follower_nickname <- Config.get([:mrf_follow_bot, :follower_nickname]),
@@ -36,12 +40,13 @@ defp try_follow(follower, message) do
       |> List.flatten()
       |> User.get_all_by_ap_id()
       |> Enum.each(fn user ->
-        Logger.info("Checking if #{user.nickname} can be followed")
+        since_thirty_days_ago = NaiveDateTime.utc_now() |> NaiveDateTime.add(-(86_400 * 30))
 
         with false <- User.following?(follower, user),
-             false <- user.locked,
-             false <- (user.bio || "") |> String.downcase() |> String.contains?("nobot") do
-          Logger.info("Following #{user.nickname}")
+             false <- User.locked?(user),
+             false <- (user.bio || "") |> String.downcase() |> String.contains?("nobot"),
+             false <- outstanding_follow_request_since?(follower, user, since_thirty_days_ago) do
+          Logger.info("#{__MODULE__}: Follow request from #{follower.nickname} to #{user.nickname}")
           CommonAPI.follow(follower, user)
         end
       end)
@@ -50,6 +55,20 @@ defp try_follow(follower, message) do
     {:ok, message}
   end
 
+  defp outstanding_follow_request_since?(
+         %User{ap_id: follower_id},
+         %User{ap_id: followee_id},
+         since_datetime
+       ) do
+    followee_id
+    |> Queries.by_object_id()
+    |> Queries.by_type("Follow")
+    |> where([a], a.inserted_at > ^since_datetime)
+    |> where([a], fragment("? ->> 'state' = 'pending'", a.data))
+    |> where([a], a.actor == ^follower_id)
+    |> Repo.exists?()
+  end
+
   @impl true
   def describe do
     {:ok, %{}}

From 3989ec508c00a66d9093ead06deb8b1272b0b985 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 19 Feb 2021 09:59:30 -0600
Subject: [PATCH 117/339] Prevent duplicates from being processed

---
 lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
index 044febe0c..2fd5d5612 100644
--- a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
@@ -38,6 +38,7 @@ defp try_follow(follower, message) do
 
       Enum.concat([to, cc, actor])
       |> List.flatten()
+      |> Enum.uniq()
       |> User.get_all_by_ap_id()
       |> Enum.each(fn user ->
         since_thirty_days_ago = NaiveDateTime.utc_now() |> NaiveDateTime.add(-(86_400 * 30))

From a176914c73456eea7926235eb48e342ac1ab112d Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 19 Feb 2021 14:42:20 -0600
Subject: [PATCH 118/339] Better checking of previous follow request attempts

---
 lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
index 2fd5d5612..c7aaa6386 100644
--- a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
@@ -65,7 +65,7 @@ defp outstanding_follow_request_since?(
     |> Queries.by_object_id()
     |> Queries.by_type("Follow")
     |> where([a], a.inserted_at > ^since_datetime)
-    |> where([a], fragment("? ->> 'state' = 'pending'", a.data))
+    |> where([a], fragment("? ->> 'state' != 'accept'", a.data))
     |> where([a], a.actor == ^follower_id)
     |> Repo.exists?()
   end

From f0dcc1ca692fb5d6a5aca4f8a9ccb255baef9c1d Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 19 Feb 2021 14:55:05 -0600
Subject: [PATCH 119/339] Lint

---
 lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
index c7aaa6386..441ce553e 100644
--- a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
@@ -47,7 +47,10 @@ defp try_follow(follower, message) do
              false <- User.locked?(user),
              false <- (user.bio || "") |> String.downcase() |> String.contains?("nobot"),
              false <- outstanding_follow_request_since?(follower, user, since_thirty_days_ago) do
-          Logger.info("#{__MODULE__}: Follow request from #{follower.nickname} to #{user.nickname}")
+          Logger.info(
+            "#{__MODULE__}: Follow request from #{follower.nickname} to #{user.nickname}"
+          )
+
           CommonAPI.follow(follower, user)
         end
       end)

From 1926d0804ba6ade106a509c027af6bf56e6a8791 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 19 Feb 2021 15:16:55 -0600
Subject: [PATCH 120/339] Add follow_requests_outstanding_since?/3 to
 Pleroma.Activity

---
 lib/pleroma/activity.ex | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index d59403884..b0f1a900d 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -345,6 +345,20 @@ def following_requests_for_actor(%User{ap_id: ap_id}) do
     |> Repo.all()
   end
 
+  def follow_requests_outstanding_since?(
+        %User{ap_id: follower_id},
+        %User{ap_id: followee_id},
+        since_datetime
+      ) do
+    followee_id
+    |> Queries.by_object_id()
+    |> Queries.by_type("Follow")
+    |> where([a], a.inserted_at > ^since_datetime)
+    |> where([a], fragment("? ->> 'state' != 'accept'", a.data))
+    |> where([a], a.actor == ^follower_id)
+    |> Repo.exists?()
+  end
+
   def restrict_deactivated_users(query) do
     deactivated_users =
       from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)

From 86182ef8e445ee8a89ce2e49f33cab3dac2d2b12 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 19 Feb 2021 15:17:33 -0600
Subject: [PATCH 121/339] Change module name to FollowbotPolicy

---
 ...llow_bot_policy.ex => followbot_policy.ex} | 25 ++++---------------
 1 file changed, 5 insertions(+), 20 deletions(-)
 rename lib/pleroma/web/activity_pub/mrf/{follow_bot_policy.ex => followbot_policy.ex} (72%)

diff --git a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex b/lib/pleroma/web/activity_pub/mrf/followbot_policy.ex
similarity index 72%
rename from lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
rename to lib/pleroma/web/activity_pub/mrf/followbot_policy.ex
index 441ce553e..838d39c88 100644
--- a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/followbot_policy.ex
@@ -1,13 +1,11 @@
-defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
+defmodule Pleroma.Web.ActivityPub.MRF.FollowbotPolicy do
   @behaviour Pleroma.Web.ActivityPub.MRF
-  alias Pleroma.Activity.Queries
+  alias Pleroma.Activity
   alias Pleroma.Config
-  alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web.CommonAPI
-  require Logger
 
-  import Ecto.Query
+  require Logger
 
   @impl true
   def filter(message) do
@@ -46,7 +44,8 @@ defp try_follow(follower, message) do
         with false <- User.following?(follower, user),
              false <- User.locked?(user),
              false <- (user.bio || "") |> String.downcase() |> String.contains?("nobot"),
-             false <- outstanding_follow_request_since?(follower, user, since_thirty_days_ago) do
+             false <-
+               Activity.follow_requests_outstanding_since?(follower, user, since_thirty_days_ago) do
           Logger.info(
             "#{__MODULE__}: Follow request from #{follower.nickname} to #{user.nickname}"
           )
@@ -59,20 +58,6 @@ defp try_follow(follower, message) do
     {:ok, message}
   end
 
-  defp outstanding_follow_request_since?(
-         %User{ap_id: follower_id},
-         %User{ap_id: followee_id},
-         since_datetime
-       ) do
-    followee_id
-    |> Queries.by_object_id()
-    |> Queries.by_type("Follow")
-    |> where([a], a.inserted_at > ^since_datetime)
-    |> where([a], fragment("? ->> 'state' != 'accept'", a.data))
-    |> where([a], a.actor == ^follower_id)
-    |> Repo.exists?()
-  end
-
   @impl true
   def describe do
     {:ok, %{}}

From 778010ef8e1f4509bd554e65556336e5e8457ef6 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 19 Feb 2021 15:25:26 -0600
Subject: [PATCH 122/339] Do not try to follow local users. Their posts are
 already available locally on the instance.

---
 lib/pleroma/web/activity_pub/mrf/followbot_policy.ex | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/activity_pub/mrf/followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/followbot_policy.ex
index 838d39c88..5c8834536 100644
--- a/lib/pleroma/web/activity_pub/mrf/followbot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/followbot_policy.ex
@@ -41,7 +41,8 @@ defp try_follow(follower, message) do
       |> Enum.each(fn user ->
         since_thirty_days_ago = NaiveDateTime.utc_now() |> NaiveDateTime.add(-(86_400 * 30))
 
-        with false <- User.following?(follower, user),
+        with false <- user.local,
+             false <- User.following?(follower, user),
              false <- User.locked?(user),
              false <- (user.bio || "") |> String.downcase() |> String.contains?("nobot"),
              false <-

From c252ac71d4ea4f3b08bd3524f32ee3fe9308be06 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 19 Feb 2021 18:34:52 -0600
Subject: [PATCH 123/339] Revert

---
 lib/pleroma/activity.ex | 14 --------------
 1 file changed, 14 deletions(-)

diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index b0f1a900d..d59403884 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -345,20 +345,6 @@ def following_requests_for_actor(%User{ap_id: ap_id}) do
     |> Repo.all()
   end
 
-  def follow_requests_outstanding_since?(
-        %User{ap_id: follower_id},
-        %User{ap_id: followee_id},
-        since_datetime
-      ) do
-    followee_id
-    |> Queries.by_object_id()
-    |> Queries.by_type("Follow")
-    |> where([a], a.inserted_at > ^since_datetime)
-    |> where([a], fragment("? ->> 'state' != 'accept'", a.data))
-    |> where([a], a.actor == ^follower_id)
-    |> Repo.exists?()
-  end
-
   def restrict_deactivated_users(query) do
     deactivated_users =
       from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)

From f73d1667854fc4c6c721bf49a7deeefde1f569e3 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 19 Feb 2021 18:36:21 -0600
Subject: [PATCH 124/339] Only need to validate a follow request is generated
 for now

---
 .../mrf/followbot_policy_test.exs             | 42 +++++++++++++++++++
 1 file changed, 42 insertions(+)
 create mode 100644 test/pleroma/web/activity_pub/mrf/followbot_policy_test.exs

diff --git a/test/pleroma/web/activity_pub/mrf/followbot_policy_test.exs b/test/pleroma/web/activity_pub/mrf/followbot_policy_test.exs
new file mode 100644
index 000000000..283e9b12c
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/followbot_policy_test.exs
@@ -0,0 +1,42 @@
+# 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.MRF.FollowbotPolicyTest do
+  use Pleroma.DataCase, async: true
+
+  alias Pleroma.User
+  alias Pleroma.Web.ActivityPub.MRF.FollowbotPolicy
+
+  import Pleroma.Factory
+
+  describe "FollowBotPolicy" do
+    test "follows remote users" do
+      bot = insert(:user, actor_type: "Service")
+      remote_user = insert(:user, local: false)
+      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
+
+      message = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "to" => [remote_user.follower_address],
+        "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+        "type" => "Create",
+        "object" => %{
+          "content" => "Test post",
+          "type" => "Note",
+          "attributedTo" => remote_user.ap_id,
+          "inReplyTo" => nil
+        },
+        "actor" => remote_user.ap_id
+      }
+
+      refute User.following?(bot, remote_user)
+
+      assert User.get_follow_requests(remote_user) |> length == 0
+
+      FollowbotPolicy.filter(message)
+
+      assert User.get_follow_requests(remote_user) |> length == 1
+    end
+  end
+end

From 4796df0bc39a57b2581168cb8d8fde7779068f2d Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 19 Feb 2021 18:36:35 -0600
Subject: [PATCH 125/339] Remove Task.async as it is broken here and probably a
 premature optimization anyway

---
 .../web/activity_pub/mrf/followbot_policy.ex  | 41 ++++++++-----------
 1 file changed, 17 insertions(+), 24 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/mrf/followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/followbot_policy.ex
index 5c8834536..ca99e429c 100644
--- a/lib/pleroma/web/activity_pub/mrf/followbot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/followbot_policy.ex
@@ -1,6 +1,5 @@
 defmodule Pleroma.Web.ActivityPub.MRF.FollowbotPolicy do
   @behaviour Pleroma.Web.ActivityPub.MRF
-  alias Pleroma.Activity
   alias Pleroma.Config
   alias Pleroma.User
   alias Pleroma.Web.CommonAPI
@@ -29,31 +28,25 @@ def filter(message) do
   end
 
   defp try_follow(follower, message) do
-    Task.start(fn ->
-      to = Map.get(message, "to", [])
-      cc = Map.get(message, "cc", [])
-      actor = [message["actor"]]
+    to = Map.get(message, "to", [])
+    cc = Map.get(message, "cc", [])
+    actor = [message["actor"]]
 
-      Enum.concat([to, cc, actor])
-      |> List.flatten()
-      |> Enum.uniq()
-      |> User.get_all_by_ap_id()
-      |> Enum.each(fn user ->
-        since_thirty_days_ago = NaiveDateTime.utc_now() |> NaiveDateTime.add(-(86_400 * 30))
+    Enum.concat([to, cc, actor])
+    |> List.flatten()
+    |> Enum.uniq()
+    |> User.get_all_by_ap_id()
+    |> Enum.each(fn user ->
+      with false <- user.local,
+           false <- User.following?(follower, user),
+           false <- User.locked?(user),
+           false <- (user.bio || "") |> String.downcase() |> String.contains?("nobot") do
+        Logger.debug(
+          "#{__MODULE__}: Follow request from #{follower.nickname} to #{user.nickname}"
+        )
 
-        with false <- user.local,
-             false <- User.following?(follower, user),
-             false <- User.locked?(user),
-             false <- (user.bio || "") |> String.downcase() |> String.contains?("nobot"),
-             false <-
-               Activity.follow_requests_outstanding_since?(follower, user, since_thirty_days_ago) do
-          Logger.info(
-            "#{__MODULE__}: Follow request from #{follower.nickname} to #{user.nickname}"
-          )
-
-          CommonAPI.follow(follower, user)
-        end
-      end)
+        CommonAPI.follow(follower, user)
+      end
     end)
 
     {:ok, message}

From fef4f3772cf035cefb939bdfaaa4b12d6444b553 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 24 Feb 2021 11:52:03 -0600
Subject: [PATCH 126/339] More tests to validate Followbot is behaving

---
 .../mrf/followbot_policy_test.exs             | 84 +++++++++++++++++++
 1 file changed, 84 insertions(+)

diff --git a/test/pleroma/web/activity_pub/mrf/followbot_policy_test.exs b/test/pleroma/web/activity_pub/mrf/followbot_policy_test.exs
index 283e9b12c..4c39e04e8 100644
--- a/test/pleroma/web/activity_pub/mrf/followbot_policy_test.exs
+++ b/test/pleroma/web/activity_pub/mrf/followbot_policy_test.exs
@@ -38,5 +38,89 @@ test "follows remote users" do
 
       assert User.get_follow_requests(remote_user) |> length == 1
     end
+
+    test "does not follow users with #nobot in bio" do
+      bot = insert(:user, actor_type: "Service")
+      remote_user = insert(:user, %{local: false, bio: "go away bots! #nobot"})
+      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
+
+      message = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "to" => [remote_user.follower_address],
+        "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+        "type" => "Create",
+        "object" => %{
+          "content" => "I don't like follow bots",
+          "type" => "Note",
+          "attributedTo" => remote_user.ap_id,
+          "inReplyTo" => nil
+        },
+        "actor" => remote_user.ap_id
+      }
+
+      refute User.following?(bot, remote_user)
+
+      assert User.get_follow_requests(remote_user) |> length == 0
+
+      FollowbotPolicy.filter(message)
+
+      assert User.get_follow_requests(remote_user) |> length == 0
+    end
+
+    test "does not follow local users" do
+      bot = insert(:user, actor_type: "Service")
+      local_user = insert(:user, local: true)
+      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
+
+      message = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "to" => [local_user.follower_address],
+        "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+        "type" => "Create",
+        "object" => %{
+          "content" => "Hi I'm a local user",
+          "type" => "Note",
+          "attributedTo" => local_user.ap_id,
+          "inReplyTo" => nil
+        },
+        "actor" => local_user.ap_id
+      }
+
+      refute User.following?(bot, local_user)
+
+      assert User.get_follow_requests(local_user) |> length == 0
+
+      FollowbotPolicy.filter(message)
+
+      assert User.get_follow_requests(local_user) |> length == 0
+    end
+
+    test "does not follow users requiring follower approval" do
+      bot = insert(:user, actor_type: "Service")
+      remote_user = insert(:user, %{local: false, is_locked: true})
+      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
+
+      message = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "to" => [remote_user.follower_address],
+        "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+        "type" => "Create",
+        "object" => %{
+          "content" => "I don't like randos following me",
+          "type" => "Note",
+          "attributedTo" => remote_user.ap_id,
+          "inReplyTo" => nil
+        },
+        "actor" => remote_user.ap_id
+      }
+
+      refute User.following?(bot, remote_user)
+
+      assert User.get_follow_requests(remote_user) |> length == 0
+
+      FollowbotPolicy.filter(message)
+
+      assert User.get_follow_requests(remote_user) |> length == 0
+    end
   end
 end

From 7eab98d5c856097c0cfe09a02adfd4c05fb5d240 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 24 Feb 2021 11:58:09 -0600
Subject: [PATCH 127/339] Document new FollowBot MRF

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index fb26c7a73..43f2bb638 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -75,6 +75,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Ability to define custom HTTP headers per each frontend
 - MRF (`NoEmptyPolicy`): New MRF Policy which will deny empty statuses or statuses of only mentions from being created by local users
 - New users will receive a simple email confirming their registration if no other emails will be dispatched. (e.g., Welcome, Confirmation, or Approval Required)
+- MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded.
 
 <details>
   <summary>API Changes</summary>

From 03f38ac4ebd97e792b0ff2a6ac804adefed85a41 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 24 Feb 2021 11:59:11 -0600
Subject: [PATCH 128/339] Prefer FollowBot naming convention vs Followbot

---
 ...llowbot_policy.ex => follow_bot_policy.ex} |   2 +-
 .../mrf/follow_bot_policy_test.exs            | 126 ++++++++++++++++++
 ...y_test.exs => follow_bot_policy_test.exs~} |   0
 3 files changed, 127 insertions(+), 1 deletion(-)
 rename lib/pleroma/web/activity_pub/mrf/{followbot_policy.ex => follow_bot_policy.ex} (96%)
 create mode 100644 test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs
 rename test/pleroma/web/activity_pub/mrf/{followbot_policy_test.exs => follow_bot_policy_test.exs~} (100%)

diff --git a/lib/pleroma/web/activity_pub/mrf/followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
similarity index 96%
rename from lib/pleroma/web/activity_pub/mrf/followbot_policy.ex
rename to lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
index ca99e429c..7307c9c14 100644
--- a/lib/pleroma/web/activity_pub/mrf/followbot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
@@ -1,4 +1,4 @@
-defmodule Pleroma.Web.ActivityPub.MRF.FollowbotPolicy do
+defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
   @behaviour Pleroma.Web.ActivityPub.MRF
   alias Pleroma.Config
   alias Pleroma.User
diff --git a/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs b/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs
new file mode 100644
index 000000000..3f63f11ef
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs
@@ -0,0 +1,126 @@
+# 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.MRF.FollowBotPolicyTest do
+  use Pleroma.DataCase, async: true
+
+  alias Pleroma.User
+  alias Pleroma.Web.ActivityPub.MRF.FollowBotPolicy
+
+  import Pleroma.Factory
+
+  describe "FollowBotPolicy" do
+    test "follows remote users" do
+      bot = insert(:user, actor_type: "Service")
+      remote_user = insert(:user, local: false)
+      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
+
+      message = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "to" => [remote_user.follower_address],
+        "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+        "type" => "Create",
+        "object" => %{
+          "content" => "Test post",
+          "type" => "Note",
+          "attributedTo" => remote_user.ap_id,
+          "inReplyTo" => nil
+        },
+        "actor" => remote_user.ap_id
+      }
+
+      refute User.following?(bot, remote_user)
+
+      assert User.get_follow_requests(remote_user) |> length == 0
+
+      FollowbotPolicy.filter(message)
+
+      assert User.get_follow_requests(remote_user) |> length == 1
+    end
+
+    test "does not follow users with #nobot in bio" do
+      bot = insert(:user, actor_type: "Service")
+      remote_user = insert(:user, %{local: false, bio: "go away bots! #nobot"})
+      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
+
+      message = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "to" => [remote_user.follower_address],
+        "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+        "type" => "Create",
+        "object" => %{
+          "content" => "I don't like follow bots",
+          "type" => "Note",
+          "attributedTo" => remote_user.ap_id,
+          "inReplyTo" => nil
+        },
+        "actor" => remote_user.ap_id
+      }
+
+      refute User.following?(bot, remote_user)
+
+      assert User.get_follow_requests(remote_user) |> length == 0
+
+      FollowbotPolicy.filter(message)
+
+      assert User.get_follow_requests(remote_user) |> length == 0
+    end
+
+    test "does not follow local users" do
+      bot = insert(:user, actor_type: "Service")
+      local_user = insert(:user, local: true)
+      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
+
+      message = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "to" => [local_user.follower_address],
+        "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+        "type" => "Create",
+        "object" => %{
+          "content" => "Hi I'm a local user",
+          "type" => "Note",
+          "attributedTo" => local_user.ap_id,
+          "inReplyTo" => nil
+        },
+        "actor" => local_user.ap_id
+      }
+
+      refute User.following?(bot, local_user)
+
+      assert User.get_follow_requests(local_user) |> length == 0
+
+      FollowbotPolicy.filter(message)
+
+      assert User.get_follow_requests(local_user) |> length == 0
+    end
+
+    test "does not follow users requiring follower approval" do
+      bot = insert(:user, actor_type: "Service")
+      remote_user = insert(:user, %{local: false, is_locked: true})
+      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
+
+      message = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "to" => [remote_user.follower_address],
+        "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+        "type" => "Create",
+        "object" => %{
+          "content" => "I don't like randos following me",
+          "type" => "Note",
+          "attributedTo" => remote_user.ap_id,
+          "inReplyTo" => nil
+        },
+        "actor" => remote_user.ap_id
+      }
+
+      refute User.following?(bot, remote_user)
+
+      assert User.get_follow_requests(remote_user) |> length == 0
+
+      FollowbotPolicy.filter(message)
+
+      assert User.get_follow_requests(remote_user) |> length == 0
+    end
+  end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/followbot_policy_test.exs b/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs~
similarity index 100%
rename from test/pleroma/web/activity_pub/mrf/followbot_policy_test.exs
rename to test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs~

From d29f6d6b6ef896d0fa47b4f5136fc6714e3425f3 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 24 Feb 2021 12:02:33 -0600
Subject: [PATCH 129/339] Add more details to the cheatsheat for FollowBot MRF

---
 docs/configuration/cheatsheet.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index d30f4cbdd..069421722 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -124,7 +124,7 @@ To add configuration to your config file, you can copy it from the base config.
     * `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)).
     * `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled delections.
     * `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines.
-    * `Pleroma.Web.ActivityPub.MRF.FollowBotPolicy`: Automatically follows newly discovered users from the specified bot account.
+    * `Pleroma.Web.ActivityPub.MRF.FollowBotPolicy`: Automatically follows newly discovered users from the specified bot account. Local accounts, locked accounts, and users with "#nobot" in their bio are respected and excluded from being followed.
 * `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
 * `transparency_exclusions`: Exclude specific instance names from MRF transparency.  The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
 

From bfcdcd4f6937bfc0b123a6ec0bbf1d3e6968f0fb Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 24 Feb 2021 12:07:40 -0600
Subject: [PATCH 130/339] Temp file leaked, oops

---
 .../mrf/follow_bot_policy_test.exs~           | 126 ------------------
 1 file changed, 126 deletions(-)
 delete mode 100644 test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs~

diff --git a/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs~ b/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs~
deleted file mode 100644
index 4c39e04e8..000000000
--- a/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs~
+++ /dev/null
@@ -1,126 +0,0 @@
-# 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.MRF.FollowbotPolicyTest do
-  use Pleroma.DataCase, async: true
-
-  alias Pleroma.User
-  alias Pleroma.Web.ActivityPub.MRF.FollowbotPolicy
-
-  import Pleroma.Factory
-
-  describe "FollowBotPolicy" do
-    test "follows remote users" do
-      bot = insert(:user, actor_type: "Service")
-      remote_user = insert(:user, local: false)
-      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
-
-      message = %{
-        "@context" => "https://www.w3.org/ns/activitystreams",
-        "to" => [remote_user.follower_address],
-        "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
-        "type" => "Create",
-        "object" => %{
-          "content" => "Test post",
-          "type" => "Note",
-          "attributedTo" => remote_user.ap_id,
-          "inReplyTo" => nil
-        },
-        "actor" => remote_user.ap_id
-      }
-
-      refute User.following?(bot, remote_user)
-
-      assert User.get_follow_requests(remote_user) |> length == 0
-
-      FollowbotPolicy.filter(message)
-
-      assert User.get_follow_requests(remote_user) |> length == 1
-    end
-
-    test "does not follow users with #nobot in bio" do
-      bot = insert(:user, actor_type: "Service")
-      remote_user = insert(:user, %{local: false, bio: "go away bots! #nobot"})
-      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
-
-      message = %{
-        "@context" => "https://www.w3.org/ns/activitystreams",
-        "to" => [remote_user.follower_address],
-        "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
-        "type" => "Create",
-        "object" => %{
-          "content" => "I don't like follow bots",
-          "type" => "Note",
-          "attributedTo" => remote_user.ap_id,
-          "inReplyTo" => nil
-        },
-        "actor" => remote_user.ap_id
-      }
-
-      refute User.following?(bot, remote_user)
-
-      assert User.get_follow_requests(remote_user) |> length == 0
-
-      FollowbotPolicy.filter(message)
-
-      assert User.get_follow_requests(remote_user) |> length == 0
-    end
-
-    test "does not follow local users" do
-      bot = insert(:user, actor_type: "Service")
-      local_user = insert(:user, local: true)
-      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
-
-      message = %{
-        "@context" => "https://www.w3.org/ns/activitystreams",
-        "to" => [local_user.follower_address],
-        "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
-        "type" => "Create",
-        "object" => %{
-          "content" => "Hi I'm a local user",
-          "type" => "Note",
-          "attributedTo" => local_user.ap_id,
-          "inReplyTo" => nil
-        },
-        "actor" => local_user.ap_id
-      }
-
-      refute User.following?(bot, local_user)
-
-      assert User.get_follow_requests(local_user) |> length == 0
-
-      FollowbotPolicy.filter(message)
-
-      assert User.get_follow_requests(local_user) |> length == 0
-    end
-
-    test "does not follow users requiring follower approval" do
-      bot = insert(:user, actor_type: "Service")
-      remote_user = insert(:user, %{local: false, is_locked: true})
-      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
-
-      message = %{
-        "@context" => "https://www.w3.org/ns/activitystreams",
-        "to" => [remote_user.follower_address],
-        "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
-        "type" => "Create",
-        "object" => %{
-          "content" => "I don't like randos following me",
-          "type" => "Note",
-          "attributedTo" => remote_user.ap_id,
-          "inReplyTo" => nil
-        },
-        "actor" => remote_user.ap_id
-      }
-
-      refute User.following?(bot, remote_user)
-
-      assert User.get_follow_requests(remote_user) |> length == 0
-
-      FollowbotPolicy.filter(message)
-
-      assert User.get_follow_requests(remote_user) |> length == 0
-    end
-  end
-end

From 16a7ffb1ea9dc8e2c7a70d9243424b40d594bd63 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 26 Feb 2021 11:04:27 -0600
Subject: [PATCH 131/339] Fix function calls due to module name change

---
 .../web/activity_pub/mrf/follow_bot_policy_test.exs       | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs b/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs
index 3f63f11ef..a61562558 100644
--- a/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs
+++ b/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs
@@ -34,7 +34,7 @@ test "follows remote users" do
 
       assert User.get_follow_requests(remote_user) |> length == 0
 
-      FollowbotPolicy.filter(message)
+      FollowBotPolicy.filter(message)
 
       assert User.get_follow_requests(remote_user) |> length == 1
     end
@@ -62,7 +62,7 @@ test "does not follow users with #nobot in bio" do
 
       assert User.get_follow_requests(remote_user) |> length == 0
 
-      FollowbotPolicy.filter(message)
+      FollowBotPolicy.filter(message)
 
       assert User.get_follow_requests(remote_user) |> length == 0
     end
@@ -90,7 +90,7 @@ test "does not follow local users" do
 
       assert User.get_follow_requests(local_user) |> length == 0
 
-      FollowbotPolicy.filter(message)
+      FollowBotPolicy.filter(message)
 
       assert User.get_follow_requests(local_user) |> length == 0
     end
@@ -118,7 +118,7 @@ test "does not follow users requiring follower approval" do
 
       assert User.get_follow_requests(remote_user) |> length == 0
 
-      FollowbotPolicy.filter(message)
+      FollowBotPolicy.filter(message)
 
       assert User.get_follow_requests(remote_user) |> length == 0
     end

From 863010ea637d6670076dba3f6da54daa144cce67 Mon Sep 17 00:00:00 2001
From: Miss Pasture <atsmdq@gmail.com>
Date: Wed, 31 Mar 2021 06:51:22 +0000
Subject: [PATCH 132/339] date-times are always strings

---
 lib/pleroma/web/api_spec/operations/account_operation.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 54e5ebc76..08d68893a 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -482,7 +482,7 @@ defp create_response do
         access_token: %Schema{type: :string},
         refresh_token: %Schema{type: :string},
         scope: %Schema{type: :string},
-        created_at: %Schema{type: :integer, format: :"date-time"},
+        created_at: %Schema{type: :string, format: :"date-time"},
         me: %Schema{type: :string},
         expires_in: %Schema{type: :integer},
         #

From af1cd28f9b8005dff563c0df7f80db47df4e8488 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Thu, 1 Apr 2021 11:50:45 +0200
Subject: [PATCH 133/339] object_validator: Refactor most of validate/2 to a
 generic block

---
 .../web/activity_pub/object_validator.ex      | 135 +++---------------
 1 file changed, 22 insertions(+), 113 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 297c19cc0..f75744203 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -37,37 +37,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
   @impl true
   def validate(object, meta)
 
-  def validate(%{"type" => type} = object, meta)
-      when type in ~w[Accept Reject] do
-    with {:ok, object} <-
-           object
-           |> AcceptRejectValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
-  def validate(%{"type" => "Event"} = object, meta) do
-    with {:ok, object} <-
-           object
-           |> EventValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
-  def validate(%{"type" => "Follow"} = object, meta) do
-    with {:ok, object} <-
-           object
-           |> FollowValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
   def validate(%{"type" => "Block"} = block_activity, meta) do
     with {:ok, block_activity} <-
            block_activity
@@ -87,16 +56,6 @@ def validate(%{"type" => "Block"} = block_activity, meta) do
     end
   end
 
-  def validate(%{"type" => "Update"} = update_activity, meta) do
-    with {:ok, update_activity} <-
-           update_activity
-           |> UpdateValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      update_activity = stringify_keys(update_activity)
-      {:ok, update_activity, meta}
-    end
-  end
-
   def validate(%{"type" => "Undo"} = object, meta) do
     with {:ok, object} <-
            object
@@ -123,76 +82,6 @@ def validate(%{"type" => "Delete"} = object, meta) do
     end
   end
 
-  def validate(%{"type" => "Like"} = object, meta) do
-    with {:ok, object} <-
-           object
-           |> LikeValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
-  def validate(%{"type" => "ChatMessage"} = object, meta) do
-    with {:ok, object} <-
-           object
-           |> ChatMessageValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
-  def validate(%{"type" => "Question"} = object, meta) do
-    with {:ok, object} <-
-           object
-           |> QuestionValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
-  def validate(%{"type" => type} = object, meta) when type in ~w[Audio Video] do
-    with {:ok, object} <-
-           object
-           |> AudioVideoValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
-  def validate(%{"type" => "Article"} = object, meta) do
-    with {:ok, object} <-
-           object
-           |> ArticleNoteValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
-  def validate(%{"type" => "Answer"} = object, meta) do
-    with {:ok, object} <-
-           object
-           |> AnswerValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
-  def validate(%{"type" => "EmojiReact"} = object, meta) do
-    with {:ok, object} <-
-           object
-           |> EmojiReactValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
   def validate(
         %{"type" => "Create", "object" => %{"type" => "ChatMessage"} = object} = create_activity,
         meta
@@ -224,10 +113,30 @@ def validate(
     end
   end
 
-  def validate(%{"type" => "Announce"} = object, meta) do
+  def validate(%{"type" => type} = object, meta)
+      when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
+      Event ChatMessage Question Audio Video Article Answer] do
+    validator =
+      case type do
+        "Accept" -> AcceptRejectValidator
+        "Reject" -> AcceptRejectValidator
+        "Follow" -> FollowValidator
+        "Update" -> UpdateValidator
+        "Like" -> LikeValidator
+        "EmojiReact" -> EmojiReactValidator
+        "Announce" -> AnnounceValidator
+        "Event" -> EventValidator
+        "ChatMessage" -> ChatMessageValidator
+        "Question" -> QuestionValidator
+        "Audio" -> AudioVideoValidator
+        "Video" -> AudioVideoValidator
+        "Article" -> ArticleNoteValidator
+        "Answer" -> AnswerValidator
+      end
+
     with {:ok, object} <-
            object
-           |> AnnounceValidator.cast_and_validate()
+           |> validator.cast_and_validate()
            |> Ecto.Changeset.apply_action(:insert) do
       object = stringify_keys(object)
       {:ok, object, meta}

From 1e3db075861198417bca36f4f2b0f29c2367c77e Mon Sep 17 00:00:00 2001
From: Haelwenn <contact+git.pleroma.social@hacktivis.me>
Date: Thu, 1 Apr 2021 12:00:58 +0000
Subject: [PATCH 134/339] Revert "Merge branch 'patch-fix-open-api-spec' into
 'develop'"

This reverts merge request !3382
---
 lib/pleroma/web/api_spec/operations/account_operation.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 08d68893a..54e5ebc76 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -482,7 +482,7 @@ defp create_response do
         access_token: %Schema{type: :string},
         refresh_token: %Schema{type: :string},
         scope: %Schema{type: :string},
-        created_at: %Schema{type: :string, format: :"date-time"},
+        created_at: %Schema{type: :integer, format: :"date-time"},
         me: %Schema{type: :string},
         expires_in: %Schema{type: :integer},
         #

From 9015df22291ab60c0efad328557936fd14eab2e6 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Thu, 7 Jan 2021 18:23:01 +0100
Subject: [PATCH 135/339] TagValidator: New

---
 .../article_note_validator.ex                 |  7 +-
 .../object_validators/attachment_validator.ex |  1 -
 .../audio_video_validator.ex                  |  7 +-
 .../object_validators/event_validator.ex      |  7 +-
 .../object_validators/question_validator.ex   |  7 +-
 .../object_validators/tag_validator.ex        | 77 +++++++++++++++++++
 6 files changed, 93 insertions(+), 13 deletions(-)
 create mode 100644 lib/pleroma/web/activity_pub/object_validators/tag_validator.ex

diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
index b0388ef3b..5910f4060 100644
--- a/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
   alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+  alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
   alias Pleroma.Web.ActivityPub.Transmogrifier
 
   import Ecto.Changeset
@@ -22,8 +23,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
     field(:cc, ObjectValidators.Recipients, default: [])
     field(:bto, ObjectValidators.Recipients, default: [])
     field(:bcc, ObjectValidators.Recipients, default: [])
-    # TODO: Write type
-    field(:tag, {:array, :map}, default: [])
+    embeds_many(:tag, TagValidator)
     field(:type, :string)
 
     field(:name, :string)
@@ -90,8 +90,9 @@ def changeset(struct, data) do
     data = fix(data)
 
     struct
-    |> cast(data, __schema__(:fields) -- [:attachment])
+    |> cast(data, __schema__(:fields) -- [:attachment, :tag])
     |> cast_embed(:attachment)
+    |> cast_embed(:tag)
   end
 
   def validate_data(data_cng) do
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 3175427ad..e7b3a3922 100644
--- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
@@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
   use Ecto.Schema
 
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
-  alias Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator
 
   import Ecto.Changeset
 
diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
index 4a96fef52..a04c95f4b 100644
--- a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
   alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+  alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
   alias Pleroma.Web.ActivityPub.Transmogrifier
 
   import Ecto.Changeset
@@ -23,8 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
     field(:cc, ObjectValidators.Recipients, default: [])
     field(:bto, ObjectValidators.Recipients, default: [])
     field(:bcc, ObjectValidators.Recipients, default: [])
-    # TODO: Write type
-    field(:tag, {:array, :map}, default: [])
+    embeds_many(:tag, TagValidator)
     field(:type, :string)
 
     field(:name, :string)
@@ -132,8 +132,9 @@ def changeset(struct, data) do
     data = fix(data)
 
     struct
-    |> cast(data, __schema__(:fields) -- [:attachment])
+    |> cast(data, __schema__(:fields) -- [:attachment, :tag])
     |> cast_embed(:attachment)
+    |> cast_embed(:tag)
   end
 
   def validate_data(data_cng) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
index 2e26726f8..0112a074d 100644
--- a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
   alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+  alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
   alias Pleroma.Web.ActivityPub.Transmogrifier
 
   import Ecto.Changeset
@@ -23,8 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
     field(:cc, ObjectValidators.Recipients, default: [])
     field(:bto, ObjectValidators.Recipients, default: [])
     field(:bcc, ObjectValidators.Recipients, default: [])
-    # TODO: Write type
-    field(:tag, {:array, :map}, default: [])
+    embeds_many(:tag, TagValidator)
     field(:type, :string)
 
     field(:name, :string)
@@ -81,8 +81,9 @@ def changeset(struct, data) do
     data = fix(data)
 
     struct
-    |> cast(data, __schema__(:fields) -- [:attachment])
+    |> cast(data, __schema__(:fields) -- [:attachment, :tag])
     |> cast_embed(:attachment)
+    |> cast_embed(:tag)
   end
 
   def validate_data(data_cng) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
index 6b746c997..7acb1e928 100644
--- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
   alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator
+  alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
   alias Pleroma.Web.ActivityPub.Transmogrifier
 
   import Ecto.Changeset
@@ -24,8 +25,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
     field(:cc, ObjectValidators.Recipients, default: [])
     field(:bto, ObjectValidators.Recipients, default: [])
     field(:bcc, ObjectValidators.Recipients, default: [])
-    # TODO: Write type
-    field(:tag, {:array, :map}, default: [])
+    embeds_many(:tag, TagValidator)
     field(:type, :string)
     field(:content, :string)
     field(:context, :string)
@@ -93,10 +93,11 @@ def changeset(struct, data) do
     data = fix(data)
 
     struct
-    |> cast(data, __schema__(:fields) -- [:anyOf, :oneOf, :attachment])
+    |> cast(data, __schema__(:fields) -- [:anyOf, :oneOf, :attachment, :tag])
     |> cast_embed(:attachment)
     |> cast_embed(:anyOf)
     |> cast_embed(:oneOf)
+    |> cast_embed(:tag)
   end
 
   def validate_data(data_cng) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex b/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex
new file mode 100644
index 000000000..751021585
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex
@@ -0,0 +1,77 @@
+# 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.TagValidator do
+  use Ecto.Schema
+
+  alias Pleroma.EctoType.ActivityPub.ObjectValidators
+
+  import Ecto.Changeset
+
+  @primary_key false
+  embedded_schema do
+    # Common
+    field(:type, :string)
+    field(:name, :string)
+
+    # Mention, Hashtag
+    field(:href, ObjectValidators.Uri)
+
+    # Emoji
+    embeds_one :icon, IconObjectValidator, primary_key: false do
+      field(:type, :string)
+      field(:url, ObjectValidators.Uri)
+    end
+
+    field(:updated, ObjectValidators.DateTime)
+    field(:id, ObjectValidators.Uri)
+  end
+
+  def cast_and_validate(data) do
+    data
+    |> cast_data()
+  end
+
+  def cast_data(data) do
+    %__MODULE__{}
+    |> changeset(data)
+  end
+
+  def changeset(struct, %{"type" => "Mention"} = data) do
+    struct
+    |> cast(data, [:type, :name, :href])
+    |> validate_required([:type, :href])
+  end
+
+  def changeset(struct, %{"type" => "Hashtag", "name" => name} = data) do
+    name =
+      cond do
+        "#" <> name -> name
+        name -> name
+      end
+      |> String.downcase()
+
+    data = Map.put(data, "name", name)
+
+    struct
+    |> cast(data, [:type, :name, :href])
+    |> validate_required([:type, :name])
+  end
+
+  def changeset(struct, %{"type" => "Emoji"} = data) do
+    data = Map.put(data, "name", String.trim(data["name"], ":"))
+
+    struct
+    |> cast(data, [:type, :name, :updated, :id])
+    |> cast_embed(:icon, with: &icon_changeset/2)
+    |> validate_required([:type, :name, :icon])
+  end
+
+  def icon_changeset(struct, data) do
+    struct
+    |> cast(data, [:type, :url])
+    |> validate_inclusion(:type, ~w[Image])
+    |> validate_required([:type, :url])
+  end
+end

From 5ae27c8451a7012b43ef9113713132158701364b Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Tue, 12 Jan 2021 14:11:29 +0100
Subject: [PATCH 136/339] pipeline_test: Fix usage of %Activity{}

---
 .../web/activity_pub/object_validator.ex      |  2 +-
 lib/pleroma/web/activity_pub/pipeline.ex      |  2 ++
 .../web/activity_pub/pipeline_test.exs        | 23 +++++++++++++------
 3 files changed, 19 insertions(+), 8 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index f75744203..15784b28c 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -169,7 +169,7 @@ def cast_and_apply(%{"type" => "Article"} = object) do
 
   def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
 
-  # is_struct/1 isn't present in Elixir 1.8.x
+  # is_struct/1 appears in Elixir 1.11
   def stringify_keys(%{__struct__: _} = object) do
     object
     |> Map.from_struct()
diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex
index 195596f94..0aa504e72 100644
--- a/lib/pleroma/web/activity_pub/pipeline.ex
+++ b/lib/pleroma/web/activity_pub/pipeline.ex
@@ -40,6 +40,8 @@ def common_pipeline(object, meta) do
     end
   end
 
+  def do_common_pipeline(%{__struct__: _}, _meta), do: {:error, :is_struct}
+
   def do_common_pipeline(object, meta) do
     with {_, {:ok, validated_object, meta}} <-
            {:validate_object, @object_validator.validate(object, meta)},
diff --git a/test/pleroma/web/activity_pub/pipeline_test.exs b/test/pleroma/web/activity_pub/pipeline_test.exs
index 52fa933ee..e606fa3d1 100644
--- a/test/pleroma/web/activity_pub/pipeline_test.exs
+++ b/test/pleroma/web/activity_pub/pipeline_test.exs
@@ -25,9 +25,6 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do
       MRFMock
       |> expect(:pipeline_filter, fn o, m -> {:ok, o, m} end)
 
-      ActivityPubMock
-      |> expect(:persist, fn o, m -> {:ok, o, m} end)
-
       SideEffectsMock
       |> expect(:handle, fn o, m -> {:ok, o, m} end)
       |> expect(:handle_after_transaction, fn m -> m end)
@@ -42,6 +39,9 @@ test "when given an `object_data` in meta, Federation will receive a the origina
 
       activity_with_object = %{activity | data: Map.put(activity.data, "object", object)}
 
+      ActivityPubMock
+      |> expect(:persist, fn _, m -> {:ok, activity, m} end)
+
       FederatorMock
       |> expect(:publish, fn ^activity_with_object -> :ok end)
 
@@ -50,7 +50,7 @@ test "when given an `object_data` in meta, Federation will receive a the origina
 
       assert {:ok, ^activity, ^meta} =
                Pleroma.Web.ActivityPub.Pipeline.common_pipeline(
-                 activity,
+                 activity.data,
                  meta
                )
     end
@@ -59,6 +59,9 @@ test "it goes through validation, filtering, persisting, side effects and federa
       activity = insert(:note_activity)
       meta = [local: true]
 
+      ActivityPubMock
+      |> expect(:persist, fn _, m -> {:ok, activity, m} end)
+
       FederatorMock
       |> expect(:publish, fn ^activity -> :ok end)
 
@@ -66,29 +69,35 @@ test "it goes through validation, filtering, persisting, side effects and federa
       |> expect(:get, fn [:instance, :federating] -> true end)
 
       assert {:ok, ^activity, ^meta} =
-               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
+               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity.data, meta)
     end
 
     test "it goes through validation, filtering, persisting, side effects without federation for remote activities" do
       activity = insert(:note_activity)
       meta = [local: false]
 
+      ActivityPubMock
+      |> expect(:persist, fn _, m -> {:ok, activity, m} end)
+
       ConfigMock
       |> expect(:get, fn [:instance, :federating] -> true end)
 
       assert {:ok, ^activity, ^meta} =
-               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
+               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity.data, meta)
     end
 
     test "it goes through validation, filtering, persisting, side effects without federation for local activities if federation is deactivated" do
       activity = insert(:note_activity)
       meta = [local: true]
 
+      ActivityPubMock
+      |> expect(:persist, fn _, m -> {:ok, activity, m} end)
+
       ConfigMock
       |> expect(:get, fn [:instance, :federating] -> false end)
 
       assert {:ok, ^activity, ^meta} =
-               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
+               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity.data, meta)
     end
   end
 end

From 37a7f521fd4778cde48f1b003ad9695e6ea45d1f Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Tue, 12 Jan 2021 09:30:22 +0100
Subject: [PATCH 137/339] Insert string-hashtags in Pipeline

Cannot be done in Ecto schemas because only one type is allowed in arrays, and
needs to be done before the MRFs.
---
 lib/pleroma/web/activity_pub/pipeline.ex      | 34 ++++++++++++-------
 .../web/activity_pub/transmogrifier.ex        |  2 +-
 lib/pleroma/web/common_api.ex                 | 12 +------
 3 files changed, 24 insertions(+), 24 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex
index 0aa504e72..e184a9376 100644
--- a/lib/pleroma/web/activity_pub/pipeline.ex
+++ b/lib/pleroma/web/activity_pub/pipeline.ex
@@ -42,23 +42,33 @@ def common_pipeline(object, meta) do
 
   def do_common_pipeline(%{__struct__: _}, _meta), do: {:error, :is_struct}
 
-  def do_common_pipeline(object, meta) do
-    with {_, {:ok, validated_object, meta}} <-
-           {:validate_object, @object_validator.validate(object, meta)},
-         {_, {:ok, mrfd_object, meta}} <-
-           {:mrf_object, @mrf.pipeline_filter(validated_object, meta)},
-         {_, {:ok, activity, meta}} <-
-           {:persist_object, @activity_pub.persist(mrfd_object, meta)},
-         {_, {:ok, activity, meta}} <-
-           {:execute_side_effects, @side_effects.handle(activity, meta)},
-         {_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do
-      {:ok, activity, meta}
+  def do_common_pipeline(message, meta) do
+    with {_, {:ok, message, meta}} <- {:validate, @object_validator.validate(message, meta)},
+         {_, {:ok, message, meta}} <- {:fixup, validation_fixups(message, meta)},
+         {_, {:ok, message, meta}} <- {:mrf, @mrf.pipeline_filter(message, meta)},
+         {_, {:ok, message, meta}} <- {:persist, @activity_pub.persist(message, meta)},
+         {_, {:ok, message, meta}} <- {:side_effects, @side_effects.handle(message, meta)},
+         {_, {:ok, _}} <- {:federation, maybe_federate(message, meta)} do
+      {:ok, message, meta}
     else
-      {:mrf_object, {:reject, message, _}} -> {:reject, message}
+      {:mrf, {:reject, message, _}} -> {:reject, message}
       e -> {:error, e}
     end
   end
 
+  defp validation_fixups(message, meta) do
+    # Insert copy of hashtags as strings for the non-hashtag table indexing
+    message =
+      if message["tag"] do
+        tag = Object.hashtags(%Object{data: message}) ++ (message["tag"] || [])
+        Map.put(message, "tag", tag)
+      else
+        message
+      end
+
+    {:ok, message, meta}
+  end
+
   defp maybe_federate(%Object{}, _), do: {:ok, :not_federated}
 
   defp maybe_federate(%Activity{} = activity, meta) do
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 8c7d6a747..4070ed14d 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -564,7 +564,7 @@ def handle_incoming(
            Pipeline.common_pipeline(data, local: false) do
       {:ok, activity}
     else
-      {:error, {:validate_object, _}} = e ->
+      {:error, {:validate, _}} = e ->
         # Check if we have a create activity for this
         with {:ok, object_id} <- ObjectValidators.ObjectID.cast(data["object"]),
              %Activity{data: %{"actor" => actor}} <-
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
index b003e30c7..895baebc9 100644
--- a/lib/pleroma/web/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -228,17 +228,7 @@ def favorite_helper(user, id) do
       {:find_object, _} ->
         {:error, :not_found}
 
-      {:common_pipeline,
-       {
-         :error,
-         {
-           :validate_object,
-           {
-             :error,
-             changeset
-           }
-         }
-       }} = e ->
+      {:common_pipeline, {:error, {:validate, {:error, changeset}}}} = e ->
         if {:object, {"already liked by this actor", []}} in changeset.errors do
           {:ok, :already_liked}
         else

From 7ebfe899007002f5bbf8744a8f0b582e0e13342e Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Tue, 12 Jan 2021 11:14:09 +0100
Subject: [PATCH 138/339] object_validators: Mark validate_data as private

---
 .../activity_pub/object_validators/accept_reject_validator.ex   | 2 +-
 .../web/activity_pub/object_validators/announce_validator.ex    | 2 +-
 .../web/activity_pub/object_validators/answer_validator.ex      | 2 +-
 .../activity_pub/object_validators/article_note_validator.ex    | 2 +-
 .../web/activity_pub/object_validators/attachment_validator.ex  | 2 +-
 .../web/activity_pub/object_validators/audio_video_validator.ex | 2 +-
 .../web/activity_pub/object_validators/block_validator.ex       | 2 +-
 .../activity_pub/object_validators/chat_message_validator.ex    | 2 +-
 .../object_validators/create_chat_message_validator.ex          | 2 +-
 .../activity_pub/object_validators/create_generic_validator.ex  | 2 +-
 .../web/activity_pub/object_validators/delete_validator.ex      | 2 +-
 .../web/activity_pub/object_validators/emoji_react_validator.ex | 2 +-
 .../web/activity_pub/object_validators/event_validator.ex       | 2 +-
 .../web/activity_pub/object_validators/follow_validator.ex      | 2 +-
 .../web/activity_pub/object_validators/like_validator.ex        | 2 +-
 .../web/activity_pub/object_validators/question_validator.ex    | 2 +-
 .../web/activity_pub/object_validators/undo_validator.ex        | 2 +-
 .../web/activity_pub/object_validators/update_validator.ex      | 2 +-
 18 files changed, 18 insertions(+), 18 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex b/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex
index d31e780c3..b577a1044 100644
--- a/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex
@@ -27,7 +27,7 @@ def cast_data(data) do
     |> cast(data, __schema__(:fields))
   end
 
-  def validate_data(cng) do
+  defp validate_data(cng) do
     cng
     |> validate_required([:id, :type, :actor, :to, :cc, :object])
     |> validate_inclusion(:type, ["Accept", "Reject"])
diff --git a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
index b08a33e68..576341790 100644
--- a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
@@ -50,7 +50,7 @@ def fix_after_cast(cng) do
     cng
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Announce"])
     |> validate_required([:id, :type, :object, :actor, :to, :cc])
diff --git a/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex b/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex
index 15e4413cd..c9bd9e42d 100644
--- a/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex
@@ -50,7 +50,7 @@ def changeset(struct, data) do
     |> cast(data, __schema__(:fields))
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Answer"])
     |> validate_required([:id, :inReplyTo, :name, :attributedTo, :actor])
diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
index 5910f4060..39ef6dc29 100644
--- a/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
@@ -95,7 +95,7 @@ def changeset(struct, data) do
     |> cast_embed(:tag)
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Article", "Note"])
     |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
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 e7b3a3922..4a0d1473d 100644
--- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
@@ -89,7 +89,7 @@ defp fix_url(data) do
     end
   end
 
-  def validate_data(cng) do
+  defp validate_data(cng) do
     cng
     |> validate_inclusion(:type, ~w[Document Audio Image Video])
     |> validate_required([:mediaType, :url, :type])
diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
index a04c95f4b..8a5a60526 100644
--- a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
@@ -137,7 +137,7 @@ def changeset(struct, data) do
     |> cast_embed(:tag)
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Audio", "Video"])
     |> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment])
diff --git a/lib/pleroma/web/activity_pub/object_validators/block_validator.ex b/lib/pleroma/web/activity_pub/object_validators/block_validator.ex
index c5f77bb76..88948135f 100644
--- a/lib/pleroma/web/activity_pub/object_validators/block_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/block_validator.ex
@@ -26,7 +26,7 @@ def cast_data(data) do
     |> cast(data, __schema__(:fields))
   end
 
-  def validate_data(cng) do
+  defp validate_data(cng) do
     cng
     |> validate_required([:id, :type, :actor, :to, :cc, :object])
     |> validate_inclusion(:type, ["Block"])
diff --git a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
index 1189778f2..b153156b0 100644
--- a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
@@ -67,7 +67,7 @@ def changeset(struct, data) do
     |> cast_embed(:attachment)
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["ChatMessage"])
     |> validate_required([:id, :actor, :to, :type, :published])
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex
index 8384c16a7..7a31a99bf 100644
--- a/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex
@@ -39,7 +39,7 @@ def cast_and_validate(data, meta \\ []) do
     |> validate_data(meta)
   end
 
-  def validate_data(cng, meta \\ []) do
+  defp validate_data(cng, meta) do
     cng
     |> validate_required([:id, :actor, :to, :type, :object])
     |> validate_inclusion(:type, ["Create"])
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
index bf56a918c..e06e442f4 100644
--- a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
@@ -79,7 +79,7 @@ defp fix(data, meta) do
     |> CommonFixes.fix_actor()
   end
 
-  def validate_data(cng, meta \\ []) do
+  defp validate_data(cng, meta) do
     cng
     |> validate_required([:actor, :type, :object])
     |> validate_inclusion(:type, ["Create"])
diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
index fc1a79a72..7da67bf16 100644
--- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
@@ -53,7 +53,7 @@ def add_deleted_activity_id(cng) do
     Tombstone
     Video
   }
-  def validate_data(cng) do
+  defp validate_data(cng) do
     cng
     |> validate_required([:id, :type, :actor, :to, :cc, :object])
     |> validate_inclusion(:type, ["Delete"])
diff --git a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
index 1906e597e..ec7566515 100644
--- a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
@@ -70,7 +70,7 @@ def validate_emoji(cng) do
     end
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["EmojiReact"])
     |> validate_required([:id, :type, :object, :actor, :context, :to, :cc, :content])
diff --git a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
index 0112a074d..d42458ef5 100644
--- a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
@@ -86,7 +86,7 @@ def changeset(struct, data) do
     |> cast_embed(:tag)
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Event"])
     |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
diff --git a/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex b/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex
index 6e428bacc..239cee5e7 100644
--- a/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex
@@ -27,7 +27,7 @@ def cast_data(data) do
     |> cast(data, __schema__(:fields))
   end
 
-  def validate_data(cng) do
+  defp validate_data(cng) do
     cng
     |> validate_required([:id, :type, :actor, :to, :cc, :object])
     |> validate_inclusion(:type, ["Follow"])
diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
index 30c40b238..509da507b 100644
--- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
@@ -76,7 +76,7 @@ def fix_recipients(cng) do
     end
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Like"])
     |> validate_required([:id, :type, :object, :actor, :context, :to, :cc])
diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
index 7acb1e928..7012e2e1d 100644
--- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
@@ -100,7 +100,7 @@ def changeset(struct, data) do
     |> cast_embed(:tag)
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Question"])
     |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
diff --git a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
index 783a79ddb..e8af60ffa 100644
--- a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
@@ -38,7 +38,7 @@ def changeset(struct, data) do
     |> cast(data, __schema__(:fields))
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Undo"])
     |> validate_required([:id, :type, :object, :actor, :to, :cc])
diff --git a/lib/pleroma/web/activity_pub/object_validators/update_validator.ex b/lib/pleroma/web/activity_pub/object_validators/update_validator.ex
index a66d41400..6bb1dc7fa 100644
--- a/lib/pleroma/web/activity_pub/object_validators/update_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/update_validator.ex
@@ -28,7 +28,7 @@ def cast_data(data) do
     |> cast(data, __schema__(:fields))
   end
 
-  def validate_data(cng) do
+  defp validate_data(cng) do
     cng
     |> validate_required([:id, :type, :actor, :to, :cc, :object])
     |> validate_inclusion(:type, ["Update"])

From 4ecf6ceea6062d68c382918010dc577151d0131c Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Thu, 1 Apr 2021 10:01:31 -0500
Subject: [PATCH 139/339] Enforce user.notification_settings is NOT NULL

---
 ...401143153_user_notification_settings_fix.exs | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)
 create mode 100644 priv/repo/migrations/20210401143153_user_notification_settings_fix.exs

diff --git a/priv/repo/migrations/20210401143153_user_notification_settings_fix.exs b/priv/repo/migrations/20210401143153_user_notification_settings_fix.exs
new file mode 100644
index 000000000..cf68f1be6
--- /dev/null
+++ b/priv/repo/migrations/20210401143153_user_notification_settings_fix.exs
@@ -0,0 +1,17 @@
+defmodule Pleroma.Repo.Migrations.UserNotificationSettingsFix do
+  use Ecto.Migration
+
+  def up do
+    execute(~s(UPDATE users
+    SET 
+      notification_settings = '{"followers": true, "follows": true, "non_follows": true, "non_followers": true}'::jsonb WHERE notification_settings IS NULL
+))
+
+    execute("ALTER TABLE users
+    ALTER COLUMN notification_settings SET NOT NULL")
+  end
+
+  def down do
+    :ok
+  end
+end

From 765f0907dfa9371038188ee35fc3b241be796d26 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Thu, 1 Apr 2021 10:07:57 -0500
Subject: [PATCH 140/339] Document user login failure fix for NULL
 notification_settings

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 43f2bb638..31a22bb31 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Fixed
 
 - Try to save exported ConfigDB settings (migrate_from_db) in the system temp directory if default location is not writable.
+- User login failures if their `notification_settings` were in a NULL state.
 
 ## [2.3.0] - 2020-03-01
 

From 31ce8a37304e24381b26d678dfbbc7b7a6b1ba35 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Thu, 1 Apr 2021 10:09:32 -0500
Subject: [PATCH 141/339] Fix CHANGELOG entry meant for next release

---
 CHANGELOG.md | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 43f2bb638..6c45cad85 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,8 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## Unreleased
 
+### Changed
+
 - The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
 
+### Added
+
+- MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded.
+
 ## Unreleased (Patch)
 
 ### Fixed
@@ -75,7 +81,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Ability to define custom HTTP headers per each frontend
 - MRF (`NoEmptyPolicy`): New MRF Policy which will deny empty statuses or statuses of only mentions from being created by local users
 - New users will receive a simple email confirming their registration if no other emails will be dispatched. (e.g., Welcome, Confirmation, or Approval Required)
-- MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded.
 
 <details>
   <summary>API Changes</summary>

From ef36f7fa5cff0a0d364aff192954556b0d2b0d2a Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Thu, 1 Apr 2021 13:49:04 +0200
Subject: [PATCH 142/339] Move tag fixup to object_validator

---
 .../web/activity_pub/object_validator.ex      | 32 +++++++++++++++----
 lib/pleroma/web/activity_pub/pipeline.ex      | 14 --------
 2 files changed, 26 insertions(+), 20 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 15784b28c..70d9a35a9 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -113,9 +113,34 @@ def validate(
     end
   end
 
+  def validate(%{"type" => type} = object, meta)
+      when type in ~w[Event Question Audio Video Article] do
+    validator =
+      case type do
+        "Event" -> EventValidator
+        "Question" -> QuestionValidator
+        "Audio" -> AudioVideoValidator
+        "Video" -> AudioVideoValidator
+        "Article" -> ArticleNoteValidator
+      end
+
+    with {:ok, object} <-
+           object
+           |> validator.cast_and_validate()
+           |> Ecto.Changeset.apply_action(:insert) do
+      object = stringify_keys(object)
+
+      # Insert copy of hashtags as strings for the non-hashtag table indexing
+      tag = (object["tag"] || []) ++ Object.hashtags(%Object{data: object})
+      object = Map.put(object, "tag", tag)
+
+      {:ok, object, meta}
+    end
+  end
+
   def validate(%{"type" => type} = object, meta)
       when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
-      Event ChatMessage Question Audio Video Article Answer] do
+      ChatMessage Answer] do
     validator =
       case type do
         "Accept" -> AcceptRejectValidator
@@ -125,12 +150,7 @@ def validate(%{"type" => type} = object, meta)
         "Like" -> LikeValidator
         "EmojiReact" -> EmojiReactValidator
         "Announce" -> AnnounceValidator
-        "Event" -> EventValidator
         "ChatMessage" -> ChatMessageValidator
-        "Question" -> QuestionValidator
-        "Audio" -> AudioVideoValidator
-        "Video" -> AudioVideoValidator
-        "Article" -> ArticleNoteValidator
         "Answer" -> AnswerValidator
       end
 
diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex
index e184a9376..377eccb92 100644
--- a/lib/pleroma/web/activity_pub/pipeline.ex
+++ b/lib/pleroma/web/activity_pub/pipeline.ex
@@ -44,7 +44,6 @@ def do_common_pipeline(%{__struct__: _}, _meta), do: {:error, :is_struct}
 
   def do_common_pipeline(message, meta) do
     with {_, {:ok, message, meta}} <- {:validate, @object_validator.validate(message, meta)},
-         {_, {:ok, message, meta}} <- {:fixup, validation_fixups(message, meta)},
          {_, {:ok, message, meta}} <- {:mrf, @mrf.pipeline_filter(message, meta)},
          {_, {:ok, message, meta}} <- {:persist, @activity_pub.persist(message, meta)},
          {_, {:ok, message, meta}} <- {:side_effects, @side_effects.handle(message, meta)},
@@ -56,19 +55,6 @@ def do_common_pipeline(message, meta) do
     end
   end
 
-  defp validation_fixups(message, meta) do
-    # Insert copy of hashtags as strings for the non-hashtag table indexing
-    message =
-      if message["tag"] do
-        tag = Object.hashtags(%Object{data: message}) ++ (message["tag"] || [])
-        Map.put(message, "tag", tag)
-      else
-        message
-      end
-
-    {:ok, message, meta}
-  end
-
   defp maybe_federate(%Object{}, _), do: {:ok, :not_federated}
 
   defp maybe_federate(%Activity{} = activity, meta) do

From e56779dd8d1668177afa199aaa836bea70e68420 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Thu, 10 Sep 2020 11:09:11 +0200
Subject: [PATCH 143/339] Transmogrifier: Simplify fix_explicit_addressing and
 fix_implicit_addressing

---
 .../web/activity_pub/transmogrifier.ex        | 51 ++++++-------------
 .../web/activity_pub/transmogrifier_test.exs  |  6 +--
 2 files changed, 19 insertions(+), 38 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 4070ed14d..047f23918 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -72,17 +72,21 @@ def fix_addressing_list(map, field) do
     end
   end
 
-  def fix_explicit_addressing(
-        %{"to" => to, "cc" => cc} = object,
-        explicit_mentions,
-        follower_collection
-      ) do
-    explicit_to = Enum.filter(to, fn x -> x in explicit_mentions end)
+  # if directMessage flag is set to true, leave the addressing alone
+  def fix_explicit_addressing(%{"directMessage" => true} = object, _follower_collection),
+    do: object
 
+  def fix_explicit_addressing(%{"to" => to, "cc" => cc} = object, follower_collection) do
+    explicit_mentions =
+      Utils.determine_explicit_mentions(object) ++
+        [Pleroma.Constants.as_public(), follower_collection]
+
+    explicit_to = Enum.filter(to, fn x -> x in explicit_mentions end)
     explicit_cc = Enum.filter(to, fn x -> x not in explicit_mentions end)
 
     final_cc =
       (cc ++ explicit_cc)
+      |> Enum.filter(& &1)
       |> Enum.reject(fn x -> String.ends_with?(x, "/followers") and x != follower_collection end)
       |> Enum.uniq()
 
@@ -91,29 +95,6 @@ def fix_explicit_addressing(
     |> Map.put("cc", final_cc)
   end
 
-  def fix_explicit_addressing(object, _explicit_mentions, _followers_collection), do: object
-
-  # if directMessage flag is set to true, leave the addressing alone
-  def fix_explicit_addressing(%{"directMessage" => true} = object), do: object
-
-  def fix_explicit_addressing(object) do
-    explicit_mentions = Utils.determine_explicit_mentions(object)
-
-    %User{follower_address: follower_collection} =
-      object
-      |> Containment.get_actor()
-      |> User.get_cached_by_ap_id()
-
-    explicit_mentions =
-      explicit_mentions ++
-        [
-          Pleroma.Constants.as_public(),
-          follower_collection
-        ]
-
-    fix_explicit_addressing(object, explicit_mentions, follower_collection)
-  end
-
   # if as:Public is addressed, then make sure the followers collection is also addressed
   # so that the activities will be delivered to local users.
   def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collection) do
@@ -137,19 +118,19 @@ def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collec
     end
   end
 
-  def fix_implicit_addressing(object, _), do: object
-
   def fix_addressing(object) do
-    {:ok, %User{} = user} = User.get_or_fetch_by_ap_id(object["actor"])
-    followers_collection = User.ap_followers(user)
+    {:ok, %User{follower_address: follower_collection}} =
+      object
+      |> Containment.get_actor()
+      |> User.get_or_fetch_by_ap_id()
 
     object
     |> fix_addressing_list("to")
     |> fix_addressing_list("cc")
     |> fix_addressing_list("bto")
     |> fix_addressing_list("bcc")
-    |> fix_explicit_addressing()
-    |> fix_implicit_addressing(followers_collection)
+    |> fix_explicit_addressing(follower_collection)
+    |> fix_implicit_addressing(follower_collection)
   end
 
   def fix_actor(%{"attributedTo" => actor} = object) do
diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs
index 4c3fcb44a..bb0b58e4d 100644
--- a/test/pleroma/web/activity_pub/transmogrifier_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs
@@ -446,7 +446,7 @@ test "moves non-explicitly mentioned actors to cc", %{user: user} do
           end)
       }
 
-      fixed_object = Transmogrifier.fix_explicit_addressing(object)
+      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"]
@@ -459,7 +459,7 @@ test "does not move actor's follower collection to cc", %{user: user} do
         "cc" => []
       }
 
-      fixed_object = Transmogrifier.fix_explicit_addressing(object)
+      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
@@ -473,7 +473,7 @@ test "removes recipient's follower collection from cc", %{user: user} do
         "cc" => [user.follower_address, recipient.follower_address]
       }
 
-      fixed_object = Transmogrifier.fix_explicit_addressing(object)
+      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"]

From e2a3365b5ce86293a5fed28c06b2e7d9dd97c9d1 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Thu, 10 Sep 2020 11:08:05 +0200
Subject: [PATCH 144/339] ObjectValidator.CommonFixes: Introduce
 fix_objects_defaults and fix_activity_defaults

---
 .../object_validators/recipients.ex           | 22 +++++++++------
 .../article_note_validator.ex                 |  3 +-
 .../audio_video_validator.ex                  |  3 +-
 .../object_validators/common_fixes.ex         | 28 +++++++++++++++----
 .../create_generic_validator.ex               | 12 +-------
 .../object_validators/event_validator.ex      |  4 +--
 .../object_validators/question_validator.ex   |  4 +--
 .../object_validators/recipients_test.exs     |  2 +-
 .../transmogrifier/audio_handling_test.exs    |  6 +++-
 .../transmogrifier/event_handling_test.exs    |  2 +-
 10 files changed, 50 insertions(+), 36 deletions(-)

diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex
index af4b0e527..b76547e75 100644
--- a/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex
+++ b/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex
@@ -15,19 +15,23 @@ def cast(object) when is_binary(object) do
 
   def cast(data) when is_list(data) do
     data
-    |> Enum.reduce_while({:ok, []}, fn element, {:ok, list} ->
-      case ObjectID.cast(element) do
-        {:ok, id} ->
-          {:cont, {:ok, [id | list]}}
+    |> Enum.reduce_while({:ok, []}, fn
+      nil, {:ok, list} ->
+        {:cont, {:ok, list}}
 
-        _ ->
-          {:halt, :error}
-      end
+      element, {:ok, list} ->
+        case ObjectID.cast(element) do
+          {:ok, id} ->
+            {:cont, {:ok, [id | list]}}
+
+          _ ->
+            {:halt, {:error, element}}
+        end
     end)
   end
 
-  def cast(_) do
-    :error
+  def cast(data) do
+    {:error, data}
   end
 
   def dump(data) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
index 39ef6dc29..d2026b5ea 100644
--- a/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
@@ -79,9 +79,8 @@ defp fix_url(data), do: data
 
   defp fix(data) do
     data
-    |> CommonFixes.fix_defaults()
-    |> CommonFixes.fix_attribution()
     |> CommonFixes.fix_actor()
+    |> CommonFixes.fix_object_defaults()
     |> fix_url()
     |> Transmogrifier.fix_emoji()
   end
diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
index 8a5a60526..8ee432947 100644
--- a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
@@ -120,9 +120,8 @@ defp fix_content(data), do: data
 
   defp fix(data) do
     data
-    |> CommonFixes.fix_defaults()
-    |> CommonFixes.fix_attribution()
     |> CommonFixes.fix_actor()
+    |> CommonFixes.fix_object_defaults()
     |> Transmogrifier.fix_emoji()
     |> fix_url()
     |> fix_content()
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
index 5f2c633bc..950eb1494 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
@@ -3,26 +3,44 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
+  alias Pleroma.EctoType.ActivityPub.ObjectValidators
   alias Pleroma.Object.Containment
+  alias Pleroma.User
+  alias Pleroma.Web.ActivityPub.Transmogrifier
   alias Pleroma.Web.ActivityPub.Utils
 
-  # based on Pleroma.Web.ActivityPub.Utils.lazy_put_objects_defaults
-  def fix_defaults(data) do
+  def fix_object_defaults(data) do
     %{data: %{"id" => context}, id: context_id} =
       Utils.create_context(data["context"] || data["conversation"])
 
+    %User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["attributedTo"])
+    {:ok, to} = ObjectValidators.Recipients.cast(data["to"] || [])
+    {:ok, cc} = ObjectValidators.Recipients.cast(data["cc"] || [])
+
     data
     |> Map.put("context", context)
     |> Map.put("context_id", context_id)
+    |> Map.put("to", to)
+    |> Map.put("cc", cc)
+    |> Transmogrifier.fix_explicit_addressing(follower_collection)
+    |> Transmogrifier.fix_implicit_addressing(follower_collection)
   end
 
-  def fix_attribution(data) do
+  def fix_activity_defaults(data, meta) do
+    object = meta[:object_data] || %{}
+
     data
-    |> Map.put_new("actor", data["attributedTo"])
+    |> Map.put_new("to", object["to"] || [])
+    |> Map.put_new("cc", object["cc"] || [])
+    |> Map.put_new("bto", object["bto"] || [])
+    |> Map.put_new("bcc", object["bcc"] || [])
   end
 
   def fix_actor(data) do
-    actor = Containment.get_actor(data)
+    actor =
+      data
+      |> Map.put_new("actor", data["attributedTo"])
+      |> Containment.get_actor()
 
     data
     |> Map.put("actor", actor)
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
index e06e442f4..99e8dc6c7 100644
--- a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
@@ -62,21 +62,11 @@ defp fix_context(data, meta) do
     end
   end
 
-  defp fix_addressing(data, meta) do
-    if object = meta[:object_data] do
-      data
-      |> Map.put_new("to", object["to"] || [])
-      |> Map.put_new("cc", object["cc"] || [])
-    else
-      data
-    end
-  end
-
   defp fix(data, meta) do
     data
     |> fix_context(meta)
-    |> fix_addressing(meta)
     |> CommonFixes.fix_actor()
+    |> CommonFixes.fix_activity_defaults(meta)
   end
 
   defp validate_data(cng, meta) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
index d42458ef5..fee2e997a 100644
--- a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
@@ -72,8 +72,8 @@ def cast_data(data) do
 
   defp fix(data) do
     data
-    |> CommonFixes.fix_defaults()
-    |> CommonFixes.fix_attribution()
+    |> CommonFixes.fix_actor()
+    |> CommonFixes.fix_object_defaults()
     |> Transmogrifier.fix_emoji()
   end
 
diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
index 7012e2e1d..083d08ec4 100644
--- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
@@ -83,8 +83,8 @@ defp fix_closed(data) do
 
   defp fix(data) do
     data
-    |> CommonFixes.fix_defaults()
-    |> CommonFixes.fix_attribution()
+    |> CommonFixes.fix_actor()
+    |> CommonFixes.fix_object_defaults()
     |> Transmogrifier.fix_emoji()
     |> fix_closed()
   end
diff --git a/test/pleroma/ecto_type/activity_pub/object_validators/recipients_test.exs b/test/pleroma/ecto_type/activity_pub/object_validators/recipients_test.exs
index d3a2fd13f..ce8bef39f 100644
--- a/test/pleroma/ecto_type/activity_pub/object_validators/recipients_test.exs
+++ b/test/pleroma/ecto_type/activity_pub/object_validators/recipients_test.exs
@@ -9,7 +9,7 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.RecipientsTest do
   test "it asserts that all elements of the list are object ids" do
     list = ["https://lain.com/users/lain", "invalid"]
 
-    assert :error == Recipients.cast(list)
+    assert {:error, "invalid"} == Recipients.cast(list)
   end
 
   test "it works with a list" do
diff --git a/test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs
index e733f167d..032ad24b5 100644
--- a/test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs
@@ -24,6 +24,8 @@ test "it works for incoming listens" do
       "actor" => "http://mastodon.example.org/users/admin",
       "object" => %{
         "type" => "Audio",
+        "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+        "cc" => [],
         "id" => "http://mastodon.example.org/users/admin/listens/1234",
         "attributedTo" => "http://mastodon.example.org/users/admin",
         "title" => "lain radio episode 1",
@@ -61,7 +63,9 @@ test "Funkwhale Audio object" do
 
     assert object.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
 
-    assert object.data["cc"] == []
+    assert object.data["cc"] == [
+             "https://channels.tests.funkwhale.audio/federation/actors/compositions/followers"
+           ]
 
     assert object.data["url"] == "https://channels.tests.funkwhale.audio/library/tracks/74"
 
diff --git a/test/pleroma/web/activity_pub/transmogrifier/event_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/event_handling_test.exs
index c4879fda1..14f5f704a 100644
--- a/test/pleroma/web/activity_pub/transmogrifier/event_handling_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier/event_handling_test.exs
@@ -31,7 +31,7 @@ test "Mobilizon Event object" do
              )
 
     assert object.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
-    assert object.data["cc"] == []
+    assert object.data["cc"] == ["https://mobilizon.org/@tcit/followers"]
 
     assert object.data["url"] ==
              "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"

From c9449326747f8d33357f5179e69d3024b39089a0 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Thu, 10 Sep 2020 11:11:10 +0200
Subject: [PATCH 145/339] Pipeline Ingestion: Note

---
 .../object_validators/recipients.ex           |  25 +--
 lib/pleroma/web/activity_pub/activity_pub.ex  |   2 +-
 .../web/activity_pub/object_validator.ex      |   7 +-
 .../article_note_validator.ex                 |  29 +++-
 .../object_validators/common_fixes.ex         |  18 +-
 .../object_validators/common_validations.ex   |   1 +
 .../create_note_validator.ex                  |  29 ----
 lib/pleroma/web/activity_pub/side_effects.ex  |  15 +-
 .../web/activity_pub/transmogrifier.ex        |  12 +-
 lib/pleroma/web/federator.ex                  |   5 +
 .../activitypub-client-post-activity.json     |   1 +
 test/pleroma/activity_test.exs                |   4 +-
 .../object_validators/recipients_test.exs     |   4 +-
 test/pleroma/notification_test.exs            |   6 +
 .../activity_pub_controller_test.exs          |  45 ++---
 .../transmogrifier/note_handling_test.exs     | 155 ++++++++----------
 .../web/activity_pub/transmogrifier_test.exs  |   4 +-
 test/pleroma/web/federator_test.exs           |   6 +-
 .../static_fe/static_fe_controller_test.exs   |  13 +-
 19 files changed, 202 insertions(+), 179 deletions(-)
 delete mode 100644 lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex

diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex
index b76547e75..a03471462 100644
--- a/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex
+++ b/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex
@@ -13,20 +13,23 @@ def cast(object) when is_binary(object) do
     cast([object])
   end
 
+  def cast(object) when is_map(object) do
+    case ObjectID.cast(object) do
+      {:ok, data} -> {:ok, data}
+      _ -> :error
+    end
+  end
+
   def cast(data) when is_list(data) do
     data
-    |> Enum.reduce_while({:ok, []}, fn
-      nil, {:ok, list} ->
-        {:cont, {:ok, list}}
+    |> Enum.reduce_while({:ok, []}, fn element, {:ok, list} ->
+      case ObjectID.cast(element) do
+        {:ok, id} ->
+          {:cont, {:ok, [id | list]}}
 
-      element, {:ok, list} ->
-        case ObjectID.cast(element) do
-          {:ok, id} ->
-            {:cont, {:ok, [id | list]}}
-
-          _ ->
-            {:halt, {:error, element}}
-        end
+        _ ->
+          {:cont, {:ok, list}}
+      end
     end)
   end
 
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index efbf92c70..b74af3f3b 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -88,7 +88,7 @@ defp increase_replies_count_if_reply(%{
 
   defp increase_replies_count_if_reply(_create_data), do: :noop
 
-  @object_types ~w[ChatMessage Question Answer Audio Video Event Article]
+  @object_types ~w[ChatMessage Question Answer Audio Video Event Article Note]
   @impl true
   def persist(%{"type" => type} = object, meta) when type in @object_types do
     with {:ok, object} <- Object.create(object) do
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 70d9a35a9..e5b35cdd4 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -101,7 +101,7 @@ def validate(
         %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
         meta
       )
-      when objtype in ~w[Question Answer Audio Video Event Article] do
+      when objtype in ~w[Question Answer Audio Video Event Article Note] do
     with {:ok, object_data} <- cast_and_apply(object),
          meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
          {:ok, create_activity} <-
@@ -114,7 +114,7 @@ def validate(
   end
 
   def validate(%{"type" => type} = object, meta)
-      when type in ~w[Event Question Audio Video Article] do
+      when type in ~w[Event Question Audio Video Article Note] do
     validator =
       case type do
         "Event" -> EventValidator
@@ -122,6 +122,7 @@ def validate(%{"type" => type} = object, meta)
         "Audio" -> AudioVideoValidator
         "Video" -> AudioVideoValidator
         "Article" -> ArticleNoteValidator
+        "Note" -> ArticleNoteValidator
       end
 
     with {:ok, object} <-
@@ -183,7 +184,7 @@ def cast_and_apply(%{"type" => "Event"} = object) do
     EventValidator.cast_and_apply(object)
   end
 
-  def cast_and_apply(%{"type" => "Article"} = object) do
+  def cast_and_apply(%{"type" => type} = object) when type in ~w[Article Note] do
     ArticleNoteValidator.cast_and_apply(object)
   end
 
diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
index d2026b5ea..193f85f49 100644
--- a/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
@@ -50,6 +50,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
 
     field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
     field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
+
+    field(:replies, {:array, ObjectValidators.ObjectID}, default: [])
   end
 
   def cast_and_apply(data) do
@@ -65,24 +67,39 @@ def cast_and_validate(data) do
   end
 
   def cast_data(data) do
-    data = fix(data)
-
     %__MODULE__{}
     |> changeset(data)
   end
 
-  defp fix_url(%{"url" => url} = data) when is_map(url) do
-    Map.put(data, "url", url["href"])
-  end
-
+  defp fix_url(%{"url" => url} = data) when is_bitstring(url), do: data
+  defp fix_url(%{"url" => url} = data) when is_map(url), do: Map.put(data, "url", url["href"])
   defp fix_url(data), do: data
 
+  defp fix_tag(%{"tag" => tag} = data) when is_list(tag), do: data
+  defp fix_tag(%{"tag" => tag} = data) when is_map(tag), do: Map.put(data, "tag", [tag])
+  defp fix_tag(data), do: Map.drop(data, ["tag"])
+
+  defp fix_replies(%{"replies" => %{"first" => %{"items" => replies}}} = data)
+       when is_list(replies),
+       do: Map.put(data, "replies", replies)
+
+  defp fix_replies(%{"replies" => %{"items" => replies}} = data) when is_list(replies),
+    do: Map.put(data, "replies", replies)
+
+  defp fix_replies(%{"replies" => replies} = data) when is_bitstring(replies),
+    do: Map.drop(data, ["replies"])
+
+  defp fix_replies(data), do: data
+
   defp fix(data) do
     data
     |> CommonFixes.fix_actor()
     |> CommonFixes.fix_object_defaults()
     |> fix_url()
+    |> fix_tag()
+    |> fix_replies()
     |> Transmogrifier.fix_emoji()
+    |> Transmogrifier.fix_content_map()
   end
 
   def changeset(struct, data) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
index 950eb1494..7309f6af2 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
@@ -26,14 +26,20 @@ def fix_object_defaults(data) do
     |> Transmogrifier.fix_implicit_addressing(follower_collection)
   end
 
-  def fix_activity_defaults(data, meta) do
+  defp fix_activity_recipients(activity, field, object) do
+    {:ok, data} = ObjectValidators.Recipients.cast(activity[field] || object[field])
+
+    Map.put(activity, field, data)
+  end
+
+  def fix_activity_defaults(activity, meta) do
     object = meta[:object_data] || %{}
 
-    data
-    |> Map.put_new("to", object["to"] || [])
-    |> Map.put_new("cc", object["cc"] || [])
-    |> Map.put_new("bto", object["bto"] || [])
-    |> Map.put_new("bcc", object["bcc"] || [])
+    activity
+    |> fix_activity_recipients("to", object)
+    |> fix_activity_recipients("cc", object)
+    |> fix_activity_recipients("bto", object)
+    |> fix_activity_recipients("bcc", object)
   end
 
   def fix_actor(data) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
index 093549a45..85ac07044 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
@@ -14,6 +14,7 @@ def validate_any_presence(cng, fields) do
       fields
       |> Enum.map(fn field -> get_field(cng, field) end)
       |> Enum.any?(fn
+        nil -> false
         [] -> false
         _ -> true
       end)
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex
deleted file mode 100644
index a85a0298c..000000000
--- a/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex
+++ /dev/null
@@ -1,29 +0,0 @@
-# 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.CreateNoteValidator do
-  use Ecto.Schema
-
-  alias Pleroma.EctoType.ActivityPub.ObjectValidators
-  alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator
-
-  import Ecto.Changeset
-
-  @primary_key false
-
-  embedded_schema do
-    field(:id, ObjectValidators.ObjectID, primary_key: true)
-    field(:actor, ObjectValidators.ObjectID)
-    field(:type, :string)
-    field(:to, ObjectValidators.Recipients, default: [])
-    field(:cc, ObjectValidators.Recipients, default: [])
-    field(:bto, ObjectValidators.Recipients, default: [])
-    field(:bcc, ObjectValidators.Recipients, default: [])
-    embeds_one(:object, NoteValidator)
-  end
-
-  def cast_data(data) do
-    cast(%__MODULE__{}, data, __schema__(:fields))
-  end
-end
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 0b9a9f0c5..3234b9e43 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -203,6 +203,19 @@ def handle(%{data: %{"type" => "Create"}} = activity, meta) do
         Object.increase_replies_count(in_reply_to)
       end
 
+      reply_depth = (meta[:depth] || 0) + 1
+
+      # FIXME: Force inReplyTo to replies
+      if Pleroma.Web.Federator.allowed_thread_distance?(reply_depth) and
+           object.data["replies"] != nil do
+        for reply_id <- object.data["replies"] do
+          Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{
+            "id" => reply_id,
+            "depth" => reply_depth
+          })
+        end
+      end
+
       ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn ->
         Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
       end)
@@ -366,7 +379,7 @@ def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do
   end
 
   def handle_object_creation(%{"type" => objtype} = object, meta)
-      when objtype in ~w[Audio Video Question Event Article] do
+      when objtype in ~w[Audio Video Question Event Article Note] do
     with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
       {:ok, object, meta}
     end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 047f23918..28bc25363 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -404,10 +404,9 @@ def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id
   # - tags
   # - emoji
   def handle_incoming(
-        %{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
+        %{"type" => "Create", "object" => %{"type" => "Page"} = object} = data,
         options
-      )
-      when objtype in ~w{Note Page} do
+      ) do
     actor = Containment.get_actor(data)
 
     with nil <- Activity.get_create_by_object_ap_id(object["id"]),
@@ -499,14 +498,15 @@ def handle_incoming(
 
   def handle_incoming(
         %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
-        _options
+        options
       )
-      when objtype in ~w{Question Answer ChatMessage Audio Video Event Article} do
+      when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note} do
     data = Map.put(data, "object", strip_internal_fields(data["object"]))
+    options = Keyword.put(options, :local, false)
 
     with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
          nil <- Activity.get_create_by_object_ap_id(obj_id),
-         {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
+         {:ok, activity, _} <- Pipeline.common_pipeline(data, options) do
       {:ok, activity}
     else
       %Activity{} = activity -> {:ok, activity}
diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex
index f5ef76d32..69cfc2d52 100644
--- a/lib/pleroma/web/federator.ex
+++ b/lib/pleroma/web/federator.ex
@@ -96,6 +96,11 @@ def perform(:incoming_ap_doc, params) do
         Logger.debug("Unhandled actor #{actor}, #{inspect(e)}")
         {:error, e}
 
+      {:error, {:validate_object, _}} = e ->
+        Logger.error("Incoming AP doc validation error: #{inspect(e)}")
+        Logger.debug(Jason.encode!(params, pretty: true))
+        e
+
       e ->
         # Just drop those for now
         Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end)
diff --git a/test/fixtures/activitypub-client-post-activity.json b/test/fixtures/activitypub-client-post-activity.json
index c985e072b..e592081bc 100644
--- a/test/fixtures/activitypub-client-post-activity.json
+++ b/test/fixtures/activitypub-client-post-activity.json
@@ -3,6 +3,7 @@
   "type": "Create",
   "object": {
     "type": "Note",
+    "to": ["https://www.w3.org/ns/activitystreams#Public"],
     "content": "It's a note"
   },
   "to": ["https://www.w3.org/ns/activitystreams#Public"]
diff --git a/test/pleroma/activity_test.exs b/test/pleroma/activity_test.exs
index 390a06344..9911aa45c 100644
--- a/test/pleroma/activity_test.exs
+++ b/test/pleroma/activity_test.exs
@@ -123,7 +123,8 @@ test "when association is not loaded" do
           "type" => "Note",
           "content" => "find me!",
           "id" => "http://mastodon.example.org/users/admin/objects/1",
-          "attributedTo" => "http://mastodon.example.org/users/admin"
+          "attributedTo" => "http://mastodon.example.org/users/admin",
+          "to" => ["https://www.w3.org/ns/activitystreams#Public"]
         },
         "to" => ["https://www.w3.org/ns/activitystreams#Public"]
       }
@@ -132,6 +133,7 @@ test "when association is not loaded" do
       {:ok, japanese_activity} = Pleroma.Web.CommonAPI.post(user, %{status: "更新情報"})
       {:ok, job} = Pleroma.Web.Federator.incoming_ap_doc(params)
       {:ok, remote_activity} = ObanHelpers.perform(job)
+      remote_activity = Activity.get_by_id_with_object(remote_activity.id)
 
       %{
         japanese_activity: japanese_activity,
diff --git a/test/pleroma/ecto_type/activity_pub/object_validators/recipients_test.exs b/test/pleroma/ecto_type/activity_pub/object_validators/recipients_test.exs
index ce8bef39f..4cdafa898 100644
--- a/test/pleroma/ecto_type/activity_pub/object_validators/recipients_test.exs
+++ b/test/pleroma/ecto_type/activity_pub/object_validators/recipients_test.exs
@@ -6,10 +6,10 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.RecipientsTest do
   alias Pleroma.EctoType.ActivityPub.ObjectValidators.Recipients
   use Pleroma.DataCase, async: true
 
-  test "it asserts that all elements of the list are object ids" do
+  test "it only keeps elements that are valid object ids" do
     list = ["https://lain.com/users/lain", "invalid"]
 
-    assert {:error, "invalid"} == Recipients.cast(list)
+    assert {:ok, ["https://lain.com/users/lain"]} == Recipients.cast(list)
   end
 
   test "it works with a list" do
diff --git a/test/pleroma/notification_test.exs b/test/pleroma/notification_test.exs
index abf1b0410..85f895f0f 100644
--- a/test/pleroma/notification_test.exs
+++ b/test/pleroma/notification_test.exs
@@ -624,6 +624,8 @@ test "it sends notifications to mentioned users in new messages" do
         "actor" => user.ap_id,
         "object" => %{
           "type" => "Note",
+          "id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
+          "to" => ["https://www.w3.org/ns/activitystreams#Public"],
           "content" => "message with a Mention tag, but no explicit tagging",
           "tag" => [
             %{
@@ -655,6 +657,9 @@ test "it does not send notifications to users who are only cc in new messages" d
         "actor" => user.ap_id,
         "object" => %{
           "type" => "Note",
+          "id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
+          "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+          "cc" => [other_user.ap_id],
           "content" => "hi everyone",
           "attributedTo" => user.ap_id
         }
@@ -951,6 +956,7 @@ test "notifications are deleted if a remote user is deleted" do
         "cc" => [],
         "object" => %{
           "type" => "Note",
+          "id" => remote_user.ap_id <> "/objects/test",
           "content" => "Hello!",
           "tag" => [
             %{
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index 19e04d472..2de52323e 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -539,7 +539,7 @@ test "it inserts an incoming activity into the database" <>
         File.read!("test/fixtures/mastodon-post-activity.json")
         |> Jason.decode!()
         |> Map.put("actor", user.ap_id)
-        |> put_in(["object", "attridbutedTo"], user.ap_id)
+        |> put_in(["object", "attributedTo"], user.ap_id)
 
       conn =
         conn
@@ -820,29 +820,34 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn, da
       assert Instances.reachable?(sender_host)
     end
 
+    @tag capture_log: true
     test "it removes all follower collections but actor's", %{conn: conn} do
       [actor, recipient] = insert_pair(:user)
 
-      data =
-        File.read!("test/fixtures/activitypub-client-post-activity.json")
-        |> Jason.decode!()
+      to = [
+        recipient.ap_id,
+        recipient.follower_address,
+        "https://www.w3.org/ns/activitystreams#Public"
+      ]
 
-      object = Map.put(data["object"], "attributedTo", actor.ap_id)
+      cc = [recipient.follower_address, actor.follower_address]
 
-      data =
-        data
-        |> Map.put("id", Utils.generate_object_id())
-        |> Map.put("actor", actor.ap_id)
-        |> Map.put("object", object)
-        |> Map.put("cc", [
-          recipient.follower_address,
-          actor.follower_address
-        ])
-        |> Map.put("to", [
-          recipient.ap_id,
-          recipient.follower_address,
-          "https://www.w3.org/ns/activitystreams#Public"
-        ])
+      data = %{
+        "@context" => ["https://www.w3.org/ns/activitystreams"],
+        "type" => "Create",
+        "id" => Utils.generate_activity_id(),
+        "to" => to,
+        "cc" => cc,
+        "actor" => actor.ap_id,
+        "object" => %{
+          "type" => "Note",
+          "to" => to,
+          "cc" => cc,
+          "content" => "It's a note",
+          "attributedTo" => actor.ap_id,
+          "id" => Utils.generate_object_id()
+        }
+      }
 
       conn
       |> assign(:valid_signature, true)
@@ -852,7 +857,7 @@ test "it removes all follower collections but actor's", %{conn: conn} do
 
       ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
 
-      activity = Activity.get_by_ap_id(data["id"])
+      assert activity = Activity.get_by_ap_id(data["id"])
 
       assert activity.id
       assert actor.follower_address in activity.recipients
diff --git a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
index deb956410..3eeae4004 100644
--- a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
@@ -14,7 +14,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
 
   import Mock
   import Pleroma.Factory
-  import ExUnit.CaptureLog
 
   setup_all do
     Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -147,9 +146,7 @@ test "it does not crash if the object in inReplyTo can't be fetched" do
         data
         |> Map.put("object", object)
 
-      assert capture_log(fn ->
-               {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
-             end) =~ "[warn] Couldn't fetch \"https://404.site/whatever\", error: nil"
+      assert {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
     end
 
     test "it does not work for deactivated users" do
@@ -221,8 +218,25 @@ test "it works for incoming notices with hashtags" do
       {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
       object = Object.normalize(data["object"], fetch: false)
 
-      assert Enum.at(Object.tags(object), 2) == "moo"
-      assert Object.hashtags(object) == ["moo"]
+      assert match?(
+               %{
+                 "href" => "http://localtesting.pleroma.lol/users/lain",
+                 "name" => "@lain@localtesting.pleroma.lol",
+                 "type" => "Mention"
+               },
+               Enum.at(object.data["tag"], 0)
+             )
+
+      assert match?(
+               %{
+                 "href" => "http://mastodon.example.org/tags/moo",
+                 "name" => "#moo",
+                 "type" => "Hashtag"
+               },
+               Enum.at(object.data["tag"], 1)
+             )
+
+      assert "moo" == Enum.at(object.data["tag"], 2)
     end
 
     test "it works for incoming notices with contentMap" do
@@ -276,13 +290,11 @@ test "it ensures that address fields become lists" do
         File.read!("test/fixtures/mastodon-post-activity.json")
         |> Jason.decode!()
         |> Map.put("actor", user.ap_id)
-        |> Map.put("to", nil)
         |> Map.put("cc", nil)
 
       object =
         data["object"]
         |> Map.put("attributedTo", user.ap_id)
-        |> Map.put("to", nil)
         |> Map.put("cc", nil)
         |> Map.put("id", user.ap_id <> "/activities/12345678")
 
@@ -290,8 +302,7 @@ test "it ensures that address fields become lists" do
 
       {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
 
-      assert !is_nil(data["to"])
-      assert !is_nil(data["cc"])
+      refute is_nil(data["cc"])
     end
 
     test "it strips internal likes" do
@@ -330,70 +341,46 @@ test "it strips internal reactions" do
     end
 
     test "it correctly processes messages with non-array to field" do
-      user = insert(:user)
+      data =
+        File.read!("test/fixtures/mastodon-post-activity.json")
+        |> Poison.decode!()
+        |> Map.put("to", "https://www.w3.org/ns/activitystreams#Public")
+        |> put_in(["object", "to"], "https://www.w3.org/ns/activitystreams#Public")
 
-      message = %{
-        "@context" => "https://www.w3.org/ns/activitystreams",
-        "to" => "https://www.w3.org/ns/activitystreams#Public",
-        "type" => "Create",
-        "object" => %{
-          "content" => "blah blah blah",
-          "type" => "Note",
-          "attributedTo" => user.ap_id,
-          "inReplyTo" => nil
-        },
-        "actor" => user.ap_id
-      }
+      assert {:ok, activity} = Transmogrifier.handle_incoming(data)
 
-      assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+      assert [
+               "http://mastodon.example.org/users/admin/followers",
+               "http://localtesting.pleroma.lol/users/lain"
+             ] == activity.data["cc"]
 
       assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
     end
 
     test "it correctly processes messages with non-array cc field" do
-      user = insert(:user)
+      data =
+        File.read!("test/fixtures/mastodon-post-activity.json")
+        |> Poison.decode!()
+        |> Map.put("cc", "http://mastodon.example.org/users/admin/followers")
+        |> put_in(["object", "cc"], "http://mastodon.example.org/users/admin/followers")
 
-      message = %{
-        "@context" => "https://www.w3.org/ns/activitystreams",
-        "to" => user.follower_address,
-        "cc" => "https://www.w3.org/ns/activitystreams#Public",
-        "type" => "Create",
-        "object" => %{
-          "content" => "blah blah blah",
-          "type" => "Note",
-          "attributedTo" => user.ap_id,
-          "inReplyTo" => nil
-        },
-        "actor" => user.ap_id
-      }
+      assert {:ok, activity} = Transmogrifier.handle_incoming(data)
 
-      assert {:ok, activity} = Transmogrifier.handle_incoming(message)
-
-      assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
-      assert [user.follower_address] == activity.data["to"]
+      assert ["http://mastodon.example.org/users/admin/followers"] == activity.data["cc"]
+      assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
     end
 
     test "it correctly processes messages with weirdness in address fields" do
-      user = insert(:user)
+      data =
+        File.read!("test/fixtures/mastodon-post-activity.json")
+        |> Poison.decode!()
+        |> Map.put("cc", ["http://mastodon.example.org/users/admin/followers", ["¿"]])
+        |> put_in(["object", "cc"], ["http://mastodon.example.org/users/admin/followers", ["¿"]])
 
-      message = %{
-        "@context" => "https://www.w3.org/ns/activitystreams",
-        "to" => [nil, user.follower_address],
-        "cc" => ["https://www.w3.org/ns/activitystreams#Public", ["¿"]],
-        "type" => "Create",
-        "object" => %{
-          "content" => "…",
-          "type" => "Note",
-          "attributedTo" => user.ap_id,
-          "inReplyTo" => nil
-        },
-        "actor" => user.ap_id
-      }
+      assert {:ok, activity} = Transmogrifier.handle_incoming(data)
 
-      assert {:ok, activity} = Transmogrifier.handle_incoming(message)
-
-      assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
-      assert [user.follower_address] == activity.data["to"]
+      assert ["http://mastodon.example.org/users/admin/followers"] == activity.data["cc"]
+      assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
     end
   end
 
@@ -419,7 +406,11 @@ test "schedules background fetching of `replies` items if max thread depth limit
     } do
       clear_config([:instance, :federation_incoming_replies_max_depth], 10)
 
-      {:ok, _activity} = Transmogrifier.handle_incoming(data)
+      {:ok, activity} = Transmogrifier.handle_incoming(data)
+
+      object = Object.normalize(activity.data["object"])
+
+      assert object.data["replies"] == items
 
       for id <- items do
         job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
@@ -442,45 +433,41 @@ test "does NOT schedule background fetching of `replies` beyond max thread depth
     setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
 
     setup do
-      user = insert(:user)
+      replies = %{
+        "type" => "Collection",
+        "items" => [
+          Pleroma.Web.ActivityPub.Utils.generate_object_id(),
+          Pleroma.Web.ActivityPub.Utils.generate_object_id()
+        ]
+      }
 
-      {:ok, activity} = CommonAPI.post(user, %{status: "post1"})
+      activity =
+        File.read!("test/fixtures/mastodon-post-activity.json")
+        |> Poison.decode!()
+        |> Kernel.put_in(["object", "replies"], replies)
 
-      {:ok, reply1} =
-        CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: activity.id})
-
-      {:ok, reply2} =
-        CommonAPI.post(user, %{status: "reply2", in_reply_to_status_id: activity.id})
-
-      replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
-
-      {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
-
-      Repo.delete(activity.object)
-      Repo.delete(activity)
-
-      %{federation_output: federation_output, replies_uris: replies_uris}
+      %{activity: activity}
     end
 
     test "schedules background fetching of `replies` items if max thread depth limit allows", %{
-      federation_output: federation_output,
-      replies_uris: replies_uris
+      activity: activity
     } do
       clear_config([:instance, :federation_incoming_replies_max_depth], 1)
 
-      {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
+      assert {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(activity)
+      object = Object.normalize(data["object"])
 
-      for id <- replies_uris do
+      for id <- object.data["replies"] do
         job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
         assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
       end
     end
 
     test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
-         %{federation_output: federation_output} do
+         %{activity: activity} do
       clear_config([:instance, :federation_incoming_replies_max_depth], 0)
 
-      {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
+      {:ok, _activity} = Transmogrifier.handle_incoming(activity)
 
       assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
     end
@@ -498,6 +485,7 @@ test "successfully reserializes a message with inReplyTo == nil" do
         "object" => %{
           "to" => ["https://www.w3.org/ns/activitystreams#Public"],
           "cc" => [],
+          "id" => Utils.generate_object_id(),
           "type" => "Note",
           "content" => "Hi",
           "inReplyTo" => nil,
@@ -522,6 +510,7 @@ test "successfully reserializes a message with AS2 objects in IR" do
         "object" => %{
           "to" => ["https://www.w3.org/ns/activitystreams#Public"],
           "cc" => [],
+          "id" => Utils.generate_object_id(),
           "type" => "Note",
           "content" => "Hi",
           "inReplyTo" => nil,
diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs
index bb0b58e4d..5a3b57acb 100644
--- a/test/pleroma/web/activity_pub/transmogrifier_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs
@@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
   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
 
@@ -159,8 +160,7 @@ test "it adds the json-ld context and the conversation property" do
       {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
       {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
 
-      assert modified["@context"] ==
-               Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
+      assert modified["@context"] == Utils.make_json_ld_header()["@context"]
 
       assert modified["object"]["conversation"] == modified["context"]
     end
diff --git a/test/pleroma/web/federator_test.exs b/test/pleroma/web/federator_test.exs
index 532ee6d30..372b6a73a 100644
--- a/test/pleroma/web/federator_test.exs
+++ b/test/pleroma/web/federator_test.exs
@@ -123,7 +123,8 @@ test "successfully processes incoming AP docs with correct origin" do
           "type" => "Note",
           "content" => "hi world!",
           "id" => "http://mastodon.example.org/users/admin/objects/1",
-          "attributedTo" => "http://mastodon.example.org/users/admin"
+          "attributedTo" => "http://mastodon.example.org/users/admin",
+          "to" => ["https://www.w3.org/ns/activitystreams#Public"]
         },
         "to" => ["https://www.w3.org/ns/activitystreams#Public"]
       }
@@ -145,7 +146,8 @@ test "rejects incoming AP docs with incorrect origin" do
           "type" => "Note",
           "content" => "hi world!",
           "id" => "http://mastodon.example.org/users/admin/objects/1",
-          "attributedTo" => "http://mastodon.example.org/users/admin"
+          "attributedTo" => "http://mastodon.example.org/users/admin",
+          "to" => ["https://www.w3.org/ns/activitystreams#Public"]
         },
         "to" => ["https://www.w3.org/ns/activitystreams#Public"]
       }
diff --git a/test/pleroma/web/static_fe/static_fe_controller_test.exs b/test/pleroma/web/static_fe/static_fe_controller_test.exs
index 2af14dfeb..5752cffda 100644
--- a/test/pleroma/web/static_fe/static_fe_controller_test.exs
+++ b/test/pleroma/web/static_fe/static_fe_controller_test.exs
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do
 
   alias Pleroma.Activity
   alias Pleroma.Web.ActivityPub.Transmogrifier
+  alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.CommonAPI
 
   import Pleroma.Factory
@@ -185,16 +186,16 @@ test "404 for private status", %{conn: conn, user: user} do
     test "302 for remote cached status", %{conn: conn, user: user} do
       message = %{
         "@context" => "https://www.w3.org/ns/activitystreams",
-        "to" => user.follower_address,
-        "cc" => "https://www.w3.org/ns/activitystreams#Public",
         "type" => "Create",
+        "actor" => user.ap_id,
         "object" => %{
+          "to" => user.follower_address,
+          "cc" => "https://www.w3.org/ns/activitystreams#Public",
+          "id" => Utils.generate_object_id(),
           "content" => "blah blah blah",
           "type" => "Note",
-          "attributedTo" => user.ap_id,
-          "inReplyTo" => nil
-        },
-        "actor" => user.ap_id
+          "attributedTo" => user.ap_id
+        }
       }
 
       assert {:ok, activity} = Transmogrifier.handle_incoming(message)

From 641184fc7aff694e4e7e802b9204a1d313c0877c Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Thu, 10 Sep 2020 19:45:42 +0200
Subject: [PATCH 146/339] recipients fixes/hardening for CreateGenericValidator

---
 .../object_validators/recipients.ex           | 25 ++++----
 .../object_validators/common_fixes.ex         | 34 ++++++-----
 .../create_generic_validator.ex               | 60 +++++++++++++------
 .../transmogrifier/note_handling_test.exs     | 12 ++--
 4 files changed, 82 insertions(+), 49 deletions(-)

diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex
index a03471462..06fed8fb3 100644
--- a/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex
+++ b/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex
@@ -15,22 +15,27 @@ def cast(object) when is_binary(object) do
 
   def cast(object) when is_map(object) do
     case ObjectID.cast(object) do
-      {:ok, data} -> {:ok, data}
+      {:ok, data} -> {:ok, [data]}
       _ -> :error
     end
   end
 
   def cast(data) when is_list(data) do
-    data
-    |> Enum.reduce_while({:ok, []}, fn element, {:ok, list} ->
-      case ObjectID.cast(element) do
-        {:ok, id} ->
-          {:cont, {:ok, [id | list]}}
+    data =
+      data
+      |> Enum.reduce_while([], fn element, list ->
+        case ObjectID.cast(element) do
+          {:ok, id} ->
+            {:cont, [id | list]}
 
-        _ ->
-          {:cont, {:ok, list}}
-      end
-    end)
+          _ ->
+            {:cont, list}
+        end
+      end)
+      |> Enum.sort()
+      |> Enum.uniq()
+
+    {:ok, data}
   end
 
   def cast(data) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
index 7309f6af2..009cd51b0 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
@@ -9,37 +9,39 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
   alias Pleroma.Web.ActivityPub.Transmogrifier
   alias Pleroma.Web.ActivityPub.Utils
 
+  def cast_recipients(message, field, field_fallback \\ []) do
+    {:ok, data} = ObjectValidators.Recipients.cast(message[field] || field_fallback)
+
+    Map.put(message, field, data)
+  end
+
   def fix_object_defaults(data) do
     %{data: %{"id" => context}, id: context_id} =
       Utils.create_context(data["context"] || data["conversation"])
 
     %User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["attributedTo"])
-    {:ok, to} = ObjectValidators.Recipients.cast(data["to"] || [])
-    {:ok, cc} = ObjectValidators.Recipients.cast(data["cc"] || [])
 
     data
     |> Map.put("context", context)
     |> Map.put("context_id", context_id)
-    |> Map.put("to", to)
-    |> Map.put("cc", cc)
+    |> cast_recipients("to")
+    |> cast_recipients("cc")
+    |> cast_recipients("bto")
+    |> cast_recipients("bcc")
     |> Transmogrifier.fix_explicit_addressing(follower_collection)
     |> Transmogrifier.fix_implicit_addressing(follower_collection)
   end
 
-  defp fix_activity_recipients(activity, field, object) do
-    {:ok, data} = ObjectValidators.Recipients.cast(activity[field] || object[field])
-
-    Map.put(activity, field, data)
-  end
-
-  def fix_activity_defaults(activity, meta) do
-    object = meta[:object_data] || %{}
+  def fix_activity_addressing(activity, _meta) do
+    %User{follower_address: follower_collection} = User.get_cached_by_ap_id(activity["actor"])
 
     activity
-    |> fix_activity_recipients("to", object)
-    |> fix_activity_recipients("cc", object)
-    |> fix_activity_recipients("bto", object)
-    |> fix_activity_recipients("bcc", object)
+    |> cast_recipients("to")
+    |> cast_recipients("cc")
+    |> cast_recipients("bto")
+    |> cast_recipients("bcc")
+    |> Transmogrifier.fix_explicit_addressing(follower_collection)
+    |> Transmogrifier.fix_implicit_addressing(follower_collection)
   end
 
   def fix_actor(data) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
index 99e8dc6c7..51d43e8d0 100644
--- a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
@@ -10,8 +10,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
 
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
   alias Pleroma.Object
+  alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+  alias Pleroma.Web.ActivityPub.Transmogrifier
 
   import Ecto.Changeset
 
@@ -23,6 +25,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
     field(:type, :string)
     field(:to, ObjectValidators.Recipients, default: [])
     field(:cc, ObjectValidators.Recipients, default: [])
+    field(:bto, ObjectValidators.Recipients, default: [])
+    field(:bcc, ObjectValidators.Recipients, default: [])
     field(:object, ObjectValidators.ObjectID)
     field(:expires_at, ObjectValidators.DateTime)
 
@@ -54,29 +58,38 @@ def changeset(struct, data) do
     |> cast(data, __schema__(:fields))
   end
 
-  defp fix_context(data, meta) do
-    if object = meta[:object_data] do
-      Map.put_new(data, "context", object["context"])
-    else
-      data
-    end
+  # CommonFixes.fix_activity_addressing adapted for Create specific behavior
+  defp fix_addressing(data, object) do
+    %User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["actor"])
+
+    data
+    |> CommonFixes.cast_recipients("to", object["to"])
+    |> CommonFixes.cast_recipients("cc", object["cc"])
+    |> CommonFixes.cast_recipients("bto", object["bto"])
+    |> CommonFixes.cast_recipients("bcc", object["bcc"])
+    |> Transmogrifier.fix_explicit_addressing(follower_collection)
+    |> Transmogrifier.fix_implicit_addressing(follower_collection)
   end
 
-  defp fix(data, meta) do
+  def fix(data, meta) do
+    object = meta[:object_data]
+
     data
-    |> fix_context(meta)
     |> CommonFixes.fix_actor()
-    |> CommonFixes.fix_activity_defaults(meta)
+    |> Map.put_new("context", object["context"])
+    |> fix_addressing(object)
   end
 
   defp validate_data(cng, meta) do
+    object = meta[:object_data]
+
     cng
-    |> validate_required([:actor, :type, :object])
+    |> validate_required([:actor, :type, :object, :to, :cc])
     |> validate_inclusion(:type, ["Create"])
     |> CommonValidations.validate_actor_presence()
-    |> CommonValidations.validate_any_presence([:to, :cc])
-    |> validate_actors_match(meta)
-    |> validate_context_match(meta)
+    |> validate_actors_match(object)
+    |> validate_context_match(object)
+    |> validate_addressing_match(object)
     |> validate_object_nonexistence()
     |> validate_object_containment()
   end
@@ -108,8 +121,8 @@ def validate_object_nonexistence(cng) do
     end)
   end
 
-  def validate_actors_match(cng, meta) do
-    attributed_to = meta[:object_data]["attributedTo"] || meta[:object_data]["actor"]
+  def validate_actors_match(cng, object) do
+    attributed_to = object["attributedTo"] || object["actor"]
 
     cng
     |> validate_change(:actor, fn :actor, actor ->
@@ -121,7 +134,7 @@ def validate_actors_match(cng, meta) do
     end)
   end
 
-  def validate_context_match(cng, %{object_data: %{"context" => object_context}}) do
+  def validate_context_match(cng, %{"context" => object_context}) do
     cng
     |> validate_change(:context, fn :context, context ->
       if context == object_context do
@@ -132,5 +145,18 @@ def validate_context_match(cng, %{object_data: %{"context" => object_context}})
     end)
   end
 
-  def validate_context_match(cng, _), do: cng
+  def validate_addressing_match(cng, object) do
+    [:to, :cc, :bcc, :bto]
+    |> Enum.reduce(cng, fn field, cng ->
+      object_data = object[to_string(field)]
+
+      validate_change(cng, field, fn field, data ->
+        if data == object_data do
+          []
+        else
+          [{field, "field doesn't match with object (#{inspect(object_data)})"}]
+        end
+      end)
+    end)
+  end
 end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
index 3eeae4004..b79f2c94c 100644
--- a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
@@ -171,8 +171,8 @@ test "it works for incoming notices" do
       assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
 
       assert data["cc"] == [
-               "http://mastodon.example.org/users/admin/followers",
-               "http://localtesting.pleroma.lol/users/lain"
+               "http://localtesting.pleroma.lol/users/lain",
+               "http://mastodon.example.org/users/admin/followers"
              ]
 
       assert data["actor"] == "http://mastodon.example.org/users/admin"
@@ -185,8 +185,8 @@ test "it works for incoming notices" do
       assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
 
       assert object_data["cc"] == [
-               "http://mastodon.example.org/users/admin/followers",
-               "http://localtesting.pleroma.lol/users/lain"
+               "http://localtesting.pleroma.lol/users/lain",
+               "http://mastodon.example.org/users/admin/followers"
              ]
 
       assert object_data["actor"] == "http://mastodon.example.org/users/admin"
@@ -350,8 +350,8 @@ test "it correctly processes messages with non-array to field" do
       assert {:ok, activity} = Transmogrifier.handle_incoming(data)
 
       assert [
-               "http://mastodon.example.org/users/admin/followers",
-               "http://localtesting.pleroma.lol/users/lain"
+               "http://localtesting.pleroma.lol/users/lain",
+               "http://mastodon.example.org/users/admin/followers"
              ] == activity.data["cc"]
 
       assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]

From 96212b2e32e2542964c665f091158fb1ff1d987d Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Tue, 15 Sep 2020 17:22:08 +0200
Subject: [PATCH 147/339] Fix addressing

---
 lib/pleroma/object/fetcher.ex                 |  7 ++++--
 .../object_validators/common_fixes.ex         | 25 +++++++++++--------
 .../create_generic_validator.ex               |  9 +++----
 3 files changed, 23 insertions(+), 18 deletions(-)

diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index bcccf1c4c..82d2c8bcb 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -4,6 +4,7 @@
 
 defmodule Pleroma.Object.Fetcher do
   alias Pleroma.HTTP
+  alias Pleroma.Maps
   alias Pleroma.Object
   alias Pleroma.Object.Containment
   alias Pleroma.Repo
@@ -124,12 +125,14 @@ def fetch_object_from_id(id, options \\ []) do
   defp prepare_activity_params(data) do
     %{
       "type" => "Create",
-      "to" => data["to"] || [],
-      "cc" => data["cc"] || [],
       # Should we seriously keep this attributedTo thing?
       "actor" => data["actor"] || data["attributedTo"],
       "object" => data
     }
+    |> Maps.put_if_present("to", data["to"])
+    |> Maps.put_if_present("cc", data["cc"])
+    |> Maps.put_if_present("bto", data["bto"])
+    |> Maps.put_if_present("bcc", data["bcc"])
   end
 
   def fetch_object_from_id!(id, options \\ []) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
index 009cd51b0..c958fcc5d 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
@@ -9,9 +9,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
   alias Pleroma.Web.ActivityPub.Transmogrifier
   alias Pleroma.Web.ActivityPub.Utils
 
-  def cast_recipients(message, field, field_fallback \\ []) do
+  def cast_and_filter_recipients(message, field, follower_collection, field_fallback \\ []) do
     {:ok, data} = ObjectValidators.Recipients.cast(message[field] || field_fallback)
 
+    data =
+      Enum.reject(data, fn x ->
+        String.ends_with?(x, "/followers") and x != follower_collection
+      end)
+
     Map.put(message, field, data)
   end
 
@@ -24,11 +29,10 @@ def fix_object_defaults(data) do
     data
     |> Map.put("context", context)
     |> Map.put("context_id", context_id)
-    |> cast_recipients("to")
-    |> cast_recipients("cc")
-    |> cast_recipients("bto")
-    |> cast_recipients("bcc")
-    |> Transmogrifier.fix_explicit_addressing(follower_collection)
+    |> cast_and_filter_recipients("to", follower_collection)
+    |> cast_and_filter_recipients("cc", follower_collection)
+    |> cast_and_filter_recipients("bto", follower_collection)
+    |> cast_and_filter_recipients("bcc", follower_collection)
     |> Transmogrifier.fix_implicit_addressing(follower_collection)
   end
 
@@ -36,11 +40,10 @@ def fix_activity_addressing(activity, _meta) do
     %User{follower_address: follower_collection} = User.get_cached_by_ap_id(activity["actor"])
 
     activity
-    |> cast_recipients("to")
-    |> cast_recipients("cc")
-    |> cast_recipients("bto")
-    |> cast_recipients("bcc")
-    |> Transmogrifier.fix_explicit_addressing(follower_collection)
+    |> cast_and_filter_recipients("to", follower_collection)
+    |> cast_and_filter_recipients("cc", follower_collection)
+    |> cast_and_filter_recipients("bto", follower_collection)
+    |> cast_and_filter_recipients("bcc", follower_collection)
     |> Transmogrifier.fix_implicit_addressing(follower_collection)
   end
 
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
index 51d43e8d0..d2de53049 100644
--- a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
@@ -63,11 +63,10 @@ defp fix_addressing(data, object) do
     %User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["actor"])
 
     data
-    |> CommonFixes.cast_recipients("to", object["to"])
-    |> CommonFixes.cast_recipients("cc", object["cc"])
-    |> CommonFixes.cast_recipients("bto", object["bto"])
-    |> CommonFixes.cast_recipients("bcc", object["bcc"])
-    |> Transmogrifier.fix_explicit_addressing(follower_collection)
+    |> CommonFixes.cast_and_filter_recipients("to", follower_collection, object["to"])
+    |> CommonFixes.cast_and_filter_recipients("cc", follower_collection, object["cc"])
+    |> CommonFixes.cast_and_filter_recipients("bto", follower_collection, object["bto"])
+    |> CommonFixes.cast_and_filter_recipients("bcc", follower_collection, object["bcc"])
     |> Transmogrifier.fix_implicit_addressing(follower_collection)
   end
 

From d1205406d9237c72d10df937dd8d2d4da2786cc5 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Tue, 15 Sep 2020 18:18:57 +0200
Subject: [PATCH 148/339] ActivityPubControllerTest: Apply same addr changes to
 object

---
 lib/pleroma/web/activity_pub/utils.ex         |  5 +++-
 .../activity_pub_controller_test.exs          | 30 ++++++++++++++-----
 2 files changed, 26 insertions(+), 9 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index a4dc469dc..e81623d83 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -97,7 +97,10 @@ def maybe_splice_recipient(ap_id, params) do
 
     if need_splice? do
       cc_list = extract_list(params["cc"])
-      Map.put(params, "cc", [ap_id | cc_list])
+
+      params
+      |> Map.put("cc", [ap_id | cc_list])
+      |> Kernel.put_in(["object", "cc"], [ap_id | cc_list])
     else
       params
     end
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index 2de52323e..f6ea9e2ca 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -649,7 +649,11 @@ test "without valid signature, " <>
 
     test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
       user = insert(:user)
-      data = Map.put(data, "bcc", [user.ap_id])
+
+      data =
+        data
+        |> Map.put("bcc", [user.ap_id])
+        |> Kernel.put_in(["object", "bcc"], [user.ap_id])
 
       conn =
         conn
@@ -666,8 +670,11 @@ test "it accepts messages with to as string instead of array", %{conn: conn, dat
       user = insert(:user)
 
       data =
-        Map.put(data, "to", user.ap_id)
-        |> Map.delete("cc")
+        data
+        |> Map.put("to", user.ap_id)
+        |> Map.put("cc", [])
+        |> Kernel.put_in(["object", "to"], user.ap_id)
+        |> Kernel.put_in(["object", "cc"], [])
 
       conn =
         conn
@@ -684,8 +691,11 @@ test "it accepts messages with cc as string instead of array", %{conn: conn, dat
       user = insert(:user)
 
       data =
-        Map.put(data, "cc", user.ap_id)
-        |> Map.delete("to")
+        data
+        |> Map.put("to", [])
+        |> Map.put("cc", user.ap_id)
+        |> Kernel.put_in(["object", "to"], [])
+        |> Kernel.put_in(["object", "cc"], user.ap_id)
 
       conn =
         conn
@@ -703,9 +713,13 @@ test "it accepts messages with bcc as string instead of array", %{conn: conn, da
       user = insert(:user)
 
       data =
-        Map.put(data, "bcc", user.ap_id)
-        |> Map.delete("to")
-        |> Map.delete("cc")
+        data
+        |> Map.put("to", [])
+        |> Map.put("cc", [])
+        |> Map.put("bcc", user.ap_id)
+        |> Kernel.put_in(["object", "to"], [])
+        |> Kernel.put_in(["object", "cc"], [])
+        |> Kernel.put_in(["object", "bcc"], user.ap_id)
 
       conn =
         conn

From b0c778fde77f5ec2320b0bd0327e8a13b0f39a63 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Tue, 15 Sep 2020 18:19:38 +0200
Subject: [PATCH 149/339] NoteHandlingTest: remove
 fix_explicit_addressing-related test

---
 .../transmogrifier/note_handling_test.exs     | 42 +++----------------
 1 file changed, 6 insertions(+), 36 deletions(-)

diff --git a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
index b79f2c94c..1846b2291 100644
--- a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
   alias Pleroma.Object
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.Transmogrifier
+  alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.CommonAPI
 
   import Mock
@@ -42,36 +43,6 @@ test "it works for incoming notices with tag not being an array (kroeg)" do
       assert Object.hashtags(object) == ["test"]
     end
 
-    test "it cleans up incoming notices which are not really DMs" do
-      user = insert(:user)
-      other_user = insert(:user)
-
-      to = [user.ap_id, other_user.ap_id]
-
-      data =
-        File.read!("test/fixtures/mastodon-post-activity.json")
-        |> Jason.decode!()
-        |> Map.put("to", to)
-        |> Map.put("cc", [])
-
-      object =
-        data["object"]
-        |> Map.put("to", to)
-        |> Map.put("cc", [])
-
-      data = Map.put(data, "object", object)
-
-      {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
-
-      assert data["to"] == []
-      assert data["cc"] == to
-
-      object_data = Object.normalize(activity, fetch: false).data
-
-      assert object_data["to"] == []
-      assert object_data["cc"] == to
-    end
-
     test "it ignores an incoming notice if we already have it" do
       activity = insert(:note_activity)
 
@@ -321,9 +292,11 @@ test "it strips internal likes" do
       object = Map.put(data["object"], "likes", likes)
       data = Map.put(data, "object", object)
 
-      {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data)
+      {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(data)
 
-      refute Map.has_key?(object.data, "likes")
+      object = Object.normalize(activity)
+
+      assert object.data["likes"] == []
     end
 
     test "it strips internal reactions" do
@@ -435,10 +408,7 @@ test "does NOT schedule background fetching of `replies` beyond max thread depth
     setup do
       replies = %{
         "type" => "Collection",
-        "items" => [
-          Pleroma.Web.ActivityPub.Utils.generate_object_id(),
-          Pleroma.Web.ActivityPub.Utils.generate_object_id()
-        ]
+        "items" => [Utils.generate_object_id(), Utils.generate_object_id()]
       }
 
       activity =

From 461123110b7cf47f4d2c01d1dd6992a2b63337fe Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Thu, 17 Sep 2020 16:17:16 +0200
Subject: [PATCH 150/339] Object.Fetcher: Fix getting transmogrifier reject
 reason

---
 lib/pleroma/object/fetcher.ex | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index 82d2c8bcb..4ca67f0fd 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -102,6 +102,9 @@ def fetch_object_from_id(id, options \\ []) do
       {:transmogrifier, {:error, {:reject, e}}} ->
         {:reject, e}
 
+      {:transmogrifier, {:reject, e}} ->
+        {:reject, e}
+
       {:transmogrifier, _} = e ->
         {:error, e}
 

From 6c9f6e62c8453f023c6ec9106d1a7c3e66ab95b7 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Mon, 28 Sep 2020 19:34:27 +0200
Subject: [PATCH 151/339] transmogrifier: Fixing votes from Note to Answer

---
 .../object_validators/answer_validator.ex     |  7 ++++++
 .../web/activity_pub/transmogrifier.ex        | 22 ++++++++++++-------
 2 files changed, 21 insertions(+), 8 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex b/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex
index c9bd9e42d..3451e1ff8 100644
--- a/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do
   use Ecto.Schema
 
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
+  alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
 
   import Ecto.Changeset
@@ -23,6 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do
     field(:name, :string)
     field(:inReplyTo, ObjectValidators.ObjectID)
     field(:attributedTo, ObjectValidators.ObjectID)
+    field(:context, :string)
 
     # TODO: Remove actor on objects
     field(:actor, ObjectValidators.ObjectID)
@@ -46,6 +48,11 @@ def cast_data(data) do
   end
 
   def changeset(struct, data) do
+    data =
+      data
+      |> CommonFixes.fix_actor()
+      |> CommonFixes.fix_object_defaults()
+
     struct
     |> cast(data, __schema__(:fields))
   end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 28bc25363..454bbce9d 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -43,7 +43,6 @@ def fix_object(object, options \\ []) do
     |> fix_content_map()
     |> fix_addressing()
     |> fix_summary()
-    |> fix_type(options)
   end
 
   def fix_summary(%{"summary" => nil} = object) do
@@ -321,19 +320,18 @@ def fix_content_map(%{"contentMap" => content_map} = object) do
 
   def fix_content_map(object), do: object
 
-  def fix_type(object, options \\ [])
+  defp fix_type(%{"type" => "Note", "inReplyTo" => reply_id, "name" => _} = object, options)
+       when is_binary(reply_id) do
+    options = Keyword.put(options, :fetch, true)
 
-  def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
-      when is_binary(reply_id) do
-    with true <- Federator.allowed_thread_distance?(options[:depth]),
-         {:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do
+    with %Object{data: %{"type" => "Question"}} <- Object.normalize(reply_id, options) do
       Map.put(object, "type", "Answer")
     else
       _ -> object
     end
   end
 
-  def fix_type(object, _), do: object
+  defp fix_type(object, _options), do: object
 
   # Reduce the object list to find the reported user.
   defp get_reported(objects) do
@@ -501,7 +499,15 @@ def handle_incoming(
         options
       )
       when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note} do
-    data = Map.put(data, "object", strip_internal_fields(data["object"]))
+    fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
+
+    object =
+      data["object"]
+      |> strip_internal_fields()
+      |> fix_type(fetch_options)
+      |> fix_in_reply_to(fetch_options)
+
+    data = Map.put(data, "object", object)
     options = Keyword.put(options, :local, false)
 
     with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),

From 0b88accae632e371becacb16be4e8798aa80c705 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Wed, 21 Oct 2020 01:20:06 +0200
Subject: [PATCH 152/339] fetcher_test: Fix missing mock function

---
 test/pleroma/object/fetcher_test.exs | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs
index a7ac90348..8d9c6c3cb 100644
--- a/test/pleroma/object/fetcher_test.exs
+++ b/test/pleroma/object/fetcher_test.exs
@@ -66,6 +66,14 @@ defmodule Pleroma.Object.FetcherTest do
           %Tesla.Env{
             status: 500
           }
+
+        %{
+          method: :get,
+          url: "https://stereophonic.space/objects/02997b83-3ea7-4b63-94af-ef3aa2d4ed17"
+        } ->
+          %Tesla.Env{
+            status: 500
+          }
       end)
 
       :ok

From 53193b84b1d07c9fd3c6b80c04e3eada4fb4cd59 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Fri, 27 Nov 2020 00:25:24 +0100
Subject: [PATCH 153/339] =?UTF-8?q?utils:=20Fix=20maybe=5Fsplice=5Frecipie?=
 =?UTF-8?q?nt=20when=20"object"=20isn=E2=80=99t=20a=20map?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/pleroma/maps.ex                                         | 6 ++++++
 lib/pleroma/web/activity_pub/utils.ex                       | 6 +++---
 .../web/activity_pub/activity_pub_controller_test.exs       | 1 -
 3 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/lib/pleroma/maps.ex b/lib/pleroma/maps.ex
index 0d2e94248..b08b83305 100644
--- a/lib/pleroma/maps.ex
+++ b/lib/pleroma/maps.ex
@@ -12,4 +12,10 @@ def put_if_present(map, key, value, value_function \\ &{:ok, &1}) when is_map(ma
       _ -> map
     end
   end
+
+  def safe_put_in(data, keys, value) when is_map(data) and is_list(keys) do
+    Kernel.put_in(data, keys, value)
+  rescue
+    _ -> data
+  end
 end
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index e81623d83..0d1a6d0f1 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -96,11 +96,11 @@ def maybe_splice_recipient(ap_id, params) do
         !label_in_collection?(ap_id, params["cc"])
 
     if need_splice? do
-      cc_list = extract_list(params["cc"])
+      cc = [ap_id | extract_list(params["cc"])]
 
       params
-      |> Map.put("cc", [ap_id | cc_list])
-      |> Kernel.put_in(["object", "cc"], [ap_id | cc_list])
+      |> Map.put("cc", cc)
+      |> Maps.safe_put_in(["object", "cc"], cc)
     else
       params
     end
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index f6ea9e2ca..f3ce703e2 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -1003,7 +1003,6 @@ test "forwarded report from mastodon", %{conn: conn} do
         "actor" => remote_actor,
         "content" => "test report",
         "id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8",
-        "nickname" => reported_user.nickname,
         "object" => [
           reported_user.ap_id,
           note.data["object"]

From 6d6bef64bf3b37457b71cf7025e84aa9017a3b86 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Thu, 25 Mar 2021 10:17:26 +0100
Subject: [PATCH 154/339] fetcher_test: Remove assert on fake Create having an
 ap_id

---
 test/pleroma/object/fetcher_test.exs | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs
index 8d9c6c3cb..bd0a6e497 100644
--- a/test/pleroma/object/fetcher_test.exs
+++ b/test/pleroma/object/fetcher_test.exs
@@ -132,8 +132,7 @@ test "it fetches an object" do
       {:ok, object} =
         Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
 
-      assert activity = Activity.get_create_by_object_ap_id(object.data["id"])
-      assert activity.data["id"]
+      assert _activity = Activity.get_create_by_object_ap_id(object.data["id"])
 
       {:ok, object_again} =
         Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")

From 5ef4659b373ae1106090952ff3e963b419fa1d72 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Mon, 5 Apr 2021 18:57:14 +0200
Subject: [PATCH 155/339] test/pleroma/web/common_api_test.exs: Strip : around
 emoji key-name

---
 test/pleroma/web/common_api_test.exs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs
index 6619f8fc8..86c12f0b2 100644
--- a/test/pleroma/web/common_api_test.exs
+++ b/test/pleroma/web/common_api_test.exs
@@ -539,8 +539,8 @@ test "it copies emoji from the subject of the parent post" do
           spoiler_text: ":joker_smile:"
         })
 
-      assert Object.normalize(reply_activity).data["emoji"][":joker_smile:"]
-      refute Object.normalize(reply_activity).data["emoji"][":joker_disapprove:"]
+      assert Object.normalize(reply_activity).data["emoji"]["joker_smile"]
+      refute Object.normalize(reply_activity).data["emoji"]["joker_disapprove"]
     end
 
     test "deactivated users can't post" do

From 681a42c359b4fbae74285363c670dff18aac5918 Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov <alex.strizhakov@gmail.com>
Date: Thu, 8 Apr 2021 15:45:31 +0300
Subject: [PATCH 156/339] release runtime provider fix for paths

---
 lib/pleroma/config/release_runtime_provider.ex      | 13 +++++++++----
 mix.exs                                             | 13 +------------
 .../config/release_runtime_provider_test.exs        |  1 -
 3 files changed, 10 insertions(+), 17 deletions(-)

diff --git a/lib/pleroma/config/release_runtime_provider.ex b/lib/pleroma/config/release_runtime_provider.ex
index 46fa35559..e5e9d3dcd 100644
--- a/lib/pleroma/config/release_runtime_provider.ex
+++ b/lib/pleroma/config/release_runtime_provider.ex
@@ -11,10 +11,11 @@ def init(opts), do: opts
   def load(config, opts) do
     with_defaults = Config.Reader.merge(config, Pleroma.Config.Holder.release_defaults())
 
-    config_path = opts[:config_path]
+    config_path =
+      opts[:config_path] || System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs"
 
     with_runtime_config =
-      if config_path && File.exists?(config_path) do
+      if File.exists?(config_path) do
         runtime_config = Config.Reader.read!(config_path)
 
         with_defaults
@@ -32,10 +33,14 @@ def load(config, opts) do
         with_defaults
       end
 
-    exported_config_path = opts[:exported_config_path]
+    exported_config_path =
+      opts[:exported_config_path] ||
+        config_path
+        |> Path.dirname()
+        |> Path.join("#{Pleroma.Config.get(:env)}.exported_from_db.secret.exs")
 
     with_exported =
-      if exported_config_path && File.exists?(exported_config_path) do
+      if File.exists?(exported_config_path) do
         exported_config = Config.Reader.read!(exported_config_path)
         Config.Reader.merge(with_runtime_config, exported_config)
       else
diff --git a/mix.exs b/mix.exs
index 7328b533b..fe5d9d963 100644
--- a/mix.exs
+++ b/mix.exs
@@ -38,7 +38,7 @@ def project do
           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, release_config_paths()}]
+          config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, []}]
         ]
       ]
     ]
@@ -67,17 +67,6 @@ def copy_nginx_config(%{path: target_path} = release) do
     release
   end
 
-  defp release_config_paths do
-    config_path = System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs"
-
-    exported_config_path =
-      config_path
-      |> Path.dirname()
-      |> Path.join("#{Mix.env()}.exported_from_db.secret.exs")
-
-    [config_path: config_path, exported_config_path: exported_config_path]
-  end
-
   # Configuration for the OTP application.
   #
   # Type `mix help compile.app` for more information.
diff --git a/test/pleroma/config/release_runtime_provider_test.exs b/test/pleroma/config/release_runtime_provider_test.exs
index 1921698c5..6578d3268 100644
--- a/test/pleroma/config/release_runtime_provider_test.exs
+++ b/test/pleroma/config/release_runtime_provider_test.exs
@@ -8,7 +8,6 @@ test "loads release defaults config and warns about non-existent runtime config"
       ExUnit.CaptureIO.capture_io(fn ->
         merged = ReleaseRuntimeProvider.load([], [])
         assert merged == Pleroma.Config.Holder.release_defaults()
-        IO.inspect(merged)
       end) =~
         "!!! Config path is not declared! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file"
     end

From 0feafcc20cec168258f592b9d509c1e6ccc8efba Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 9 Apr 2021 10:30:27 -0500
Subject: [PATCH 157/339] Use URI.merge to prevent concatenating two canonical
 URLs when a custom instance thumbnail was uploaded via AdminFE

---
 lib/pleroma/web/mastodon_api/views/instance_view.ex | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
index 73205fb6d..dac68d8e6 100644
--- a/lib/pleroma/web/mastodon_api/views/instance_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -23,7 +23,8 @@ def render("show.json", _) do
         streaming_api: Pleroma.Web.Endpoint.websocket_url()
       },
       stats: Pleroma.Stats.get_stats(),
-      thumbnail: Pleroma.Web.base_url() <> Keyword.get(instance, :instance_thumbnail),
+      thumbnail:
+        URI.merge(Pleroma.Web.base_url(), Keyword.get(instance, :instance_thumbnail)) |> to_string,
       languages: ["en"],
       registrations: Keyword.get(instance, :registrations_open),
       approval_required: Keyword.get(instance, :account_approval_required),

From 9fbcdc15b11dedf27bc5c78d09048ba354906c16 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 13 Apr 2021 10:52:53 -0500
Subject: [PATCH 158/339] Validate custom instance thumbnail set via AdminAPI
 produces correct URL

---
 CHANGELOG.md                                  |  1 +
 .../controllers/config_controller_test.exs    | 42 +++++++++++++++++++
 2 files changed, 43 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6c45cad85..1553245e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Fixed
 
 - Try to save exported ConfigDB settings (migrate_from_db) in the system temp directory if default location is not writable.
+- Uploading custom instance thumbnail via AdminAPI/AdminFE generated invalid URL to the image
 
 ## [2.3.0] - 2020-03-01
 
diff --git a/test/pleroma/web/admin_api/controllers/config_controller_test.exs b/test/pleroma/web/admin_api/controllers/config_controller_test.exs
index 578a4c914..71151712e 100644
--- a/test/pleroma/web/admin_api/controllers/config_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/config_controller_test.exs
@@ -1410,6 +1410,48 @@ test "enables the welcome messages", %{conn: conn} do
                "need_reboot" => false
              }
     end
+
+    test "custom instance thumbnail", %{conn: conn} do
+      clear_config([:instance])
+
+      params = %{
+        "group" => ":pleroma",
+        "key" => ":instance",
+        "value" => [
+          %{
+            "tuple" => [
+              ":instance_thumbnail",
+              "https://example.com/media/new_thumbnail.jpg"
+            ]
+          }
+        ]
+      }
+
+      res =
+        assert conn
+               |> put_req_header("content-type", "application/json")
+               |> post("/api/pleroma/admin/config", %{"configs" => [params]})
+               |> json_response_and_validate_schema(200)
+
+      assert res == %{
+               "configs" => [
+                 %{
+                   "db" => [":instance_thumbnail"],
+                   "group" => ":pleroma",
+                   "key" => ":instance",
+                   "value" => params["value"]
+                 }
+               ],
+               "need_reboot" => false
+             }
+
+      assert res =
+               conn
+               |> get("/api/v1/instance")
+               |> json_response_and_validate_schema(200)
+
+      assert res = %{"thumbnail" => "https://example.com/media/new_thumbnail.jpg"}
+    end
   end
 
   describe "GET /api/pleroma/admin/config/descriptions" do

From cdd271b0655799e65bb9a13016dc82441ec34f87 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 13 Apr 2021 10:55:54 -0500
Subject: [PATCH 159/339] Fix assignment / assertion

---
 .../web/admin_api/controllers/config_controller_test.exs      | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/pleroma/web/admin_api/controllers/config_controller_test.exs b/test/pleroma/web/admin_api/controllers/config_controller_test.exs
index 71151712e..c4d07d61c 100644
--- a/test/pleroma/web/admin_api/controllers/config_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/config_controller_test.exs
@@ -1445,8 +1445,8 @@ test "custom instance thumbnail", %{conn: conn} do
                "need_reboot" => false
              }
 
-      assert res =
-               conn
+      _res =
+        assert conn
                |> get("/api/v1/instance")
                |> json_response_and_validate_schema(200)
 

From 905efc57e9f2a96519bf1ac84b56f88d1818cca3 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 13 Apr 2021 11:15:52 -0500
Subject: [PATCH 160/339] Initial test validating the AdminAPI issue

---
 .../controllers/config_controller_test.exs    | 35 +++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/test/pleroma/web/admin_api/controllers/config_controller_test.exs b/test/pleroma/web/admin_api/controllers/config_controller_test.exs
index c4d07d61c..d26fd3150 100644
--- a/test/pleroma/web/admin_api/controllers/config_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/config_controller_test.exs
@@ -1452,6 +1452,41 @@ test "custom instance thumbnail", %{conn: conn} do
 
       assert res = %{"thumbnail" => "https://example.com/media/new_thumbnail.jpg"}
     end
+
+    test "Concurrent Limiter", %{conn: conn} do
+      clear_config([ConcurrentLimiter])
+
+      params = %{
+        "group" => ":pleroma",
+        "key" => "ConcurrentLimiter",
+        "value" => [
+          %{
+            "tuple" => [
+              "Pleroma.Web.RichMedia.Helpers",
+              [
+                %{"tuple" => [":max_running", 6]},
+                %{"tuple" => [":max_waiting", 6]}
+              ]
+            ]
+          },
+          %{
+            "tuple" => [
+              "Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy",
+              [
+                %{"tuple" => [":max_running", 7]},
+                %{"tuple" => [":max_waiting", 7]}
+              ]
+            ]
+          }
+        ]
+      }
+
+      _res =
+        assert conn
+               |> put_req_header("content-type", "application/json")
+               |> post("/api/pleroma/admin/config", %{"configs" => [params]})
+               |> json_response_and_validate_schema(200)
+    end
   end
 
   describe "GET /api/pleroma/admin/config/descriptions" do

From ee53ad4d7705328a5a583680c6f551c4c3bf2302 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 13 Apr 2021 12:09:18 -0500
Subject: [PATCH 161/339] Add ConcurrentLimiter to module_name?/1 and apply
 string_to_elixir_types/1 to search_opts keys during update_or_create/1

---
 lib/pleroma/config_db.ex | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/lib/pleroma/config_db.ex b/lib/pleroma/config_db.ex
index b874e0e37..03905c06b 100644
--- a/lib/pleroma/config_db.ex
+++ b/lib/pleroma/config_db.ex
@@ -141,7 +141,9 @@ defp deep_merge(_key, value1, value2) do
   @spec update_or_create(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
   def update_or_create(params) do
     params = Map.put(params, :value, to_elixir_types(params[:value]))
-    search_opts = Map.take(params, [:group, :key])
+
+    search_opts =
+      Map.take(params, [:group, :key]) |> Map.update!(:key, &string_to_elixir_types(&1))
 
     with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts),
          {_, true, config} <- {:partial_update, can_be_partially_updated?(config), config},
@@ -387,6 +389,6 @@ defp find_valid_delimiter([delimiter | others], pattern, regex_delimiter) do
   @spec module_name?(String.t()) :: boolean()
   def module_name?(string) do
     Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth|Swoosh)\./, string) or
-      string in ["Oban", "Ueberauth", "ExSyslogger"]
+      string in ["Oban", "Ueberauth", "ExSyslogger", "ConcurrentLimiter"]
   end
 end

From 861f1928526930eeb78f79c4840c69cee5c2f215 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 13 Apr 2021 14:39:44 -0500
Subject: [PATCH 162/339] Document fixed ability to save ConcurrentLimiter
 settings in ConfigDB

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1553245e5..6e13b3875 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 - Try to save exported ConfigDB settings (migrate_from_db) in the system temp directory if default location is not writable.
 - Uploading custom instance thumbnail via AdminAPI/AdminFE generated invalid URL to the image
+- Applying ConcurrentLimiter settings via AdminAPI
 
 ## [2.3.0] - 2020-03-01
 

From c3b8c77967b0c42f93286f864236b7d6f1471c13 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 13 Apr 2021 14:25:15 -0500
Subject: [PATCH 163/339] Improve string_to_elixir_types/1 with guards

---
 lib/pleroma/config_db.ex | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/config_db.ex b/lib/pleroma/config_db.ex
index 03905c06b..eeeb026c1 100644
--- a/lib/pleroma/config_db.ex
+++ b/lib/pleroma/config_db.ex
@@ -327,7 +327,7 @@ def to_elixir_types(entity), do: entity
 
   @spec string_to_elixir_types(String.t()) ::
           atom() | Regex.t() | module() | String.t() | no_return()
-  def string_to_elixir_types("~r" <> _pattern = regex) do
+  def string_to_elixir_types("~r" <> _pattern = regex) when is_binary(regex) do
     pattern =
       ~r/^~r(?'delimiter'[\/|"'([{<]{1})(?'pattern'.+)[\/|"')\]}>]{1}(?'modifier'[uismxfU]*)/u
 
@@ -341,9 +341,9 @@ def string_to_elixir_types("~r" <> _pattern = regex) do
     end
   end
 
-  def string_to_elixir_types(":" <> atom), do: String.to_atom(atom)
+  def string_to_elixir_types(":" <> atom) when is_binary(atom), do: String.to_atom(atom)
 
-  def string_to_elixir_types(value) do
+  def string_to_elixir_types(value) when is_binary(value) do
     if module_name?(value) do
       String.to_existing_atom("Elixir." <> value)
     else
@@ -351,6 +351,8 @@ def string_to_elixir_types(value) do
     end
   end
 
+  def string_to_elixir_types(value) when is_atom(value), do: value
+
   defp parse_host("localhost"), do: :localhost
 
   defp parse_host(host) do

From f95b52255b2d7373a3e0bf4adff81f83c080b2ef Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 14 Apr 2021 09:39:57 -0500
Subject: [PATCH 164/339] Revert guards on string_to_elixir_types/1, remove
 unnecessary assignment in test

---
 lib/pleroma/config_db.ex                             | 12 ++++--------
 .../admin_api/controllers/config_controller_test.exs |  9 ++++-----
 2 files changed, 8 insertions(+), 13 deletions(-)

diff --git a/lib/pleroma/config_db.ex b/lib/pleroma/config_db.ex
index eeeb026c1..cb57673e3 100644
--- a/lib/pleroma/config_db.ex
+++ b/lib/pleroma/config_db.ex
@@ -141,9 +141,7 @@ defp deep_merge(_key, value1, value2) do
   @spec update_or_create(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
   def update_or_create(params) do
     params = Map.put(params, :value, to_elixir_types(params[:value]))
-
-    search_opts =
-      Map.take(params, [:group, :key]) |> Map.update!(:key, &string_to_elixir_types(&1))
+    search_opts = Map.take(params, [:group, :key])
 
     with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts),
          {_, true, config} <- {:partial_update, can_be_partially_updated?(config), config},
@@ -327,7 +325,7 @@ def to_elixir_types(entity), do: entity
 
   @spec string_to_elixir_types(String.t()) ::
           atom() | Regex.t() | module() | String.t() | no_return()
-  def string_to_elixir_types("~r" <> _pattern = regex) when is_binary(regex) do
+  def string_to_elixir_types("~r" <> _pattern = regex) do
     pattern =
       ~r/^~r(?'delimiter'[\/|"'([{<]{1})(?'pattern'.+)[\/|"')\]}>]{1}(?'modifier'[uismxfU]*)/u
 
@@ -341,9 +339,9 @@ def string_to_elixir_types("~r" <> _pattern = regex) when is_binary(regex) do
     end
   end
 
-  def string_to_elixir_types(":" <> atom) when is_binary(atom), do: String.to_atom(atom)
+  def string_to_elixir_types(":" <> atom), do: String.to_atom(atom)
 
-  def string_to_elixir_types(value) when is_binary(value) do
+  def string_to_elixir_types(value) do
     if module_name?(value) do
       String.to_existing_atom("Elixir." <> value)
     else
@@ -351,8 +349,6 @@ def string_to_elixir_types(value) when is_binary(value) do
     end
   end
 
-  def string_to_elixir_types(value) when is_atom(value), do: value
-
   defp parse_host("localhost"), do: :localhost
 
   defp parse_host(host) do
diff --git a/test/pleroma/web/admin_api/controllers/config_controller_test.exs b/test/pleroma/web/admin_api/controllers/config_controller_test.exs
index d26fd3150..c39c1b1e1 100644
--- a/test/pleroma/web/admin_api/controllers/config_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/config_controller_test.exs
@@ -1481,11 +1481,10 @@ test "Concurrent Limiter", %{conn: conn} do
         ]
       }
 
-      _res =
-        assert conn
-               |> put_req_header("content-type", "application/json")
-               |> post("/api/pleroma/admin/config", %{"configs" => [params]})
-               |> json_response_and_validate_schema(200)
+      assert conn
+             |> put_req_header("content-type", "application/json")
+             |> post("/api/pleroma/admin/config", %{"configs" => [params]})
+             |> json_response_and_validate_schema(200)
     end
   end
 

From d9fce0133ef3444ef7d09ae7e2760583540d1cd2 Mon Sep 17 00:00:00 2001
From: Sean King <seanking2919@protonmail.com>
Date: Wed, 14 Apr 2021 14:01:33 -0600
Subject: [PATCH 165/339] Fix Mastodon interface link

---
 docs/index.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/index.md b/docs/index.md
index 1a90d0a8d..80c5d2631 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -20,7 +20,7 @@ The default front-end used by Pleroma is Pleroma-FE. You can find more informati
 
 ### Mastodon interface
 If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too!
-Just add a "/web" after your instance url (e.g. <https://pleroma.soycaf.com/web>) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC!
+Just add a "/web" after your instance url (e.g. <https://pleroma.soykaf.com/web>) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC!
 The Mastodon interface is from the Glitch-soc fork. For more information on the Mastodon interface you can check the [Mastodon](https://docs.joinmastodon.org/) and [Glitch-soc](https://glitch-soc.github.io/docs/) documentation.
 
 Remember, what you see is only the frontend part of Mastodon, the backend is still Pleroma.

From c6dcd863e28531e0d21ee64a8387bd27c2c0ed31 Mon Sep 17 00:00:00 2001
From: rinpatch <rin@patch.cx>
Date: Fri, 16 Apr 2021 09:59:50 +0000
Subject: [PATCH 166/339] Apply rinpatch's suggestion(s) to 1 file(s)

---
 lib/pleroma/web/api_spec/operations/twitter_util_operation.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex b/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
index 62c9826f6..decb6572a 100644
--- a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
@@ -59,7 +59,7 @@ def frontend_configurations_operation do
 
   def change_password_operation do
     %Operation{
-      tags: ["Accounts"],
+      tags: ["Account credentials"],
       summary: "Change account password",
       security: [%{"oAuth" => ["write:accounts"]}],
       operationId: "UtilController.change_password",

From 2b4f958b2ad653ee8e294ade18aa4482e4d372e1 Mon Sep 17 00:00:00 2001
From: Sean King <seanking2919@protonmail.com>
Date: Sun, 18 Apr 2021 14:00:18 -0600
Subject: [PATCH 167/339] Add opting out of Google FLoC to HTTPSecurityPlug
 headers

---
 lib/pleroma/web/plugs/http_security_plug.ex | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex
index 0025b042a..d1e6cc9d3 100644
--- a/lib/pleroma/web/plugs/http_security_plug.ex
+++ b/lib/pleroma/web/plugs/http_security_plug.ex
@@ -48,7 +48,8 @@ def headers do
       {"x-content-type-options", "nosniff"},
       {"referrer-policy", referrer_policy},
       {"x-download-options", "noopen"},
-      {"content-security-policy", csp_string()}
+      {"content-security-policy", csp_string()},
+      {"permissions-policy", "interest-cohort=()"}
     ]
 
     headers =

From efed94a23e30260bcf1b297910906b11d6e4d895 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Mon, 19 Apr 2021 16:23:57 -0500
Subject: [PATCH 168/339] Fix error response which was breaking tests related
 to pinned posts

---
 lib/pleroma/web/common_api.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
index 3970c19a8..1b5f8491e 100644
--- a/lib/pleroma/web/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -415,7 +415,7 @@ def pin(id, %User{} = user) do
            ) do
       {:ok, activity}
     else
-      {:error, {:execute_side_effects, error}} -> error
+      {:error, {:side_effects, error}} -> error
       error -> error
     end
   end

From 2780cdd4e7acde0f4bf4719b7c82bc7e2d1bf3b5 Mon Sep 17 00:00:00 2001
From: Sean King <seanking2919@protonmail.com>
Date: Mon, 19 Apr 2021 16:06:19 -0600
Subject: [PATCH 169/339] Add CHANGELOG entry

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b0678023..bfa76a89a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Changed
 
 - The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
+- HTTPSecurityPlug now sends a response header to opt out of Google's FLoC (Federated Learning of Cohorts) targeted advertising.
 
 ### Added
 

From 7eded7218922b46c5cc085e715b6031ffff9b6ce Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 20 Apr 2021 12:31:14 -0500
Subject: [PATCH 170/339] Fix incorrect shell command

Can't be in /opt/pleroma/bin and then call ./bin/pleroma_ctl :)
---
 docs/installation/otp_en.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md
index 42e264e65..13f9636f3 100644
--- a/docs/installation/otp_en.md
+++ b/docs/installation/otp_en.md
@@ -290,7 +290,7 @@ nginx -t
 
 ## Create your first user and set as admin
 ```sh
-cd /opt/pleroma/bin
+cd /opt/pleroma
 su pleroma -s $SHELL -lc "./bin/pleroma_ctl user new joeuser joeuser@sld.tld --admin"
 ```
 This will create an account withe the username of 'joeuser' with the email address of joeuser@sld.tld, and set that user's account as an admin. This will result in a link that you can paste into the browser, which logs you in and enables you to set the password.

From 30b1d5093808974310a52917e6ab85d528683fae Mon Sep 17 00:00:00 2001
From: Haelwenn <contact+git.pleroma.social@hacktivis.me>
Date: Tue, 20 Apr 2021 21:06:32 +0000
Subject: [PATCH 171/339] Apply lanodan's suggestion(s) to 1 file(s)

---
 lib/pleroma/web/api_spec/operations/twitter_util_operation.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex b/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
index decb6572a..6ddc93a92 100644
--- a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
@@ -88,7 +88,7 @@ def change_password_operation do
 
   def change_email_operation do
     %Operation{
-      tags: ["Accounts"],
+      tags: ["Account credentials"],
       summary: "Change account email",
       security: [%{"oAuth" => ["write:accounts"]}],
       operationId: "UtilController.change_email",

From e104829c2f5b3eae9133ea1a6a81d138c3a8e314 Mon Sep 17 00:00:00 2001
From: Haelwenn <contact+git.pleroma.social@hacktivis.me>
Date: Tue, 20 Apr 2021 21:06:39 +0000
Subject: [PATCH 172/339] Apply lanodan's suggestion(s) to 1 file(s)

---
 lib/pleroma/web/api_spec/operations/twitter_util_operation.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex b/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
index 6ddc93a92..dbed1b518 100644
--- a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
@@ -143,7 +143,7 @@ def update_notificaton_settings_operation do
 
   def disable_account_operation do
     %Operation{
-      tags: ["Accounts"],
+      tags: ["Account credentials"],
       summary: "Disable Account",
       security: [%{"oAuth" => ["write:accounts"]}],
       operationId: "UtilController.disable_account",

From 42185d87504ea595138e8e3f5bf9ce6840edd2f1 Mon Sep 17 00:00:00 2001
From: Haelwenn <contact+git.pleroma.social@hacktivis.me>
Date: Tue, 20 Apr 2021 21:06:45 +0000
Subject: [PATCH 173/339] Apply lanodan's suggestion(s) to 1 file(s)

---
 lib/pleroma/web/api_spec/operations/twitter_util_operation.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex b/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
index dbed1b518..6e6d330de 100644
--- a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
@@ -163,7 +163,7 @@ def disable_account_operation do
 
   def delete_account_operation do
     %Operation{
-      tags: ["Accounts"],
+      tags: ["Account credentials"],
       summary: "Delete Account",
       security: [%{"oAuth" => ["write:accounts"]}],
       operationId: "UtilController.delete_account",

From f9bedf5597dd00ce4f429a4077e7bb4473c97410 Mon Sep 17 00:00:00 2001
From: Haelwenn <contact+git.pleroma.social@hacktivis.me>
Date: Tue, 20 Apr 2021 21:08:31 +0000
Subject: [PATCH 174/339] Apply lanodan's suggestion(s) to 1 file(s)

---
 lib/pleroma/web/api_spec/operations/twitter_util_operation.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex b/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
index 6e6d330de..0cafbc719 100644
--- a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
@@ -195,7 +195,7 @@ def captcha_operation do
   def healthcheck_operation do
     %Operation{
       tags: ["Accounts"],
-      summary: "Disable Account",
+      summary: "Quick status check on the instance",
       security: [%{"oAuth" => ["write:accounts"]}],
       operationId: "UtilController.healthcheck",
       parameters: [],

From 0effcd2cfed36baec1d960b64c901da7e56710a8 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Mon, 19 Apr 2021 15:43:17 -0500
Subject: [PATCH 175/339] Set Repo.transaction/2 timeout to infinity. Fixes
 pleroma.user delete_activities mix task.

---
 lib/pleroma/web/activity_pub/pipeline.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex
index 377eccb92..400823094 100644
--- a/lib/pleroma/web/activity_pub/pipeline.ex
+++ b/lib/pleroma/web/activity_pub/pipeline.ex
@@ -24,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
   @spec common_pipeline(map(), keyword()) ::
           {:ok, Activity.t() | Object.t(), keyword()} | {:error, any()}
   def common_pipeline(object, meta) do
-    case Repo.transaction(fn -> do_common_pipeline(object, meta) end) do
+    case Repo.transaction(fn -> do_common_pipeline(object, meta) end, timeout: :infinity) do
       {:ok, {:ok, activity, meta}} ->
         @side_effects.handle_after_transaction(meta)
         {:ok, activity, meta}

From 9bc69196d5dfbd3fb37c0e62da19ce08fb9bf28d Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 20 Apr 2021 11:10:39 -0500
Subject: [PATCH 176/339] Add utility function to return infinite timeout for
 SQL transactions if we detect it was called from a Mix Task

---
 lib/pleroma/utils.ex | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/lib/pleroma/utils.ex b/lib/pleroma/utils.ex
index bc0c95332..5e2fa8bf7 100644
--- a/lib/pleroma/utils.ex
+++ b/lib/pleroma/utils.ex
@@ -63,4 +63,13 @@ def posix_error_message(code) when code in @posix_error_codes do
   end
 
   def posix_error_message(_), do: ""
+
+  def query_timeout do
+    {parent, _, _, _} = Process.info(self(), :current_stacktrace) |> elem(1) |> Enum.fetch!(2)
+
+    cond do
+      parent |> to_string |> String.starts_with?("Elixir.Mix.Task") -> [timeout: :infinity]
+      true -> [timeout: 15_000]
+    end
+  end
 end

From 9f711ddcf84bdb5a5680e1b55afa83768014906d Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 20 Apr 2021 11:16:24 -0500
Subject: [PATCH 177/339] Try to set query timeout intelligently

---
 lib/pleroma/web/activity_pub/pipeline.ex | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex
index 400823094..a0f2e0312 100644
--- a/lib/pleroma/web/activity_pub/pipeline.ex
+++ b/lib/pleroma/web/activity_pub/pipeline.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
   alias Pleroma.Config
   alias Pleroma.Object
   alias Pleroma.Repo
+  alias Pleroma.Utils
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.MRF
   alias Pleroma.Web.ActivityPub.ObjectValidator
@@ -24,7 +25,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
   @spec common_pipeline(map(), keyword()) ::
           {:ok, Activity.t() | Object.t(), keyword()} | {:error, any()}
   def common_pipeline(object, meta) do
-    case Repo.transaction(fn -> do_common_pipeline(object, meta) end, timeout: :infinity) do
+    case Repo.transaction(fn -> do_common_pipeline(object, meta) end, Utils.query_timeout()) do
       {:ok, {:ok, activity, meta}} ->
         @side_effects.handle_after_transaction(meta)
         {:ok, activity, meta}

From 99fd9c5e38ad08973f435f1a67d6af60d004c578 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 20 Apr 2021 12:00:02 -0500
Subject: [PATCH 178/339] OTP releases executing commands via pleroma_ctl show
 the parent of the process is :erl_eval

---
 lib/pleroma/utils.ex | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lib/pleroma/utils.ex b/lib/pleroma/utils.ex
index 5e2fa8bf7..55aecc509 100644
--- a/lib/pleroma/utils.ex
+++ b/lib/pleroma/utils.ex
@@ -69,6 +69,7 @@ def query_timeout do
 
     cond do
       parent |> to_string |> String.starts_with?("Elixir.Mix.Task") -> [timeout: :infinity]
+      parent == :erl_eval -> [timeout: :infinity]
       true -> [timeout: 15_000]
     end
   end

From 959dc6e6fc95b33700fb7e08689afb701b2877f2 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Thu, 22 Apr 2021 10:11:08 -0500
Subject: [PATCH 179/339] Cleanup and ensure we obey custom Repo timeout

---
 lib/pleroma/utils.ex | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/utils.ex b/lib/pleroma/utils.ex
index 55aecc509..a446d3ae6 100644
--- a/lib/pleroma/utils.ex
+++ b/lib/pleroma/utils.ex
@@ -11,6 +11,8 @@ defmodule Pleroma.Utils do
     eperm epipe erange erofs espipe esrch estale etxtbsy exdev
   )a
 
+  @repo_timeout Pleroma.Config.get([Pleroma.Repo, :timeout], 15_000)
+
   def compile_dir(dir) when is_binary(dir) do
     dir
     |> File.ls!()
@@ -64,13 +66,20 @@ def posix_error_message(code) when code in @posix_error_codes do
 
   def posix_error_message(_), do: ""
 
+  @doc """
+  Returns [timeout: integer] suitable for passing as an option to Repo functions.
+
+  This function detects if the execution was triggered from IEx shell, Mix task, or
+  ./bin/pleroma_ctl and sets the timeout to :infinity, else returns the default timeout value.
+  """
+  @spec query_timeout() :: [timeout: integer]
   def query_timeout do
     {parent, _, _, _} = Process.info(self(), :current_stacktrace) |> elem(1) |> Enum.fetch!(2)
 
     cond do
       parent |> to_string |> String.starts_with?("Elixir.Mix.Task") -> [timeout: :infinity]
       parent == :erl_eval -> [timeout: :infinity]
-      true -> [timeout: 15_000]
+      true -> [timeout: @repo_timeout]
     end
   end
 end

From d7a71a275abea6286ee116d092ddc9440a9419a5 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Thu, 22 Apr 2021 10:15:05 -0500
Subject: [PATCH 180/339] Fixed pleroma.user delete_activities mix task.

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index bfa76a89a..a1173414d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Uploading custom instance thumbnail via AdminAPI/AdminFE generated invalid URL to the image
 - Applying ConcurrentLimiter settings via AdminAPI
 - User login failures if their `notification_settings` were in a NULL state.
+- Mix task `pleroma.user delete_activities` query transaction timeout is now :infinity
 
 ## [2.3.0] - 2020-03-01
 

From b9a99ac0d4b245ff3df6a9aa1b4db46ee75e9d22 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 27 Apr 2021 11:54:28 -0500
Subject: [PATCH 181/339] Cache gitlab-ci based on mix.lock

---
 .gitlab-ci.yml | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2bc571971..2651ff9e6 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,7 +8,9 @@ variables: &global_variables
   MIX_ENV: test
 
 cache: &global_cache_policy
-  key: ${CI_COMMIT_REF_SLUG}
+  key:
+    files:
+      - mix.lock
   paths:
     - deps
     - _build
@@ -171,8 +173,8 @@ spec-deploy:
     - apk add curl
   script:
     - curl -X POST -F"token=$API_DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" -F"variables[JOB_REF]=$CI_JOB_ID" https://git.pleroma.social/api/v4/projects/1130/trigger/pipeline
-  
- 
+
+
 stop_review_app:
   image: alpine:3.9
   stage: deploy
@@ -231,7 +233,7 @@ amd64-musl:
   stage: release
   artifacts: *release-artifacts
   only: *release-only
-  image: elixir:1.10.3-alpine 
+  image: elixir:1.10.3-alpine
   cache: *release-cache
   variables: *release-variables
   before_script: &before-release-musl

From 8c1d6e88395e1d7ada9d86236a7fa2339d9097e9 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Thu, 29 Apr 2021 12:20:46 -0500
Subject: [PATCH 182/339] CHANGELOG: Return OAuth token `id`

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a1173414d..9a0171763 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Added
 
 - MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded.
+- Return OAuth token `id` (primary key) in POST `/oauth/token`.
 
 ## Unreleased (Patch)
 

From b5ae8268982524a0a4fd295ddef64e4983832489 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Thu, 29 Apr 2021 13:03:41 -0500
Subject: [PATCH 183/339] CI: Purge pleroma build directory between runs

---
 .gitlab-ci.yml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2651ff9e6..78e715d47 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -24,6 +24,7 @@ stages:
   - docker
 
 before_script:
+  - rm -rf _build/*/lib/pleroma
   - apt-get update && apt-get install -y cmake
   - mix local.hex --force
   - mix local.rebar --force
@@ -31,6 +32,9 @@ before_script:
   - apt-get -qq update
   - apt-get install -y libmagic-dev
 
+after_script:
+  - rm -rf _build/*/lib/pleroma
+
 build:
   stage: build
   script:

From 004bcedb074d50bc42803e4c0a884239bd504b3d Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Fri, 30 Apr 2021 12:23:11 -0500
Subject: [PATCH 184/339] Upgrade Earmark 1.4.15

---
 mix.exs  | 2 +-
 mix.lock | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/mix.exs b/mix.exs
index 06d77edb7..8ba2d8fbc 100644
--- a/mix.exs
+++ b/mix.exs
@@ -144,7 +144,7 @@ defp deps do
       {:ex_aws, "~> 2.1.6"},
       {:ex_aws_s3, "~> 2.0"},
       {:sweet_xml, "~> 0.6.6"},
-      {:earmark, "1.4.13"},
+      {:earmark, "1.4.15"},
       {:bbcode_pleroma, "~> 0.2.0"},
       {:crypt,
        git: "https://github.com/msantos/crypt.git",
diff --git a/mix.lock b/mix.lock
index e4dd32c83..06542f18d 100644
--- a/mix.lock
+++ b/mix.lock
@@ -27,8 +27,8 @@
   "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
   "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.13", "2c6ce9768fc9fdbf4046f457e207df6360ee6c91ee1ecb8e9a139f96a4289d91", [:mix], [{:earmark_parser, ">= 1.4.12", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "a0cf3ed88ef2b1964df408889b5ecb886d1a048edde53497fc935ccd15af3403"},
-  "earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"},
+  "earmark": {:hex, :earmark, "1.4.15", "2c7f924bf495ec1f65bd144b355d0949a05a254d0ec561740308a54946a67888", [:mix], [{:earmark_parser, ">= 1.4.13", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "3b1209b85bc9f3586f370f7c363f6533788fb4e51db23aa79565875e7f9999ee"},
+  "earmark_parser": {:hex, :earmark_parser, "1.4.13", "0c98163e7d04a15feb62000e1a891489feb29f3d10cb57d4f845c405852bbef8", [:mix], [], "hexpm", "d602c26af3a0af43d2f2645613f65841657ad6efc9f0e361c3b6c06b578214ba"},
   "ecto": {:hex, :ecto, "3.4.6", "08f7afad3257d6eb8613309af31037e16c36808dfda5a3cd0cb4e9738db030e4", [: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", "6f13a9e2a62e75c2dcfc7207bfc65645ab387af8360db4c89fee8b5a4bf3f70b"},
   "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.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"},

From 6727a3659f60c0e09fa6375b6c0843c01f5be3dc Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Fri, 30 Apr 2021 12:27:06 -0500
Subject: [PATCH 185/339] Remove Pleroma.Formatter.minify/2

---
 lib/pleroma/formatter.ex                              | 11 -----------
 .../object_validators/audio_video_validator.ex        |  1 -
 lib/pleroma/web/common_api/utils.ex                   |  1 -
 test/pleroma/formatter_test.exs                       |  7 -------
 4 files changed, 20 deletions(-)

diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index 2aa236ca9..baf652a5a 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -142,17 +142,6 @@ def html_escape(text, "text/plain") do
     |> Enum.join("")
   end
 
-  def minify({text, mentions, hashtags}, type) do
-    {minify(text, type), mentions, hashtags}
-  end
-
-  def minify(text, "text/html") do
-    text
-    |> String.replace(">\n", ">")
-    |> String.replace(">  ", ">")
-    |> String.replace("  <", "<")
-  end
-
   def truncate(text, max_length \\ 200, omission \\ "...") do
     # Remove trailing whitespace
     text = Regex.replace(~r/([^ \t\r\n])([ \t]+$)/u, text, "\\g{1}")
diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
index fa3e2c026..9b38aa4c2 100644
--- a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
@@ -96,7 +96,6 @@ defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = data)
     content =
       content
       |> Pleroma.Formatter.markdown_to_html()
-      |> Pleroma.Formatter.minify("text/html")
       |> Pleroma.HTML.filter_tags()
 
     Map.put(data, "content", content)
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index be86009af..4731e79be 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -296,7 +296,6 @@ def format_input(text, "text/markdown", options) do
     |> Formatter.mentions_escape(options)
     |> Formatter.markdown_to_html()
     |> Formatter.linkify(options)
-    |> Formatter.minify("text/html")
     |> Formatter.html_escape("text/html")
   end
 
diff --git a/test/pleroma/formatter_test.exs b/test/pleroma/formatter_test.exs
index ceedd1b6d..5781a3f01 100644
--- a/test/pleroma/formatter_test.exs
+++ b/test/pleroma/formatter_test.exs
@@ -307,11 +307,4 @@ test "it escapes HTML in plain text" do
 
     assert Formatter.html_escape(text, "text/plain") == expected
   end
-
-  test "it minifies html" do
-    text = "<p>\nhello</p>\n<p>\nworld</p>\n"
-    expected = "<p>hello</p><p>world</p>"
-
-    assert Formatter.minify(text, "text/html") == expected
-  end
 end

From 53760d2cda9b9f241355365b3fff9852bcb1a8a2 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Fri, 30 Apr 2021 12:51:18 -0500
Subject: [PATCH 186/339] Delete obsolete EarmarkRendereTests (moved to
 UtilsTest)

---
 test/pleroma/earmark_renderer_test.exs | 79 --------------------------
 1 file changed, 79 deletions(-)
 delete mode 100644 test/pleroma/earmark_renderer_test.exs

diff --git a/test/pleroma/earmark_renderer_test.exs b/test/pleroma/earmark_renderer_test.exs
deleted file mode 100644
index 3adbefc1e..000000000
--- a/test/pleroma/earmark_renderer_test.exs
+++ /dev/null
@@ -1,79 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.EarmarkRendererTest do
-  use ExUnit.Case
-
-  test "Paragraph" do
-    code = ~s[Hello\n\nWorld!]
-    result = Pleroma.Formatter.markdown_to_html(code)
-    assert result == "<p>Hello</p><p>World!</p>"
-  end
-
-  test "raw HTML" do
-    code = ~s[<a href="http://example.org/">OwO</a><!-- what's this?-->]
-    result = Pleroma.Formatter.markdown_to_html(code)
-    assert result == "<p>#{code}</p>"
-  end
-
-  test "rulers" do
-    code = ~s[before\n\n-----\n\nafter]
-    result = Pleroma.Formatter.markdown_to_html(code)
-    assert result == "<p>before</p><hr /><p>after</p>"
-  end
-
-  test "headings" do
-    code = ~s[# h1\n## h2\n### h3\n]
-    result = Pleroma.Formatter.markdown_to_html(code)
-    assert result == ~s[<h1>h1</h1><h2>h2</h2><h3>h3</h3>]
-  end
-
-  test "blockquote" do
-    code = ~s[> whoms't are you quoting?]
-    result = Pleroma.Formatter.markdown_to_html(code)
-    assert result == "<blockquote><p>whoms’t are you quoting?</p></blockquote>"
-  end
-
-  test "code" do
-    code = ~s[`mix`]
-    result = Pleroma.Formatter.markdown_to_html(code)
-    assert result == ~s[<p><code class="inline">mix</code></p>]
-
-    code = ~s[``mix``]
-    result = Pleroma.Formatter.markdown_to_html(code)
-    assert result == ~s[<p><code class="inline">mix</code></p>]
-
-    code = ~s[```\nputs "Hello World"\n```]
-    result = Pleroma.Formatter.markdown_to_html(code)
-    assert result == ~s[<pre><code class="">puts &quot;Hello World&quot;</code></pre>]
-  end
-
-  test "lists" do
-    code = ~s[- one\n- two\n- three\n- four]
-    result = Pleroma.Formatter.markdown_to_html(code)
-    assert result == "<ul><li>one</li><li>two</li><li>three</li><li>four</li></ul>"
-
-    code = ~s[1. one\n2. two\n3. three\n4. four\n]
-    result = Pleroma.Formatter.markdown_to_html(code)
-    assert result == "<ol><li>one</li><li>two</li><li>three</li><li>four</li></ol>"
-  end
-
-  test "delegated renderers" do
-    code = ~s[a<br/>b]
-    result = Pleroma.Formatter.markdown_to_html(code)
-    assert result == "<p>#{code}</p>"
-
-    code = ~s[*aaaa~*]
-    result = Pleroma.Formatter.markdown_to_html(code)
-    assert result == ~s[<p><em>aaaa~</em></p>]
-
-    code = ~s[**aaaa~**]
-    result = Pleroma.Formatter.markdown_to_html(code)
-    assert result == ~s[<p><strong>aaaa~</strong></p>]
-
-    # strikethrought
-    code = ~s[<del>aaaa~</del>]
-    result = Pleroma.Formatter.markdown_to_html(code)
-    assert result == ~s[<p><del>aaaa~</del></p>]
-  end
-end

From a8fa00ef666f574aec8048626aed78a7d62e6915 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Fri, 30 Apr 2021 12:55:43 -0500
Subject: [PATCH 187/339] Fix failing remote mentions test, valid TLDs

---
 test/pleroma/web/common_api/utils_test.exs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs
index 28b05ed91..8c79a9a83 100644
--- a/test/pleroma/web/common_api/utils_test.exs
+++ b/test/pleroma/web/common_api/utils_test.exs
@@ -209,10 +209,10 @@ test "local mentions" do
     end
 
     test "remote mentions" do
-      mario = insert(:user, %{nickname: "mario@mushroom.kingdom", local: false})
-      luigi = insert(:user, %{nickname: "luigi@mushroom.kingdom", local: false})
+      mario = insert(:user, %{nickname: "mario@mushroom.world", local: false})
+      luigi = insert(:user, %{nickname: "luigi@mushroom.world", local: false})
 
-      code = "@mario@mushroom.kingdom @luigi@mushroom.kingdom yo what's up?"
+      code = "@mario@mushroom.world @luigi@mushroom.world yo what's up?"
       {result, _, []} = Utils.format_input(code, "text/markdown")
 
       assert result ==

From 3d742c3c1af69a9526c12a171663630b3439b5cc Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Thu, 18 Mar 2021 15:31:50 -0500
Subject: [PATCH 188/339] SimplePolicy: filter nested objects

---
 lib/pleroma/web/activity_pub/mrf/simple_policy.ex    | 11 ++++++++++-
 .../web/activity_pub/mrf/simple_policy_test.exs      | 12 ++++++++++++
 2 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index bb3838d2c..b3e5d814d 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -181,6 +181,14 @@ defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image
 
   defp check_banner_removal(_actor_info, object), do: {:ok, object}
 
+  defp check_object(%{"object" => object} = activity) when is_map(object) do
+    with {:ok, _object} <- filter(object) do
+      {:ok, activity}
+    end
+  end
+
+  defp check_object(object), do: {:ok, object}
+
   @impl true
   def filter(%{"type" => "Delete", "actor" => actor} = object) do
     %{host: actor_host} = URI.parse(actor)
@@ -206,7 +214,8 @@ def filter(%{"actor" => actor} = object) do
          {:ok, object} <- check_media_nsfw(actor_info, object),
          {:ok, object} <- check_ftl_removal(actor_info, object),
          {:ok, object} <- check_followers_only(actor_info, object),
-         {:ok, object} <- check_report_removal(actor_info, object) do
+         {:ok, object} <- check_report_removal(actor_info, object),
+         {:ok, object} <- check_object(object) do
       {:ok, object}
     else
       {:reject, nil} -> {:reject, "[SimplePolicy]"}
diff --git a/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs b/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs
index f48e5b39b..b6d9f2ded 100644
--- a/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs
+++ b/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs
@@ -260,6 +260,18 @@ test "actor has a matching host" do
 
       assert {:reject, _} = SimplePolicy.filter(remote_user)
     end
+
+    test "reject Announce when object would be rejected" do
+      clear_config([:mrf_simple, :reject], ["blocked.tld"])
+
+      announce = %{
+        "type" => "Announce",
+        "actor" => "https://okay.tld/users/alice",
+        "object" => %{"type" => "Note", "actor" => "https://blocked.tld/users/bob"}
+      }
+
+      assert {:reject, _} = SimplePolicy.filter(announce)
+    end
   end
 
   describe "when :followers_only" do

From c16c7fdb8794df8558cf8fbe4231d8f9ec01bb6d Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Thu, 29 Apr 2021 11:51:49 -0500
Subject: [PATCH 189/339] SimplePolicy: filter string Objects

---
 lib/pleroma/web/activity_pub/mrf/simple_policy.ex | 15 ++++++++++++++-
 .../web/activity_pub/mrf/simple_policy_test.exs   | 12 ++++++++++++
 2 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index b3e5d814d..b07d70401 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -181,7 +181,7 @@ defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image
 
   defp check_banner_removal(_actor_info, object), do: {:ok, object}
 
-  defp check_object(%{"object" => object} = activity) when is_map(object) do
+  defp check_object(%{"object" => object} = activity) do
     with {:ok, _object} <- filter(object) do
       {:ok, activity}
     end
@@ -240,6 +240,19 @@ def filter(%{"id" => actor, "type" => obj_type} = object)
     end
   end
 
+  def filter(object) when is_binary(object) do
+    uri = URI.parse(object)
+
+    with {:ok, object} <- check_accept(uri, object),
+         {:ok, object} <- check_reject(uri, object) do
+      {:ok, object}
+    else
+      {:reject, nil} -> {:reject, "[SimplePolicy]"}
+      {:reject, _} = e -> e
+      _ -> {:reject, "[SimplePolicy]"}
+    end
+  end
+
   def filter(object), do: {:ok, object}
 
   @impl true
diff --git a/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs b/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs
index b6d9f2ded..8024a2459 100644
--- a/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs
+++ b/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs
@@ -272,6 +272,18 @@ test "reject Announce when object would be rejected" do
 
       assert {:reject, _} = SimplePolicy.filter(announce)
     end
+
+    test "reject by URI object" do
+      clear_config([:mrf_simple, :reject], ["blocked.tld"])
+
+      announce = %{
+        "type" => "Announce",
+        "actor" => "https://okay.tld/users/alice",
+        "object" => "https://blocked.tld/activities/1"
+      }
+
+      assert {:reject, _} = SimplePolicy.filter(announce)
+    end
   end
 
   describe "when :followers_only" do

From 20878c7f9913e1501821356f24e97c2c42b00a41 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Fri, 2 Apr 2021 12:18:35 -0500
Subject: [PATCH 190/339] CHANGELOG: SimplePolicy embedded objects are now
 checked

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9a0171763..150cd4147 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Applying ConcurrentLimiter settings via AdminAPI
 - User login failures if their `notification_settings` were in a NULL state.
 - Mix task `pleroma.user delete_activities` query transaction timeout is now :infinity
+- MRF (`SimplePolicy`): Embedded objects are now checked. If any embedded object would be rejected, its parent is rejected. This fixes Announces leaking posts from blocked domains.
 
 ## [2.3.0] - 2020-03-01
 

From dca87c5e7b4b12918cf59a83a77be389a7e0df01 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Sat, 1 May 2021 11:28:06 -0500
Subject: [PATCH 191/339] CHANGELOG: markdown

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9a0171763..ed6e548dd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Applying ConcurrentLimiter settings via AdminAPI
 - User login failures if their `notification_settings` were in a NULL state.
 - Mix task `pleroma.user delete_activities` query transaction timeout is now :infinity
+- Fixed some Markdown issues, including trailing slash in links.
 
 ## [2.3.0] - 2020-03-01
 

From c80b1aaf514dec6b538a9833d48df027708b6b4d Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Mon, 3 May 2021 14:27:03 -0500
Subject: [PATCH 192/339] Don't crash when email settings are invalid Fixes:
 https://git.pleroma.social/pleroma/pleroma/-/issues/2606 Fixes:
 https://gitlab.com/soapbox-pub/soapbox/-/issues/4

---
 lib/pleroma/application_requirements.ex       | 38 ++++++++++---------
 .../pleroma/application_requirements_test.exs | 18 ++++-----
 test/pleroma/user_test.exs                    | 18 +++++++++
 3 files changed, 47 insertions(+), 27 deletions(-)

diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex
index 6ef65b263..c412dec5e 100644
--- a/lib/pleroma/application_requirements.ex
+++ b/lib/pleroma/application_requirements.ex
@@ -34,15 +34,16 @@ defp handle_result({:error, message}), do: raise(VerifyError, message: message)
   defp check_welcome_message_config!(:ok) do
     if Pleroma.Config.get([:welcome, :email, :enabled], false) and
          not Pleroma.Emails.Mailer.enabled?() do
-      Logger.error("""
-      To send welcome email do you need to enable mail.
-      \nconfig :pleroma, Pleroma.Emails.Mailer, enabled: true
-      """)
+      Logger.warn("""
+      To send welcome emails, you need to enable the mailer.
+      Welcome emails will NOT be sent with the current config.
 
-      {:error, "The mail disabled."}
-    else
-      :ok
+      Enable the mailer:
+        config :pleroma, Pleroma.Emails.Mailer, enabled: true
+      """)
     end
+
+    :ok
   end
 
   defp check_welcome_message_config!(result), do: result
@@ -51,18 +52,21 @@ defp check_welcome_message_config!(result), do: result
   #
   def check_confirmation_accounts!(:ok) do
     if Pleroma.Config.get([:instance, :account_activation_required]) &&
-         not Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled]) do
-      Logger.error(
-        "Account activation enabled, but no Mailer settings enabled.\n" <>
-          "Please set config :pleroma, :instance, account_activation_required: false\n" <>
-          "Otherwise setup and enable Mailer."
-      )
+         not Pleroma.Emails.Mailer.enabled?() do
+      Logger.warn("""
+      Account activation is required, but the mailer is disabled.
+      Users will NOT be able to confirm their accounts with this config.
+      Either disable account activation or enable the mailer.
 
-      {:error,
-       "Account activation enabled, but Mailer is disabled. Cannot send confirmation emails."}
-    else
-      :ok
+      Disable account activation:
+        config :pleroma, :instance, account_activation_required: false
+
+      Enable the mailer:
+        config :pleroma, Pleroma.Emails.Mailer, enabled: true
+      """)
     end
+
+    :ok
   end
 
   def check_confirmation_accounts!(result), do: result
diff --git a/test/pleroma/application_requirements_test.exs b/test/pleroma/application_requirements_test.exs
index 683ac8c96..a54c37968 100644
--- a/test/pleroma/application_requirements_test.exs
+++ b/test/pleroma/application_requirements_test.exs
@@ -35,13 +35,13 @@ test "doesn't raise if the pool size is unexpected but the respective flag is se
     setup do: clear_config([:welcome])
     setup do: clear_config([Pleroma.Emails.Mailer])
 
-    test "raises if welcome email enabled but mail disabled" do
+    test "warns if welcome email enabled but mail disabled" do
       clear_config([:welcome, :email, :enabled], true)
       clear_config([Pleroma.Emails.Mailer, :enabled], false)
 
-      assert_raise Pleroma.ApplicationRequirements.VerifyError, "The mail disabled.", fn ->
-        capture_log(&Pleroma.ApplicationRequirements.verify!/0)
-      end
+      assert capture_log(fn ->
+               assert Pleroma.ApplicationRequirements.verify!() == :ok
+             end) =~ "Welcome emails will NOT be sent"
     end
   end
 
@@ -57,15 +57,13 @@ test "raises if welcome email enabled but mail disabled" do
 
     setup do: clear_config([:instance, :account_activation_required])
 
-    test "raises if account confirmation is required but mailer isn't enable" do
+    test "warns if account confirmation is required but mailer isn't enabled" do
       clear_config([:instance, :account_activation_required], true)
       clear_config([Pleroma.Emails.Mailer, :enabled], false)
 
-      assert_raise Pleroma.ApplicationRequirements.VerifyError,
-                   "Account activation enabled, but Mailer is disabled. Cannot send confirmation emails.",
-                   fn ->
-                     capture_log(&Pleroma.ApplicationRequirements.verify!/0)
-                   end
+      assert capture_log(fn ->
+               assert Pleroma.ApplicationRequirements.verify!() == :ok
+             end) =~ "Users will NOT be able to confirm their accounts"
     end
 
     test "doesn't do anything if account confirmation is disabled" do
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index 6f5bcab57..f89ea458a 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -572,6 +572,24 @@ test "it sends a registration confirmed email if no others will be sent" do
       )
     end
 
+    test "it fails gracefully with invalid email config" do
+      cng = User.register_changeset(%User{}, @full_user_data)
+
+      # Disable the mailer but enable all the things that want to send emails
+      clear_config([Pleroma.Emails.Mailer, :enabled], false)
+      clear_config([:instance, :account_activation_required], true)
+      clear_config([:instance, :account_approval_required], true)
+      clear_config([:welcome, :email, :enabled], true)
+      clear_config([:welcome, :email, :sender], "lain@lain.com")
+
+      # The user is still created
+      assert {:ok, %User{nickname: "nick"}} = User.register(cng)
+
+      # No emails are sent
+      ObanHelpers.perform_all()
+      refute_email_sent()
+    end
+
     test "it requires an email, name, nickname and password, bio is optional when account_activation_required is enabled" do
       clear_config([:instance, :account_activation_required], true)
 

From 90770e0841d3ffea87627b35627bfe38cad52f07 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Mon, 3 May 2021 14:30:21 -0500
Subject: [PATCH 193/339] CHANGELOG: don't crash so hard when email settings
 are invalid

---
 CHANGELOG.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9a0171763..74086a54b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded.
 - Return OAuth token `id` (primary key) in POST `/oauth/token`.
 
+### Fixed
+- Don't crash so hard when email settings are invalid.
+
 ## Unreleased (Patch)
 
 ### Fixed

From 22b2451edd9e42ba96bf7f815383b2eaad9a5e56 Mon Sep 17 00:00:00 2001
From: faried nawaz <faried@gmail.com>
Date: Wed, 21 Apr 2021 02:37:03 +0500
Subject: [PATCH 194/339] migration: add on_delete: :delete_all to hashtags
 object_id fk

---
 ...204354_delete_hashtags_objects_cascade.exs | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)
 create mode 100644 priv/repo/migrations/20210420204354_delete_hashtags_objects_cascade.exs

diff --git a/priv/repo/migrations/20210420204354_delete_hashtags_objects_cascade.exs b/priv/repo/migrations/20210420204354_delete_hashtags_objects_cascade.exs
new file mode 100644
index 000000000..f4ebf53d6
--- /dev/null
+++ b/priv/repo/migrations/20210420204354_delete_hashtags_objects_cascade.exs
@@ -0,0 +1,19 @@
+defmodule Pleroma.Repo.Migrations.DeleteHashtagsObjectsCascade do
+  use Ecto.Migration
+
+  def up do
+    execute("ALTER TABLE hashtags_objects DROP CONSTRAINT hashtags_objects_object_id_fkey")
+
+    alter table(:hashtags_objects) do
+      modify(:object_id, references(:objects, on_delete: :delete_all))
+    end
+  end
+
+  def down do
+    execute("ALTER TABLE hashtags_objects DROP CONSTRAINT hashtags_objects_object_id_fkey")
+
+    alter table(:hashtags_objects) do
+      modify(:object_id, references(:objects, on_delete: :nothing))
+    end
+  end
+end

From a0c9a2b4cc8c22d6238b0f31239c1e655f47730f Mon Sep 17 00:00:00 2001
From: faried nawaz <faried@gmail.com>
Date: Wed, 21 Apr 2021 02:38:59 +0500
Subject: [PATCH 195/339] mix prune_objects: remove unused hashtags after
 pruning remote objects

---
 lib/mix/tasks/pleroma/database.ex | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index e7f4b67a4..53ad58b64 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -96,6 +96,17 @@ def run(["prune_objects" | args]) do
     )
     |> Repo.delete_all(timeout: :infinity)
 
+    prune_hashtags_query = """
+    delete from hashtags
+    where id in (
+      select id from hashtags as ht
+      left join hashtags_objects as hto
+      on hto.hashtag_id = ht.id
+      where hto.hashtag_id is null)
+    """
+
+    Repo.query(prune_hashtags_query)
+
     if Keyword.get(options, :vacuum) do
       Maintenance.vacuum("full")
     end

From 5be9d139816fa40ff6227950b58f3c6cea01fc81 Mon Sep 17 00:00:00 2001
From: faried nawaz <faried@gmail.com>
Date: Wed, 21 Apr 2021 03:52:32 +0500
Subject: [PATCH 196/339] a better query to delete from hashtags

old query:

Delete on hashtags  (cost=5089.81..5521.63 rows=6160 width=18)
   ->  Hash Semi Join  (cost=5089.81..5521.63 rows=6160 width=18)
         Hash Cond: (hashtags.id = ht.id)
         ->  Seq Scan on hashtags  (cost=0.00..317.28 rows=17528 width=14)
         ->  Hash  (cost=5012.81..5012.81 rows=6160 width=20)
               ->  Merge Anti Join  (cost=0.70..5012.81 rows=6160 width=20)
                     Merge Cond: (ht.id = hto.hashtag_id)
                     ->  Index Scan using hashtags_pkey on hashtags ht  (cost=0.29..610.53 rows=17528 width=14)
                     ->  Index Scan using hashtags_objects_pkey on hashtags_objects hto  (cost=0.42..3506.48 rows=68158 width=14)

new query:

Delete on hashtags ht  (cost=0.70..5012.81 rows=6160 width=12)
   ->  Merge Anti Join  (cost=0.70..5012.81 rows=6160 width=12)
         Merge Cond: (ht.id = hto.hashtag_id)
         ->  Index Scan using hashtags_pkey on hashtags ht  (cost=0.29..610.53 rows=17528 width=14)
         ->  Index Scan using hashtags_objects_pkey on hashtags_objects hto  (cost=0.42..3506.48 rows=68158 width=14)
---
 lib/mix/tasks/pleroma/database.ex | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index 53ad58b64..bcde07774 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -97,12 +97,10 @@ def run(["prune_objects" | args]) do
     |> Repo.delete_all(timeout: :infinity)
 
     prune_hashtags_query = """
-    delete from hashtags
-    where id in (
-      select id from hashtags as ht
-      left join hashtags_objects as hto
-      on hto.hashtag_id = ht.id
-      where hto.hashtag_id is null)
+    delete from hashtags as ht
+    where not exists (
+      select 1 from hashtags_objects hto
+      where ht.id = hto.hashtag_id)
     """
 
     Repo.query(prune_hashtags_query)

From ab9eabdf20180f2dd8539cf5d3dc0fdc6412496b Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Wed, 12 May 2021 13:38:11 -0500
Subject: [PATCH 197/339] Add SetMeta filter to store uploaded image sizes

---
 lib/pleroma/upload.ex                         |  9 ++++-
 lib/pleroma/upload/filter/set_meta.ex         | 36 +++++++++++++++++++
 .../web/mastodon_api/views/status_view.ex     | 16 +++++++++
 test/pleroma/upload/filter/set_meta_test.exs  | 19 ++++++++++
 .../mastodon_api/views/status_view_test.exs   |  5 ++-
 5 files changed, 83 insertions(+), 2 deletions(-)
 create mode 100644 lib/pleroma/upload/filter/set_meta.ex
 create mode 100644 test/pleroma/upload/filter/set_meta_test.exs

diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index 654711351..4d58abd48 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -23,6 +23,8 @@ defmodule Pleroma.Upload do
     is once created permanent and changing it (especially in uploaders) is probably a bad idea!
   * `:tempfile` - path to the temporary file. Prefer in-place changes on the file rather than changing the
   path as the temporary file is also tracked by `Plug.Upload{}` and automatically deleted once the request is over.
+  * `:width` - width of the media in pixels
+  * `:height` - height of the media in pixels
 
   Related behaviors:
 
@@ -32,6 +34,7 @@ defmodule Pleroma.Upload do
   """
   alias Ecto.UUID
   alias Pleroma.Config
+  alias Pleroma.Maps
   require Logger
 
   @type source ::
@@ -53,9 +56,11 @@ defmodule Pleroma.Upload do
           name: String.t(),
           tempfile: String.t(),
           content_type: String.t(),
+          width: integer(),
+          height: integer(),
           path: String.t()
         }
-  defstruct [:id, :name, :tempfile, :content_type, :path]
+  defstruct [:id, :name, :tempfile, :content_type, :width, :height, :path]
 
   defp get_description(opts, upload) do
     case {opts[:description], Pleroma.Config.get([Pleroma.Upload, :default_description])} do
@@ -89,6 +94,8 @@ def store(upload, opts \\ []) do
              "mediaType" => upload.content_type,
              "href" => url_from_spec(upload, opts.base_url, url_spec)
            }
+           |> Maps.put_if_present("width", upload.width)
+           |> Maps.put_if_present("height", upload.height)
          ],
          "name" => description
        }}
diff --git a/lib/pleroma/upload/filter/set_meta.ex b/lib/pleroma/upload/filter/set_meta.ex
new file mode 100644
index 000000000..cccb6c371
--- /dev/null
+++ b/lib/pleroma/upload/filter/set_meta.ex
@@ -0,0 +1,36 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.Filter.SetMeta do
+  @moduledoc """
+  Extracts metadata about the upload, such as width/height
+  """
+  require Logger
+
+  @behaviour Pleroma.Upload.Filter
+
+  @spec filter(Pleroma.Upload.t()) ::
+          {:ok, :filtered, Pleroma.Upload.t()} | {:ok, :noop} | {:error, String.t()}
+  def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _} = upload) do
+    try do
+      image =
+        file
+        |> Mogrify.open()
+        |> Mogrify.verbose()
+
+      upload =
+        upload
+        |> Map.put(:width, image.width)
+        |> Map.put(:height, image.height)
+
+      {:ok, :filtered, upload}
+    rescue
+      e in ErlangError ->
+        Logger.warn("#{__MODULE__}: #{inspect(e)}")
+        {:ok, :noop}
+    end
+  end
+
+  def filter(_), do: {:ok, :noop}
+end
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index bac897a57..5dbdc309e 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -426,10 +426,26 @@ def render("attachment.json", %{attachment: attachment}) do
       type: type,
       description: attachment["name"],
       pleroma: %{mime_type: media_type},
+      meta: render("attachment_meta.json", %{attachment: attachment}),
       blurhash: attachment["blurhash"]
     }
   end
 
+  def render("attachment_meta.json", %{
+        attachment: %{"url" => [%{"width" => width, "height" => height} | _]}
+      })
+      when is_integer(width) and is_integer(height) do
+    %{
+      original: %{
+        width: width,
+        height: height,
+        aspect: width / height
+      }
+    }
+  end
+
+  def render("attachment_meta.json", _), do: %{}
+
   def render("context.json", %{activity: activity, activities: activities, user: user}) do
     %{ancestors: ancestors, descendants: descendants} =
       activities
diff --git a/test/pleroma/upload/filter/set_meta_test.exs b/test/pleroma/upload/filter/set_meta_test.exs
new file mode 100644
index 000000000..650e527b4
--- /dev/null
+++ b/test/pleroma/upload/filter/set_meta_test.exs
@@ -0,0 +1,19 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.Filter.SetMetaTest do
+  use Pleroma.DataCase, async: true
+  alias Pleroma.Upload.Filter.SetMeta
+
+  test "adds the image dimensions" do
+    upload = %Pleroma.Upload{
+      name: "an… image.jpg",
+      content_type: "image/jpeg",
+      path: Path.absname("test/fixtures/image.jpg"),
+      tempfile: Path.absname("test/fixtures/image.jpg")
+    }
+
+    assert {:ok, :filtered, %{width: 1024, height: 768}} = SetMeta.filter(upload)
+  end
+end
diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs
index 2de3afc4f..e6c37e782 100644
--- a/test/pleroma/web/mastodon_api/views/status_view_test.exs
+++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs
@@ -458,7 +458,9 @@ test "attachments" do
       "url" => [
         %{
           "mediaType" => "image/png",
-          "href" => "someurl"
+          "href" => "someurl",
+          "width" => 200,
+          "height" => 100
         }
       ],
       "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
@@ -474,6 +476,7 @@ test "attachments" do
       text_url: "someurl",
       description: nil,
       pleroma: %{mime_type: "image/png"},
+      meta: %{original: %{width: 200, height: 100, aspect: 2}},
       blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
     }
 

From 4c060ae73371a8567468186e5d1333ec00fbdf41 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Wed, 12 May 2021 15:38:49 -0500
Subject: [PATCH 198/339] Ingest remote attachment width/height

---
 .../object_validators/attachment_validator.ex |  4 ++-
 .../web/activity_pub/transmogrifier.ex        |  2 ++
 .../attachment_validator_test.exs             | 33 +++++++++++++++++++
 .../transmogrifier/audio_handling_test.exs    |  4 ++-
 .../transmogrifier/video_handling_test.exs    | 12 +++++--
 5 files changed, 50 insertions(+), 5 deletions(-)

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 3175427ad..a99b40adc 100644
--- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
@@ -21,6 +21,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
       field(:type, :string)
       field(:href, ObjectValidators.Uri)
       field(:mediaType, :string, default: "application/octet-stream")
+      field(:width, :integer)
+      field(:height, :integer)
     end
   end
 
@@ -52,7 +54,7 @@ def url_changeset(struct, data) do
     data = fix_media_type(data)
 
     struct
-    |> cast(data, [:type, :href, :mediaType])
+    |> cast(data, [:type, :href, :mediaType, :width, :height])
     |> validate_inclusion(:type, ["Link"])
     |> validate_required([:type, :href, :mediaType])
   end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 4d9a5617e..b5767863c 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -245,6 +245,8 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm
               "type" => Map.get(url || %{}, "type", "Link")
             }
             |> Maps.put_if_present("mediaType", media_type)
+            |> Maps.put_if_present("width", (url || %{})["width"])
+            |> Maps.put_if_present("height", (url || %{})["height"])
 
           %{
             "url" => [attachment_url],
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 b775515e0..0e49fda99 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
@@ -72,5 +72,38 @@ test "it handles our own uploads" do
 
       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
   end
 end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs
index e733f167d..a21e9e3d3 100644
--- a/test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs
@@ -76,7 +76,9 @@ test "Funkwhale Audio object" do
                    "href" =>
                      "https://channels.tests.funkwhale.audio/api/v1/listen/3901e5d8-0445-49d5-9711-e096cf32e515/?upload=42342395-0208-4fee-a38d-259a6dae0871&download=false",
                    "mediaType" => "audio/ogg",
-                   "type" => "Link"
+                   "type" => "Link",
+                   "width" => nil,
+                   "height" => nil
                  }
                ]
              }
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 6ddf7c172..62b4a2cb3 100644
--- a/test/pleroma/web/activity_pub/transmogrifier/video_handling_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier/video_handling_test.exs
@@ -60,7 +60,9 @@ test "it remaps video URLs as attachments if necessary" do
                    "href" =>
                      "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
                    "mediaType" => "video/mp4",
-                   "type" => "Link"
+                   "type" => "Link",
+                   "width" => nil,
+                   "height" => nil
                  }
                ]
              }
@@ -83,7 +85,9 @@ test "it remaps video URLs as attachments if necessary" do
                    "href" =>
                      "https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4",
                    "mediaType" => "video/mp4",
-                   "type" => "Link"
+                   "type" => "Link",
+                   "width" => nil,
+                   "height" => nil
                  }
                ]
              }
@@ -113,7 +117,9 @@ test "it works for peertube videos with only their mpegURL map" do
                    "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",
+                   "width" => nil,
+                   "height" => nil
                  }
                ]
              }

From 02b9436494998e441fe2119b78c0e4f68c45a9e1 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Wed, 12 May 2021 16:16:10 -0500
Subject: [PATCH 199/339] Don't render media `meta` if nil

---
 lib/pleroma/web/mastodon_api/views/status_view.ex | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 5dbdc309e..7f318e81b 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
 
   alias Pleroma.Activity
   alias Pleroma.HTML
+  alias Pleroma.Maps
   alias Pleroma.Object
   alias Pleroma.Repo
   alias Pleroma.User
@@ -406,6 +407,7 @@ def render("attachment.json", %{attachment: attachment}) do
     media_type = attachment_url["mediaType"] || attachment_url["mimeType"] || "image"
     href = attachment_url["href"] |> MediaProxy.url()
     href_preview = attachment_url["href"] |> MediaProxy.preview_url()
+    meta = render("attachment_meta.json", %{attachment: attachment})
 
     type =
       cond do
@@ -426,9 +428,9 @@ def render("attachment.json", %{attachment: attachment}) do
       type: type,
       description: attachment["name"],
       pleroma: %{mime_type: media_type},
-      meta: render("attachment_meta.json", %{attachment: attachment}),
       blurhash: attachment["blurhash"]
     }
+    |> Maps.put_if_present(:meta, meta)
   end
 
   def render("attachment_meta.json", %{
@@ -444,7 +446,7 @@ def render("attachment_meta.json", %{
     }
   end
 
-  def render("attachment_meta.json", _), do: %{}
+  def render("attachment_meta.json", _), do: nil
 
   def render("context.json", %{activity: activity, activities: activities, user: user}) do
     %{ancestors: ancestors, descendants: descendants} =

From 6f0b42656dcce9cd7e4c833be42b6544954ca93b Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Wed, 12 May 2021 19:03:10 -0500
Subject: [PATCH 200/339] Federate attachments as Links instead of Documents

---
 lib/pleroma/web/activity_pub/transmogrifier.ex | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index b5767863c..acb4f4b3e 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -245,8 +245,8 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm
               "type" => Map.get(url || %{}, "type", "Link")
             }
             |> Maps.put_if_present("mediaType", media_type)
-            |> Maps.put_if_present("width", (url || %{})["width"])
-            |> Maps.put_if_present("height", (url || %{})["height"])
+            |> Maps.put_if_present("width", (url || %{})["width"] || data["width"])
+            |> Maps.put_if_present("height", (url || %{})["height"] || data["height"])
 
           %{
             "url" => [attachment_url],
@@ -963,7 +963,7 @@ def prepare_attachments(object) do
       object
       |> Map.get("attachment", [])
       |> Enum.map(fn data ->
-        [%{"mediaType" => media_type, "href" => href} | _] = data["url"]
+        [%{"mediaType" => media_type, "href" => href} = url | _] = data["url"]
 
         %{
           "url" => href,
@@ -971,6 +971,8 @@ def prepare_attachments(object) do
           "name" => data["name"],
           "type" => "Document"
         }
+        |> Maps.put_if_present("width", url["width"])
+        |> Maps.put_if_present("height", url["height"])
       end)
 
     Map.put(object, "attachment", attachments)

From 5a57b025c7745ebdc7ecf8c7d6b75bcc6770562a Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Wed, 12 May 2021 20:15:33 -0500
Subject: [PATCH 201/339] Changelog: attachment meta

---
 CHANGELOG.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5bb4b1e73..22eaa0b94 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 - MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded.
 - Return OAuth token `id` (primary key) in POST `/oauth/token`.
+- `SetMeta` upload filter for extracting attachment dimensions.
+- Attachment dimensions are federated when available.
 
 ### Fixed
 - Don't crash so hard when email settings are invalid.

From 543e9402d64bce556f85294f91dc690c9acec51f Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Fri, 14 May 2021 08:38:23 -0500
Subject: [PATCH 202/339] Support blurhash

---
 lib/pleroma/upload.ex                          | 7 +++++--
 lib/pleroma/upload/filter/set_meta.ex          | 9 +++++++++
 lib/pleroma/web/activity_pub/transmogrifier.ex | 1 +
 mix.exs                                        | 3 +++
 mix.lock                                       | 1 +
 5 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index 4d58abd48..5570ed104 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -25,6 +25,7 @@ defmodule Pleroma.Upload do
   path as the temporary file is also tracked by `Plug.Upload{}` and automatically deleted once the request is over.
   * `:width` - width of the media in pixels
   * `:height` - height of the media in pixels
+  * `:blurhash` - string hash of the image encoded with the blurhash algorithm (https://blurha.sh/)
 
   Related behaviors:
 
@@ -58,9 +59,10 @@ defmodule Pleroma.Upload do
           content_type: String.t(),
           width: integer(),
           height: integer(),
+          blurhash: String.t(),
           path: String.t()
         }
-  defstruct [:id, :name, :tempfile, :content_type, :width, :height, :path]
+  defstruct [:id, :name, :tempfile, :content_type, :width, :height, :blurhash, :path]
 
   defp get_description(opts, upload) do
     case {opts[:description], Pleroma.Config.get([Pleroma.Upload, :default_description])} do
@@ -98,7 +100,8 @@ def store(upload, opts \\ []) do
            |> Maps.put_if_present("height", upload.height)
          ],
          "name" => description
-       }}
+       }
+       |> Maps.put_if_present("blurhash", upload.blurhash)}
     else
       {:description_limit, _} ->
         {:error, :description_too_long}
diff --git a/lib/pleroma/upload/filter/set_meta.ex b/lib/pleroma/upload/filter/set_meta.ex
index cccb6c371..81c48228a 100644
--- a/lib/pleroma/upload/filter/set_meta.ex
+++ b/lib/pleroma/upload/filter/set_meta.ex
@@ -23,6 +23,7 @@ def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _} = upload)
         upload
         |> Map.put(:width, image.width)
         |> Map.put(:height, image.height)
+        |> Map.put(:blurhash, get_blurhash(file))
 
       {:ok, :filtered, upload}
     rescue
@@ -33,4 +34,12 @@ def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _} = upload)
   end
 
   def filter(_), do: {:ok, :noop}
+
+  defp get_blurhash(file) do
+    with {:ok, blurhash} <- :eblurhash.magick(file) do
+      blurhash
+    else
+      _ -> nil
+    end
+  end
 end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index acb4f4b3e..f601d6111 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -973,6 +973,7 @@ def prepare_attachments(object) do
         }
         |> Maps.put_if_present("width", url["width"])
         |> Maps.put_if_present("height", url["height"])
+        |> Maps.put_if_present("blurhash", data["blurhash"])
       end)
 
     Map.put(object, "attachment", attachments)
diff --git a/mix.exs b/mix.exs
index 436381f32..08581824a 100644
--- a/mix.exs
+++ b/mix.exs
@@ -198,6 +198,9 @@ defp deps do
       {:open_api_spex,
        git: "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git",
        ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"},
+      {:eblurhash,
+       git: "https://github.com/zotonic/eblurhash.git",
+       ref: "04a0b76eadf4de1be17726f39b6313b88708fd12"},
 
       ## dev & test
       {:ex_doc, "~> 0.22", only: :dev, runtime: false},
diff --git a/mix.lock b/mix.lock
index 99be81826..d24f9c699 100644
--- a/mix.lock
+++ b/mix.lock
@@ -29,6 +29,7 @@
   "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
   "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
   "earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
+  "eblurhash": {:git, "https://github.com/zotonic/eblurhash.git", "04a0b76eadf4de1be17726f39b6313b88708fd12", [ref: "04a0b76eadf4de1be17726f39b6313b88708fd12"]},
   "ecto": {:hex, :ecto, "3.4.6", "08f7afad3257d6eb8613309af31037e16c36808dfda5a3cd0cb4e9738db030e4", [: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", "6f13a9e2a62e75c2dcfc7207bfc65645ab387af8360db4c89fee8b5a4bf3f70b"},
   "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.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"},

From b22f54eb29237b4c34a26b497f88770dbebf5578 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Sun, 16 May 2021 12:26:32 -0500
Subject: [PATCH 203/339] Make prod.secret.exs optional (with warning)

---
 config/prod.exs | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/config/prod.exs b/config/prod.exs
index adbce5606..0e151000b 100644
--- a/config/prod.exs
+++ b/config/prod.exs
@@ -63,7 +63,12 @@
 
 # Finally import the config/prod.secret.exs
 # which should be versioned separately.
-import_config "prod.secret.exs"
+if File.exists?("./config/prod.secret.exs") do
+  import_config "prod.secret.exs"
+else
+  "`config/prod.secret.exs` not found. You may want to create one by running `mix pleroma.instance gen`"
+  |> IO.warn([])
+end
 
 if File.exists?("./config/prod.exported_from_db.secret.exs"),
   do: import_config("prod.exported_from_db.secret.exs")

From b540fff9081765feeadcc880af43f5d5d49d1e9c Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Sun, 16 May 2021 12:20:20 -0500
Subject: [PATCH 204/339] Docs: use `MIX_ENV=prod mix pleroma.instance gen`

---
 docs/installation/alpine_linux_en.md |  2 +-
 docs/installation/arch_linux_en.md   |  2 +-
 docs/installation/debian_based_en.md |  2 +-
 docs/installation/debian_based_jp.md |  4 ++--
 docs/installation/freebsd_en.md      |  6 +++---
 docs/installation/gentoo_en.md       | 10 +++++-----
 docs/installation/netbsd_en.md       |  4 ++--
 docs/installation/openbsd_en.md      |  2 +-
 8 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/docs/installation/alpine_linux_en.md b/docs/installation/alpine_linux_en.md
index 7eb1718f2..c2dbd836d 100644
--- a/docs/installation/alpine_linux_en.md
+++ b/docs/installation/alpine_linux_en.md
@@ -117,7 +117,7 @@ cd /opt/pleroma
 sudo -Hu pleroma mix deps.get
 ```
 
-* Generate the configuration: `sudo -Hu pleroma mix pleroma.instance gen`
+* Generate the configuration: `sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen`
   * Answer with `yes` if it asks you to install `rebar3`.
   * This may take some time, because parts of pleroma get compiled first.
   * After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
diff --git a/docs/installation/arch_linux_en.md b/docs/installation/arch_linux_en.md
index da78c3205..53afccc0f 100644
--- a/docs/installation/arch_linux_en.md
+++ b/docs/installation/arch_linux_en.md
@@ -92,7 +92,7 @@ cd /opt/pleroma
 sudo -Hu pleroma mix deps.get
 ```
 
-* Generate the configuration: `sudo -Hu pleroma mix pleroma.instance gen`
+* Generate the configuration: `sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen`
   * Answer with `yes` if it asks you to install `rebar3`.
   * This may take some time, because parts of pleroma get compiled first.
   * After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
diff --git a/docs/installation/debian_based_en.md b/docs/installation/debian_based_en.md
index c5687a01e..a9cf86ab3 100644
--- a/docs/installation/debian_based_en.md
+++ b/docs/installation/debian_based_en.md
@@ -90,7 +90,7 @@ cd /opt/pleroma
 sudo -Hu pleroma mix deps.get
 ```
 
-* Generate the configuration: `sudo -Hu pleroma mix pleroma.instance gen`
+* Generate the configuration: `sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen`
   * Answer with `yes` if it asks you to install `rebar3`.
   * This may take some time, because parts of pleroma get compiled first.
   * After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
diff --git a/docs/installation/debian_based_jp.md b/docs/installation/debian_based_jp.md
index c4bbd4780..e076e2308 100644
--- a/docs/installation/debian_based_jp.md
+++ b/docs/installation/debian_based_jp.md
@@ -89,7 +89,7 @@ sudo -Hu pleroma mix deps.get
 
 * コンフィギュレーションを生成します。
 ```
-sudo -Hu pleroma mix pleroma.instance gen
+sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen
 ```
     * rebar3をインストールしてもよいか聞かれたら、yesを入力してください。
     * このときにpleromaの一部がコンパイルされるため、この処理には時間がかかります。
@@ -103,7 +103,7 @@ sudo -Hu pleroma mv config/{generated_config.exs,prod.secret.exs}
 
 * 先程のコマンドで、すでに `config/setup_db.psql` というファイルが作られています。このファイルをもとに、データベースを作成します。
 ```
-sudo -Hu pleroma mix pleroma.instance gen
+sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen
 ```
 
 * そして、データベースのマイグレーションを実行します。
diff --git a/docs/installation/freebsd_en.md b/docs/installation/freebsd_en.md
index 2dc466eb8..f4f4d0db9 100644
--- a/docs/installation/freebsd_en.md
+++ b/docs/installation/freebsd_en.md
@@ -1,8 +1,8 @@
-# Installing on FreeBSD 
+# Installing on FreeBSD
 
 This document was written for FreeBSD 12.1, but should be work on future releases.
 
-## Required software 
+## Required software
 
 This assumes the target system has `pkg(8)`.
 
@@ -54,7 +54,7 @@ Configure Pleroma. Note that you need a domain name at this point:
 ```
 $ cd /home/pleroma/pleroma
 $ mix deps.get # Enter "y" when asked to install Hex
-$ mix pleroma.instance gen # You will be asked a few questions here.
+$ MIX_ENV=prod mix pleroma.instance gen # You will be asked a few questions here.
 $ cp config/generated_config.exs config/prod.secret.exs
 ```
 
diff --git a/docs/installation/gentoo_en.md b/docs/installation/gentoo_en.md
index f2380ab72..af68db70d 100644
--- a/docs/installation/gentoo_en.md
+++ b/docs/installation/gentoo_en.md
@@ -54,7 +54,7 @@ Gentoo quite pointedly does not come with a cron daemon installed, and as such i
  # emerge --ask dev-db/postgresql dev-lang/elixir dev-vcs/git www-servers/nginx app-crypt/certbot app-crypt/certbot-nginx dev-util/cmake sys-apps/file
 ```
 
-If you would not like to install the optional packages, remove them from this line. 
+If you would not like to install the optional packages, remove them from this line.
 
 If you're running this from a low-powered virtual machine, it should work though it will take some time. There were no issues on a VPS with a single core and 1GB of RAM; if you are using an even more limited device and run into issues, you can try creating a swapfile or use a more powerful machine running Gentoo to [cross build](https://wiki.gentoo.org/wiki/Cross_build_environment). If you have a wait ahead of you, now would be a good time to take a break, strech a bit, refresh your beverage of choice and/or get a snack, and reply to Arch users' posts with "I use Gentoo btw" as we do.
 
@@ -79,12 +79,12 @@ The output from emerging postgresql should give you a command for initializing t
 ```
 
 * Start postgres and enable the system service
- 
+
 ```shell
  # /etc/init.d/postgresql-11 start
  # rc-update add postgresql-11 default
  ```
- 
+
 ### A note on licenses, the AGPL, and deployment procedures
 
 If you do not plan to make any modifications to your Pleroma instance, cloning directly from the main repo will get you what you need. However, if you plan on doing any contributions to upstream development, making changes or modifications to your instance, making custom themes, or want to play around--and let's be honest here, if you're using Gentoo that is most likely you--you will save yourself a lot of headache later if you take the time right now to fork the Pleroma repo and use that in the following section.
@@ -135,7 +135,7 @@ pleroma$ mix deps.get
 * Generate the configuration:
 
 ```shell
-pleroma$ mix pleroma.instance gen
+pleroma$ MIX_ENV=prod mix pleroma.instance gen
 ```
 
   * Answer with `yes` if it asks you to install `rebar3`.
@@ -241,7 +241,7 @@ First, ensure that the command you will be installing into your crontab works.
  # /usr/bin/certbot renew --nginx
 ```
 
-Assuming not much time has passed since you got certbot working a few steps ago, you should get a message for all domains you installed certificates for saying `Cert not yet due for renewal`. 
+Assuming not much time has passed since you got certbot working a few steps ago, you should get a message for all domains you installed certificates for saying `Cert not yet due for renewal`.
 
 Now, run crontab as a superuser with `crontab -e` or `sudo crontab -e` as appropriate, and add the following line to your cron:
 
diff --git a/docs/installation/netbsd_en.md b/docs/installation/netbsd_en.md
index 233cf28b7..22cdd5691 100644
--- a/docs/installation/netbsd_en.md
+++ b/docs/installation/netbsd_en.md
@@ -1,6 +1,6 @@
 # Installing on NetBSD
 
-## Required software 
+## Required software
 
 pkgin should have been installed by the NetBSD installer if you selected
 the right options. If it isn't installed, install it using pkg_add.
@@ -71,7 +71,7 @@ Configure Pleroma. Note that you need a domain name at this point:
 ```
 $ cd /home/pleroma/pleroma
 $ mix deps.get
-$ mix pleroma.instance gen # You will be asked a few questions here.
+$ MIX_ENV=prod mix pleroma.instance gen # You will be asked a few questions here.
 ```
 
 Since Postgres is configured, we can now initialize the database. There should
diff --git a/docs/installation/openbsd_en.md b/docs/installation/openbsd_en.md
index 0e1269ca5..017b37519 100644
--- a/docs/installation/openbsd_en.md
+++ b/docs/installation/openbsd_en.md
@@ -239,7 +239,7 @@ Enter a shell as \_pleroma (as root `su _pleroma -`) and enter pleroma's install
 Then follow the main installation guide:
 
   * run `mix deps.get`
-  * run `mix pleroma.instance gen` and enter your instance's information when asked
+  * run `MIX_ENV=prod mix pleroma.instance gen` and enter your instance's information when asked
   * copy config/generated\_config.exs to config/prod.secret.exs. The default values should be sufficient but you should edit it and check that everything seems OK.
   * exit your current shell back to a root one and run `psql -U postgres -f /home/_pleroma/pleroma/config/setup_db.psql` to setup the database.
   * return to a \_pleroma shell into pleroma's installation directory (`su _pleroma -;cd ~/pleroma`) and run `MIX_ENV=prod mix ecto.migrate`

From 230ad82dadf013cb56909d1e8df2a2d652c47068 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Sun, 16 May 2021 13:22:07 -0500
Subject: [PATCH 205/339] gitignore `config/runtime.exs`

---
 .gitignore | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.gitignore b/.gitignore
index f30f4cf5f..da73b6f36 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,6 +28,7 @@ erl_crash.dump
 # variables.
 /config/*.secret.exs
 /config/generated_config.exs
+/config/runtime.exs
 /config/*.env
 
 
@@ -56,4 +57,4 @@ pleroma.iml
 
 # Editor temp files
 /*~
-/*#
\ No newline at end of file
+/*#

From 9b6b5ac196d9a2defb74902bffad67505b0de5c5 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 18 May 2021 15:33:33 -0500
Subject: [PATCH 206/339] Rename upload filter to AnalyzeMetadata

---
 CHANGELOG.md                                                | 2 +-
 .../upload/filter/{set_meta.ex => analyze_metadata.ex}      | 2 +-
 .../filter/{set_meta_test.exs => analyze_metadata_test.exs} | 6 +++---
 3 files changed, 5 insertions(+), 5 deletions(-)
 rename lib/pleroma/upload/filter/{set_meta.ex => analyze_metadata.ex} (95%)
 rename test/pleroma/upload/filter/{set_meta_test.exs => analyze_metadata_test.exs} (70%)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 22eaa0b94..1a69414a5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,7 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 - MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded.
 - Return OAuth token `id` (primary key) in POST `/oauth/token`.
-- `SetMeta` upload filter for extracting attachment dimensions.
+- `AnalyzeMetadata` upload filter for extracting attachment dimensions.
 - Attachment dimensions are federated when available.
 
 ### Fixed
diff --git a/lib/pleroma/upload/filter/set_meta.ex b/lib/pleroma/upload/filter/analyze_metadata.ex
similarity index 95%
rename from lib/pleroma/upload/filter/set_meta.ex
rename to lib/pleroma/upload/filter/analyze_metadata.ex
index 81c48228a..8c23076d4 100644
--- a/lib/pleroma/upload/filter/set_meta.ex
+++ b/lib/pleroma/upload/filter/analyze_metadata.ex
@@ -2,7 +2,7 @@
 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.Upload.Filter.SetMeta do
+defmodule Pleroma.Upload.Filter.AnalyzeMetadata do
   @moduledoc """
   Extracts metadata about the upload, such as width/height
   """
diff --git a/test/pleroma/upload/filter/set_meta_test.exs b/test/pleroma/upload/filter/analyze_metadata_test.exs
similarity index 70%
rename from test/pleroma/upload/filter/set_meta_test.exs
rename to test/pleroma/upload/filter/analyze_metadata_test.exs
index 650e527b4..6f0e432ef 100644
--- a/test/pleroma/upload/filter/set_meta_test.exs
+++ b/test/pleroma/upload/filter/analyze_metadata_test.exs
@@ -2,9 +2,9 @@
 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.Upload.Filter.SetMetaTest do
+defmodule Pleroma.Upload.Filter.AnalyzeMetadataTest do
   use Pleroma.DataCase, async: true
-  alias Pleroma.Upload.Filter.SetMeta
+  alias Pleroma.Upload.Filter.AnalyzeMetadata
 
   test "adds the image dimensions" do
     upload = %Pleroma.Upload{
@@ -14,6 +14,6 @@ test "adds the image dimensions" do
       tempfile: Path.absname("test/fixtures/image.jpg")
     }
 
-    assert {:ok, :filtered, %{width: 1024, height: 768}} = SetMeta.filter(upload)
+    assert {:ok, :filtered, %{width: 1024, height: 768}} = AnalyzeMetadata.filter(upload)
   end
 end

From 4ab3ef07d0f10815e7a91ba3143b7f97cd2a6058 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 18 May 2021 15:51:11 -0500
Subject: [PATCH 207/339] Check AnalyzeMetadata filter's required commands

eblurhash:magick uses "convert"
Fetching image metadata uses "mogrify"
---
 lib/pleroma/application_requirements.ex | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex
index c412dec5e..294eb3b6b 100644
--- a/lib/pleroma/application_requirements.ex
+++ b/lib/pleroma/application_requirements.ex
@@ -166,7 +166,9 @@ defp check_system_commands!(:ok) do
     filter_commands_statuses = [
       check_filter(Pleroma.Upload.Filters.Exiftool, "exiftool"),
       check_filter(Pleroma.Upload.Filters.Mogrify, "mogrify"),
-      check_filter(Pleroma.Upload.Filters.Mogrifun, "mogrify")
+      check_filter(Pleroma.Upload.Filters.Mogrifun, "mogrify"),
+      check_filter(Pleroma.Upload.Filters.AnalyzeMetadata, "mogrify"),
+      check_filter(Pleroma.Upload.Filters.AnalyzeMetadata, "convert")
     ]
 
     preview_proxy_commands_status =

From c64cbee26c7b78f9743b668724d4797faa6a942a Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 18 May 2021 16:28:21 -0500
Subject: [PATCH 208/339] Fixed checking for Upload Filter required commands

---
 CHANGELOG.md                            |  1 +
 lib/pleroma/application_requirements.ex | 10 +++++-----
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a69414a5..768405dd6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ### Fixed
 - Don't crash so hard when email settings are invalid.
+- Checking activated Upload Filters for required commands.
 
 ## Unreleased (Patch)
 
diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex
index 294eb3b6b..ee6ee9516 100644
--- a/lib/pleroma/application_requirements.ex
+++ b/lib/pleroma/application_requirements.ex
@@ -164,11 +164,11 @@ defp do_check_rum!(setting, migrate) do
 
   defp check_system_commands!(:ok) do
     filter_commands_statuses = [
-      check_filter(Pleroma.Upload.Filters.Exiftool, "exiftool"),
-      check_filter(Pleroma.Upload.Filters.Mogrify, "mogrify"),
-      check_filter(Pleroma.Upload.Filters.Mogrifun, "mogrify"),
-      check_filter(Pleroma.Upload.Filters.AnalyzeMetadata, "mogrify"),
-      check_filter(Pleroma.Upload.Filters.AnalyzeMetadata, "convert")
+      check_filter(Pleroma.Upload.Filter.Exiftool, "exiftool"),
+      check_filter(Pleroma.Upload.Filter.Mogrify, "mogrify"),
+      check_filter(Pleroma.Upload.Filter.Mogrifun, "mogrify"),
+      check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "mogrify"),
+      check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "convert")
     ]
 
     preview_proxy_commands_status =

From 2d7f6ce6fb047872083c2db6ad8b75a9032211fd Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 18 May 2021 16:46:51 -0500
Subject: [PATCH 209/339] Clarify AttachmentMetadata changes

---
 CHANGELOG.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 768405dd6..898f8adb5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,8 +15,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 - MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded.
 - Return OAuth token `id` (primary key) in POST `/oauth/token`.
-- `AnalyzeMetadata` upload filter for extracting attachment dimensions.
-- Attachment dimensions are federated when available.
+- `AnalyzeMetadata` upload filter for extracting attachment dimensions and generating blurhashes.
+- Attachment dimensions and blurhashes are federated when available.
 
 ### Fixed
 - Don't crash so hard when email settings are invalid.

From 07fed0fda2473fc4e1e3b01e863217391fd2902f Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 18 May 2021 17:11:25 -0500
Subject: [PATCH 210/339] Switch to aliasing `Router.Helpers` instead of
 importing

---
 lib/pleroma/web.ex                                          | 6 ++++--
 lib/pleroma/web/feed/user_controller.ex                     | 2 +-
 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex | 4 ++--
 lib/pleroma/web/o_auth/o_auth_controller.ex                 | 4 ++--
 lib/pleroma/web/templates/feed/feed/tag.atom.eex            | 4 ++--
 lib/pleroma/web/templates/feed/feed/tag.rss.eex             | 2 +-
 lib/pleroma/web/templates/feed/feed/user.atom.eex           | 6 +++---
 lib/pleroma/web/templates/feed/feed/user.rss.eex            | 6 +++---
 lib/pleroma/web/templates/masto_fe/index.html.eex           | 2 +-
 lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex      | 4 ++--
 lib/pleroma/web/templates/o_auth/mfa/totp.html.eex          | 4 ++--
 lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex   | 2 +-
 lib/pleroma/web/templates/o_auth/o_auth/register.html.eex   | 2 +-
 lib/pleroma/web/templates/o_auth/o_auth/show.html.eex       | 2 +-
 .../web/templates/twitter_api/password/reset.html.eex       | 2 +-
 .../web/templates/twitter_api/remote_follow/follow.html.eex | 2 +-
 .../twitter_api/remote_follow/follow_login.html.eex         | 2 +-
 .../templates/twitter_api/remote_follow/follow_mfa.html.eex | 2 +-
 .../web/templates/twitter_api/util/subscribe.html.eex       | 2 +-
 .../web/twitter_api/controllers/remote_follow_controller.ex | 2 +-
 lib/pleroma/web/views/masto_fe_view.ex                      | 2 +-
 21 files changed, 33 insertions(+), 31 deletions(-)

diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex
index 8630f244b..24751faba 100644
--- a/lib/pleroma/web.ex
+++ b/lib/pleroma/web.ex
@@ -35,9 +35,10 @@ def controller do
       import Plug.Conn
 
       import Pleroma.Web.Gettext
-      import Pleroma.Web.Router.Helpers
       import Pleroma.Web.TranslationHelpers
 
+      alias Pleroma.Web.Router.Helpers, as: Routes
+
       plug(:set_put_layout)
 
       defp set_put_layout(conn, _) do
@@ -131,7 +132,8 @@ def view do
 
       import Pleroma.Web.ErrorHelpers
       import Pleroma.Web.Gettext
-      import Pleroma.Web.Router.Helpers
+
+      alias Pleroma.Web.Router.Helpers, as: Routes
 
       require Logger
 
diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex
index 58d35da1e..fa7879caf 100644
--- a/lib/pleroma/web/feed/user_controller.ex
+++ b/lib/pleroma/web/feed/user_controller.ex
@@ -28,7 +28,7 @@ def feed_redirect(%{assigns: %{format: format}} = conn, _params)
 
   def feed_redirect(conn, %{"nickname" => nickname}) do
     with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
-      redirect(conn, external: "#{user_feed_url(conn, :feed, user.nickname)}.atom")
+      redirect(conn, external: "#{Routes.user_feed_url(conn, :feed, user.nickname)}.atom")
     end
   end
 
diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
index eb6639fc5..4920d65da 100644
--- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
@@ -53,7 +53,7 @@ def login(conn, params) do
   defp redirect_to_oauth_form(conn, _params) do
     with {:ok, app} <- local_mastofe_app() do
       path =
-        o_auth_path(conn, :authorize,
+        Routes.o_auth_path(conn, :authorize,
           response_type: "code",
           client_id: app.client_id,
           redirect_uri: ".",
@@ -90,7 +90,7 @@ def password_reset(conn, params) do
   defp local_mastodon_post_login_path(conn) do
     case get_session(conn, :return_to) do
       nil ->
-        masto_fe_path(conn, :index, ["getting-started"])
+        Routes.masto_fe_path(conn, :index, ["getting-started"])
 
       return_to ->
         delete_session(conn, :return_to)
diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex
index 215d97b3a..42f4d768f 100644
--- a/lib/pleroma/web/o_auth/o_auth_controller.ex
+++ b/lib/pleroma/web/o_auth/o_auth_controller.ex
@@ -427,7 +427,7 @@ def prepare_request(%Plug.Conn{} = conn, %{
       |> Map.put("state", state)
 
     # Handing the request to Ueberauth
-    redirect(conn, to: o_auth_path(conn, :request, provider, params))
+    redirect(conn, to: Routes.o_auth_path(conn, :request, provider, params))
   end
 
   def request(%Plug.Conn{} = conn, params) do
@@ -601,7 +601,7 @@ def login(%User{} = user, %App{} = app, requested_scopes) when is_list(requested
   end
 
   # Special case: Local MastodonFE
-  defp redirect_uri(%Plug.Conn{} = conn, "."), do: auth_url(conn, :login)
+  defp redirect_uri(%Plug.Conn{} = conn, "."), do: Routes.auth_url(conn, :login)
 
   defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri
 
diff --git a/lib/pleroma/web/templates/feed/feed/tag.atom.eex b/lib/pleroma/web/templates/feed/feed/tag.atom.eex
index a288539ed..de0731085 100644
--- a/lib/pleroma/web/templates/feed/feed/tag.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/tag.atom.eex
@@ -9,13 +9,13 @@
       xmlns:ostatus="http://ostatus.org/schema/1.0"
       xmlns:statusnet="http://status.net/schema/api/1/">
 
-    <id><%= '#{tag_feed_url(@conn, :feed, @tag)}.rss' %></id>
+    <id><%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %></id>
     <title>#<%= @tag %></title>
 
     <subtitle>These are public toots tagged with #<%= @tag %>. You can interact with them if you have an account anywhere in the fediverse.</subtitle>
     <logo><%= feed_logo() %></logo>
     <updated><%= most_recent_update(@activities) %></updated>
-    <link rel="self" href="<%= '#{tag_feed_url(@conn, :feed, @tag)}.atom'  %>" type="application/atom+xml"/>
+    <link rel="self" href="<%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.atom'  %>" type="application/atom+xml"/>
     <%= for activity <- @activities do %>
     <%= render @view_module, "_tag_activity.atom", Map.merge(assigns, prepare_activity(activity, actor: true)) %>
     <% end %>
diff --git a/lib/pleroma/web/templates/feed/feed/tag.rss.eex b/lib/pleroma/web/templates/feed/feed/tag.rss.eex
index eeda01a04..9c3613feb 100644
--- a/lib/pleroma/web/templates/feed/feed/tag.rss.eex
+++ b/lib/pleroma/web/templates/feed/feed/tag.rss.eex
@@ -5,7 +5,7 @@
 
     <title>#<%= @tag %></title>
     <description>These are public toots tagged with #<%= @tag %>. You can interact with them if you have an account anywhere in the fediverse.</description>
-    <link><%= '#{tag_feed_url(@conn, :feed, @tag)}.rss' %></link>
+    <link><%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %></link>
     <webfeeds:logo><%= feed_logo() %></webfeeds:logo>
     <webfeeds:accentColor>2b90d9</webfeeds:accentColor>
     <%= for activity <- @activities do %>
diff --git a/lib/pleroma/web/templates/feed/feed/user.atom.eex b/lib/pleroma/web/templates/feed/feed/user.atom.eex
index c6acd848f..5c1f0ecbc 100644
--- a/lib/pleroma/web/templates/feed/feed/user.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/user.atom.eex
@@ -6,16 +6,16 @@
   xmlns:poco="http://portablecontacts.net/spec/1.0"
   xmlns:ostatus="http://ostatus.org/schema/1.0">
 
-  <id><%= user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id>
+  <id><%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id>
   <title><%= @user.nickname <> "'s timeline" %></title>
   <updated><%= most_recent_update(@activities, @user) %></updated>
   <logo><%= logo(@user) %></logo>
-  <link rel="self" href="<%= '#{user_feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/>
+  <link rel="self" href="<%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/>
 
   <%= render @view_module, "_author.atom", assigns %>
 
   <%= if last_activity(@activities) do %>
-    <link rel="next" href="<%= '#{user_feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/>
+    <link rel="next" href="<%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/>
   <% end %>
 
   <%= for activity <- @activities do %>
diff --git a/lib/pleroma/web/templates/feed/feed/user.rss.eex b/lib/pleroma/web/templates/feed/feed/user.rss.eex
index d69120480..6b842a085 100644
--- a/lib/pleroma/web/templates/feed/feed/user.rss.eex
+++ b/lib/pleroma/web/templates/feed/feed/user.rss.eex
@@ -1,16 +1,16 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <rss version="2.0">
   <channel>
-    <guid><%= user_feed_url(@conn, :feed, @user.nickname) <> ".rss" %></guid>
+    <guid><%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".rss" %></guid>
     <title><%= @user.nickname <> "'s timeline" %></title>
     <updated><%= most_recent_update(@activities, @user) %></updated>
     <image><%= logo(@user) %></image>
-    <link><%= '#{user_feed_url(@conn, :feed, @user.nickname)}.rss' %></link>
+    <link><%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss' %></link>
 
     <%= render @view_module, "_author.rss", assigns %>
 
     <%= if last_activity(@activities) do %>
-      <link rel="next"><%= '#{user_feed_url(@conn, :feed, @user.nickname)}.rss?max_id=#{last_activity(@activities).id}' %></link>
+      <link rel="next"><%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss?max_id=#{last_activity(@activities).id}' %></link>
     <% end %>
 
     <%= for activity <- @activities do %>
diff --git a/lib/pleroma/web/templates/masto_fe/index.html.eex b/lib/pleroma/web/templates/masto_fe/index.html.eex
index c330960fa..6f2b98957 100644
--- a/lib/pleroma/web/templates/masto_fe/index.html.eex
+++ b/lib/pleroma/web/templates/masto_fe/index.html.eex
@@ -7,7 +7,7 @@
 <%= Config.get([:instance, :name]) %>
 </title>
 <link rel="icon" type="image/png" href="/favicon.png"/>
-<link rel="manifest" type="applicaton/manifest+json" href="<%= masto_fe_path(Pleroma.Web.Endpoint, :manifest) %>" />
+<link rel="manifest" type="applicaton/manifest+json" href="<%= Routes.masto_fe_path(Pleroma.Web.Endpoint, :manifest) %>" />
 
 <meta name="theme-color" content="<%= Config.get([:manifest, :theme_color]) %>" />
 
diff --git a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
index 5ab59b57b..b9daa8d8b 100644
--- a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
+++ b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
@@ -7,7 +7,7 @@
 
 <h2>Two-factor recovery</h2>
 
-<%= form_for @conn, mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
+<%= form_for @conn, Routes.mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
 <div class="input">
   <%= label f, :code, "Recovery code" %>
   <%= text_input f, :code, [autocomplete: false, autocorrect: "off", autocapitalize: "off", autofocus: true, spellcheck: false] %>
@@ -19,6 +19,6 @@
 
 <%= submit "Verify" %>
 <% end %>
-<a href="<%= mfa_path(@conn, :show, %{challenge_type: "totp", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
+<a href="<%= Routes.mfa_path(@conn, :show, %{challenge_type: "totp", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
   Enter a two-factor code
 </a>
diff --git a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
index af85777eb..29ea7c5fb 100644
--- a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
+++ b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
@@ -7,7 +7,7 @@
 
 <h2>Two-factor authentication</h2>
 
-<%= form_for @conn, mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
+<%= form_for @conn, Routes.mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
 <div class="input">
   <%= label f, :code, "Authentication code" %>
   <%= text_input f, :code, [autocomplete: false, autocorrect: "off", autocapitalize: "off", autofocus: true, pattern: "[0-9]*", spellcheck: false] %>
@@ -19,6 +19,6 @@
 
 <%= submit "Verify" %>
 <% end %>
-<a href="<%= mfa_path(@conn, :show, %{challenge_type: "recovery", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
+<a href="<%= Routes.mfa_path(@conn, :show, %{challenge_type: "recovery", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
   Enter a two-factor recovery code
 </a>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
index 4a0718851..dc4521a62 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
@@ -1,6 +1,6 @@
 <h2>Sign in with external provider</h2>
 
-<%= form_for @conn, o_auth_path(@conn, :prepare_request), [as: "authorization", method: "get"], fn f -> %>
+<%= form_for @conn, Routes.o_auth_path(@conn, :prepare_request), [as: "authorization", method: "get"], fn f -> %>
   <div style="display: none">
     <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
   </div>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
index facedc8db..99f900fb7 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
@@ -8,7 +8,7 @@
 <h2>Registration Details</h2>
 
 <p>If you'd like to register a new account, please provide the details below.</p>
-<%= form_for @conn, o_auth_path(@conn, :register), [as: "authorization"], fn f -> %>
+<%= form_for @conn, Routes.o_auth_path(@conn, :register), [as: "authorization"], fn f -> %>
 
 <div class="input">
   <%= label f, :nickname, "Nickname" %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
index 1a85818ec..2846ec7e7 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
@@ -5,7 +5,7 @@
 <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
 <% end %>
 
-<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
+<%= form_for @conn, Routes.o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
 
 <%= if @user do %>
   <div class="account-header">
diff --git a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex
index 7d3ef6b0d..fbcacdc14 100644
--- a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex
@@ -1,5 +1,5 @@
 <h2>Password Reset for <%= @user.nickname %></h2>
-<%= form_for @conn, reset_password_path(@conn, :do_reset), [as: "data"], fn f -> %>
+<%= form_for @conn, Routes.reset_password_path(@conn, :do_reset), [as: "data"], fn f -> %>
   <div class="form-row">
     <%= label f, :password, "Password" %>
     <%= password_input f, :password %>
diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
index 5ba192cd7..a7be53091 100644
--- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
@@ -4,7 +4,7 @@
     <h2>Remote follow</h2>
     <img height="128" width="128" src="<%= avatar_url(@followee) %>">
     <p><%= @followee.nickname %></p>
-    <%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %>
+    <%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %>
     <%= hidden_input f, :id, value: @followee.id %>
     <%= submit "Authorize" %>
     <% end %>
diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
index df44988ee..a8026fa9d 100644
--- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
@@ -4,7 +4,7 @@
 <h2>Log in to follow</h2>
 <p><%= @followee.nickname %></p>
 <img height="128" width="128" src="<%= avatar_url(@followee) %>">
-<%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %>
+<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %>
 <%= text_input f, :name, placeholder: "Username", required: true %>
 <br>
 <%= password_input f, :password, placeholder: "Password", required: true %>
diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex
index adc3a3e3d..a54ed83b5 100644
--- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex
@@ -4,7 +4,7 @@
 <h2>Two-factor authentication</h2>
 <p><%= @followee.nickname %></p>
 <img height="128" width="128" src="<%= avatar_url(@followee) %>">
-<%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "mfa"], fn f -> %>
+<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "mfa"], fn f -> %>
 <%= text_input f, :code, placeholder: "Authentication code", required: true %>
 <br>
 <%= hidden_input f, :id, value: @followee.id %>
diff --git a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex b/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex
index f60accebf..a6b313d8a 100644
--- a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex
@@ -2,7 +2,7 @@
   <h2>Error: <%= @error %></h2>
 <% else %>
   <h2>Remotely follow <%= @nickname %></h2>
-  <%= form_for @conn, util_path(@conn, :remote_subscribe), [as: "user"], fn f -> %>
+  <%= form_for @conn, Routes.util_path(@conn, :remote_subscribe), [as: "user"], fn f -> %>
   <%= hidden_input f, :nickname, value: @nickname %>
   <%= text_input f, :profile, placeholder: "Your account ID, e.g. lain@quitter.se" %>
   <%= submit "Follow" %>
diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
index 6ca02fbd7..9843cc362 100644
--- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
@@ -38,7 +38,7 @@ def follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
   defp follow_status(conn, _user, acct) do
     with {:ok, object} <- Fetcher.fetch_object_from_id(acct),
          %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object.data["id"]) do
-      redirect(conn, to: o_status_path(conn, :notice, activity_id))
+      redirect(conn, to: Routes.o_status_path(conn, :notice, activity_id))
     else
       error ->
         handle_follow_error(conn, error)
diff --git a/lib/pleroma/web/views/masto_fe_view.ex b/lib/pleroma/web/views/masto_fe_view.ex
index b9055cb7f..82b301949 100644
--- a/lib/pleroma/web/views/masto_fe_view.ex
+++ b/lib/pleroma/web/views/masto_fe_view.ex
@@ -79,7 +79,7 @@ def render("manifest.json", _params) do
       background_color: Config.get([:manifest, :background_color]),
       display: "standalone",
       scope: Pleroma.Web.base_url(),
-      start_url: masto_fe_path(Pleroma.Web.Endpoint, :index, ["getting-started"]),
+      start_url: Routes.masto_fe_path(Pleroma.Web.Endpoint, :index, ["getting-started"]),
       categories: [
         "social"
       ],

From e3173a279dad89dfce6eae89368ad3ba180c0490 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Wed, 19 May 2021 14:27:02 -0500
Subject: [PATCH 211/339] Put Plugs in runtime mode in :dev, :test to speed up
 recompilation

---
 config/dev.exs  | 4 ++++
 config/test.exs | 4 ++++
 2 files changed, 8 insertions(+)

diff --git a/config/dev.exs b/config/dev.exs
index 4faaeff5b..8e7d5c587 100644
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -54,6 +54,10 @@
 
 config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true
 
+# Reduce recompilation time
+# https://dashbit.co/blog/speeding-up-re-compilation-of-elixir-projects
+config :phoenix, :plug_init_mode, :runtime
+
 if File.exists?("./config/dev.secret.exs") do
   import_config "dev.secret.exs"
 else
diff --git a/config/test.exs b/config/test.exs
index 87396a88d..007951097 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -133,6 +133,10 @@
   ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock,
   logger: Pleroma.LoggerMock
 
+# Reduce recompilation time
+# https://dashbit.co/blog/speeding-up-re-compilation-of-elixir-projects
+config :phoenix, :plug_init_mode, :runtime
+
 if File.exists?("./config/test.secret.exs") do
   import_config "test.secret.exs"
 else

From 05d678c070b47848c400103a029f6ed278bce9e6 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Thu, 20 May 2021 12:50:43 -0500
Subject: [PATCH 212/339] Expose user email address to user/owner; not
 publicly.

---
 CHANGELOG.md                                    |  1 +
 .../web/mastodon_api/views/account_view.ex      | 11 +++++++++++
 .../mastodon_api/views/account_view_test.exs    | 17 +++++++++++++++++
 3 files changed, 29 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 898f8adb5..61339a1aa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 - The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
 - HTTPSecurityPlug now sends a response header to opt out of Google's FLoC (Federated Learning of Cohorts) targeted advertising.
+- Email address is now returned if requesting user is the owner of the user account so it can be exposed in client and FE user settings UIs.
 
 ### Added
 
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index ac25aefdd..9e9de33f6 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -292,6 +292,7 @@ defp do_render("show.json", %{user: user} = opts) do
     |> maybe_put_allow_following_move(user, opts[:for])
     |> maybe_put_unread_conversation_count(user, opts[:for])
     |> maybe_put_unread_notification_count(user, opts[:for])
+    |> maybe_put_email_address(user, opts[:for])
   end
 
   defp username_from_nickname(string) when is_binary(string) do
@@ -403,6 +404,16 @@ defp maybe_put_unread_notification_count(data, %User{id: user_id}, %User{id: use
 
   defp maybe_put_unread_notification_count(data, _, _), do: data
 
+  defp maybe_put_email_address(data, %User{id: user_id}, %User{id: user_id} = user) do
+    Kernel.put_in(
+      data,
+      [:pleroma, :email],
+      user.email
+    )
+  end
+
+  defp maybe_put_email_address(data, _, _), do: data
+
   defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
   defp image_url(_), do: nil
 end
diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs
index 5373a17c3..3fa17a6ca 100644
--- a/test/pleroma/web/mastodon_api/views/account_view_test.exs
+++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs
@@ -468,6 +468,23 @@ test "shows unread_count only to the account owner" do
                %{user: user, for: user}
              )[:pleroma][:unread_notifications_count] == 7
     end
+
+    test "shows email only to the account owner" do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      user = User.get_cached_by_ap_id(user.ap_id)
+
+      assert AccountView.render(
+               "show.json",
+               %{user: user, for: other_user}
+             )[:pleroma][:email] == nil
+
+      assert AccountView.render(
+               "show.json",
+               %{user: user, for: user}
+             )[:pleroma][:email] == user.email
+    end
   end
 
   describe "follow requests counter" do

From fe40f6f2910967609f7aa952d69981cadc47372c Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Thu, 20 May 2021 13:55:37 -0500
Subject: [PATCH 213/339] Switch from the deprecated "use Mix.config" to
 "import Config"

---
 config/benchmark.exs                                  | 2 +-
 config/config.exs                                     | 2 +-
 config/description.exs                                | 2 +-
 config/dev.exs                                        | 2 +-
 config/dokku.exs                                      | 2 +-
 config/prod.exs                                       | 2 +-
 config/test.exs                                       | 2 +-
 test/fixtures/config/temp.exported_from_db.secret.exs | 2 +-
 test/fixtures/config/temp.secret.exs                  | 2 +-
 test/mix/tasks/pleroma/config_test.exs                | 9 +--------
 10 files changed, 10 insertions(+), 17 deletions(-)

diff --git a/config/benchmark.exs b/config/benchmark.exs
index 5567ff26e..a4d048f1b 100644
--- a/config/benchmark.exs
+++ b/config/benchmark.exs
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
 
 # We don't run a server during test. If one is required,
 # you can enable the server option below.
diff --git a/config/config.exs b/config/config.exs
index 4381068ac..d333c618e 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -41,7 +41,7 @@
 #
 # This configuration file is loaded before any dependency and
 # is restricted to this project.
-use Mix.Config
+import Config
 
 # General application configuration
 config :pleroma, ecto_repos: [Pleroma.Repo]
diff --git a/config/description.exs b/config/description.exs
index bb1f43305..f00c53d28 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
 
 websocket_config = [
   path: "/websocket",
diff --git a/config/dev.exs b/config/dev.exs
index 4faaeff5b..cfe3cce47 100644
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
 
 # For development, we disable any cache and enable
 # debugging and code reloading.
diff --git a/config/dokku.exs b/config/dokku.exs
index 9ea0ec450..1cc396c3d 100644
--- a/config/dokku.exs
+++ b/config/dokku.exs
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
 
 config :pleroma, Pleroma.Web.Endpoint,
   http: [
diff --git a/config/prod.exs b/config/prod.exs
index 0e151000b..968f596e0 100644
--- a/config/prod.exs
+++ b/config/prod.exs
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
 
 # For production, we often load configuration from external
 # sources, such as your system environment. For this reason,
diff --git a/config/test.exs b/config/test.exs
index 87396a88d..c531b1290 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
 
 # We don't run a server during test. If one is required,
 # you can enable the server option below.
diff --git a/test/fixtures/config/temp.exported_from_db.secret.exs b/test/fixtures/config/temp.exported_from_db.secret.exs
index 64bee7f32..dda5d0fa6 100644
--- a/test/fixtures/config/temp.exported_from_db.secret.exs
+++ b/test/fixtures/config/temp.exported_from_db.secret.exs
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
 
 config :pleroma, exported_config_merged: true
 
diff --git a/test/fixtures/config/temp.secret.exs b/test/fixtures/config/temp.secret.exs
index 4b3af39ec..9c5c88d98 100644
--- a/test/fixtures/config/temp.secret.exs
+++ b/test/fixtures/config/temp.secret.exs
@@ -2,7 +2,7 @@
 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-use Mix.Config
+import Config
 
 config :pleroma, :first_setting, key: "value", key2: [Pleroma.Repo]
 
diff --git a/test/mix/tasks/pleroma/config_test.exs b/test/mix/tasks/pleroma/config_test.exs
index 3ed1e94b8..2b8252db7 100644
--- a/test/mix/tasks/pleroma/config_test.exs
+++ b/test/mix/tasks/pleroma/config_test.exs
@@ -188,15 +188,8 @@ test "load a settings with large values and pass to file", %{temp_file: temp_fil
       assert File.exists?(temp_file)
       {:ok, file} = File.read(temp_file)
 
-      header =
-        if Code.ensure_loaded?(Config.Reader) do
-          "import Config"
-        else
-          "use Mix.Config"
-        end
-
       assert file ==
-               "#{header}\n\nconfig :pleroma, :instance,\n  name: \"Pleroma\",\n  email: \"example@example.com\",\n  notify_email: \"noreply@example.com\",\n  description: \"A Pleroma instance, an alternative fediverse server\",\n  limit: 5000,\n  chat_limit: 5000,\n  remote_limit: 100_000,\n  upload_limit: 16_000_000,\n  avatar_upload_limit: 2_000_000,\n  background_upload_limit: 4_000_000,\n  banner_upload_limit: 4_000_000,\n  poll_limits: %{\n    max_expiration: 31_536_000,\n    max_option_chars: 200,\n    max_options: 20,\n    min_expiration: 0\n  },\n  registrations_open: true,\n  federating: true,\n  federation_incoming_replies_max_depth: 100,\n  federation_reachability_timeout_days: 7,\n  federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],\n  allow_relay: true,\n  public: true,\n  quarantined_instances: [],\n  managed_config: true,\n  static_dir: \"instance/static/\",\n  allowed_post_formats: [\"text/plain\", \"text/html\", \"text/markdown\", \"text/bbcode\"],\n  autofollowed_nicknames: [],\n  max_pinned_statuses: 1,\n  attachment_links: false,\n  max_report_comment_size: 1000,\n  safe_dm_mentions: false,\n  healthcheck: false,\n  remote_post_retention_days: 90,\n  skip_thread_containment: true,\n  limit_to_local_content: :unauthenticated,\n  user_bio_length: 5000,\n  user_name_length: 100,\n  max_account_fields: 10,\n  max_remote_account_fields: 20,\n  account_field_name_length: 512,\n  account_field_value_length: 2048,\n  external_user_synchronization: true,\n  extended_nickname_format: true,\n  multi_factor_authentication: [\n    totp: [digits: 6, period: 30],\n    backup_codes: [number: 2, length: 6]\n  ]\n"
+               "import Config\n\nconfig :pleroma, :instance,\n  name: \"Pleroma\",\n  email: \"example@example.com\",\n  notify_email: \"noreply@example.com\",\n  description: \"A Pleroma instance, an alternative fediverse server\",\n  limit: 5000,\n  chat_limit: 5000,\n  remote_limit: 100_000,\n  upload_limit: 16_000_000,\n  avatar_upload_limit: 2_000_000,\n  background_upload_limit: 4_000_000,\n  banner_upload_limit: 4_000_000,\n  poll_limits: %{\n    max_expiration: 31_536_000,\n    max_option_chars: 200,\n    max_options: 20,\n    min_expiration: 0\n  },\n  registrations_open: true,\n  federating: true,\n  federation_incoming_replies_max_depth: 100,\n  federation_reachability_timeout_days: 7,\n  federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],\n  allow_relay: true,\n  public: true,\n  quarantined_instances: [],\n  managed_config: true,\n  static_dir: \"instance/static/\",\n  allowed_post_formats: [\"text/plain\", \"text/html\", \"text/markdown\", \"text/bbcode\"],\n  autofollowed_nicknames: [],\n  max_pinned_statuses: 1,\n  attachment_links: false,\n  max_report_comment_size: 1000,\n  safe_dm_mentions: false,\n  healthcheck: false,\n  remote_post_retention_days: 90,\n  skip_thread_containment: true,\n  limit_to_local_content: :unauthenticated,\n  user_bio_length: 5000,\n  user_name_length: 100,\n  max_account_fields: 10,\n  max_remote_account_fields: 20,\n  account_field_name_length: 512,\n  account_field_value_length: 2048,\n  external_user_synchronization: true,\n  extended_nickname_format: true,\n  multi_factor_authentication: [\n    totp: [digits: 6, period: 30],\n    backup_codes: [number: 2, length: 6]\n  ]\n"
     end
   end
 

From 46948537664a4296e7d5c517cbdbf3adccef1272 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Thu, 27 May 2021 12:04:42 -0500
Subject: [PATCH 214/339] Provide totalItems field for featured collections

---
 lib/pleroma/web/activity_pub/views/user_view.ex                | 3 ++-
 test/pleroma/web/activity_pub/activity_pub_controller_test.exs | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 462f3b4a7..344da19d3 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -261,7 +261,8 @@ def render("featured.json", %{
     %{
       "id" => featured_address,
       "type" => "OrderedCollection",
-      "orderedItems" => objects
+      "orderedItems" => objects,
+      "totalItems" => length(objects)
     }
     |> Map.merge(Utils.make_json_ld_header())
   end
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index cea4b3a97..c1e13c7cb 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -1966,7 +1966,7 @@ test "pinned collection", %{conn: conn} do
     %{nickname: nickname, featured_address: featured_address, pinned_objects: pinned_objects} =
       refresh_record(user)
 
-    %{"id" => ^featured_address, "orderedItems" => items} =
+    %{"id" => ^featured_address, "orderedItems" => items, "totalItems" => 2} =
       conn
       |> get("/users/#{nickname}/collections/featured")
       |> json_response(200)

From cd4352a86fe37983cbc617c42f3f6c8a631fb9b3 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Thu, 27 May 2021 12:20:21 -0500
Subject: [PATCH 215/339] Missing entry for pinned posts federation from MR
 !3312

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 61339a1aa..eacba0208 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Return OAuth token `id` (primary key) in POST `/oauth/token`.
 - `AnalyzeMetadata` upload filter for extracting attachment dimensions and generating blurhashes.
 - Attachment dimensions and blurhashes are federated when available.
+- Pinned posts federation
 
 ### Fixed
 - Don't crash so hard when email settings are invalid.

From 21787546c01069d1d1d8261f0bc37d13a73122a9 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Sun, 23 May 2021 17:25:18 -0500
Subject: [PATCH 216/339] Router: move StaticFEPlug to a pipeline Speed up
 recompilation by breaking a cycle. Removes StaticFEPlug as a compile-time dep
 of Router.

---
 lib/pleroma/web/router.ex | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 72ad14f05..4e7de2b89 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -140,6 +140,10 @@ defmodule Pleroma.Web.Router do
     plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
   end
 
+  pipeline :static_fe do
+    plug(Pleroma.Web.Plugs.StaticFEPlug)
+  end
+
   scope "/api/v1/pleroma", Pleroma.Web.TwitterAPI do
     pipe_through(:pleroma_api)
 
@@ -631,7 +635,7 @@ defmodule Pleroma.Web.Router do
   scope "/", Pleroma.Web do
     # Note: html format is supported only if static FE is enabled
     # Note: http signature is only considered for json requests (no auth for non-json requests)
-    pipe_through([:accepts_html_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug])
+    pipe_through([:accepts_html_json, :http_signature, :static_fe])
 
     get("/objects/:uuid", OStatus.OStatusController, :object)
     get("/activities/:uuid", OStatus.OStatusController, :activity)
@@ -645,7 +649,7 @@ defmodule Pleroma.Web.Router do
   scope "/", Pleroma.Web do
     # Note: html format is supported only if static FE is enabled
     # Note: http signature is only considered for json requests (no auth for non-json requests)
-    pipe_through([:accepts_html_xml_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug])
+    pipe_through([:accepts_html_xml_json, :http_signature, :static_fe])
 
     # Note: returns user _profile_ for json requests, redirects to user _feed_ for non-json ones
     get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
@@ -653,7 +657,7 @@ defmodule Pleroma.Web.Router do
 
   scope "/", Pleroma.Web do
     # Note: html format is supported only if static FE is enabled
-    pipe_through([:accepts_html_xml, Pleroma.Web.Plugs.StaticFEPlug])
+    pipe_through([:accepts_html_xml, :static_fe])
 
     get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)
   end

From fda34591cefad94277385311c6391d1ca2adb36c Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Sat, 22 May 2021 15:04:05 -0500
Subject: [PATCH 217/339] Don't make MediaProxy be a compile-dep of Router
 Speeds up recompilation by removing MediaProxy as a compile-time dep of
 Router

---
 lib/pleroma/web/router.ex | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 72ad14f05..257455616 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -764,11 +764,11 @@ defmodule Pleroma.Web.Router do
     get("/embed/:id", EmbedController, :show)
   end
 
-  scope "/proxy/", Pleroma.Web.MediaProxy do
-    get("/preview/:sig/:url", MediaProxyController, :preview)
-    get("/preview/:sig/:url/:filename", MediaProxyController, :preview)
-    get("/:sig/:url", MediaProxyController, :remote)
-    get("/:sig/:url/:filename", MediaProxyController, :remote)
+  scope "/proxy/", Pleroma.Web do
+    get("/preview/:sig/:url", MediaProxy.MediaProxyController, :preview)
+    get("/preview/:sig/:url/:filename", MediaProxy.MediaProxyController, :preview)
+    get("/:sig/:url", MediaProxy.MediaProxyController, :remote)
+    get("/:sig/:url/:filename", MediaProxy.MediaProxyController, :remote)
   end
 
   if Pleroma.Config.get(:env) == :dev do

From c23b81e399d5be6fc30f4acb1d757d5eb291d8e1 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Fri, 21 May 2021 14:47:11 -0500
Subject: [PATCH 218/339] Pleroma.Web.get_api_routes/0 -->
 Pleroma.Web.Router.get_api_routes/0 Reduce recompilation time by breaking
 compile-time cycles

---
 lib/pleroma/web.ex                                   | 12 ------------
 lib/pleroma/web/plugs/frontend_static.ex             |  2 +-
 lib/pleroma/web/router.ex                            | 12 ++++++++++++
 test/pleroma/web/plugs/frontend_static_plug_test.exs |  2 +-
 4 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex
index 8630f244b..f1f9d6229 100644
--- a/lib/pleroma/web.ex
+++ b/lib/pleroma/web.ex
@@ -233,16 +233,4 @@ defmacro __using__(which) when is_atom(which) do
   def base_url do
     Pleroma.Web.Endpoint.url()
   end
-
-  # TODO: Change to Phoenix.Router.routes/1 for Phoenix 1.6.0+
-  def get_api_routes do
-    Pleroma.Web.Router.__routes__()
-    |> Enum.reject(fn r -> r.plug == Pleroma.Web.Fallback.RedirectController end)
-    |> Enum.map(fn r ->
-      r.path
-      |> String.split("/", trim: true)
-      |> List.first()
-    end)
-    |> Enum.uniq()
-  end
 end
diff --git a/lib/pleroma/web/plugs/frontend_static.ex b/lib/pleroma/web/plugs/frontend_static.ex
index eb385e94d..e7c943b41 100644
--- a/lib/pleroma/web/plugs/frontend_static.ex
+++ b/lib/pleroma/web/plugs/frontend_static.ex
@@ -10,7 +10,7 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do
   """
   @behaviour Plug
 
-  @api_routes Pleroma.Web.get_api_routes()
+  @api_routes Pleroma.Web.Router.get_api_routes()
 
   def file_path(path, frontend_type \\ :primary) do
     if configuration = Pleroma.Config.get([:frontends, frontend_type]) do
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 72ad14f05..3550088bb 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -821,4 +821,16 @@ defmodule Pleroma.Web.Router do
 
     options("/*path", RedirectController, :empty)
   end
+
+  # TODO: Change to Phoenix.Router.routes/1 for Phoenix 1.6.0+
+  def get_api_routes do
+    __MODULE__.__routes__()
+    |> Enum.reject(fn r -> r.plug == Pleroma.Web.Fallback.RedirectController end)
+    |> Enum.map(fn r ->
+      r.path
+      |> String.split("/", trim: true)
+      |> List.first()
+    end)
+    |> Enum.uniq()
+  end
 end
diff --git a/test/pleroma/web/plugs/frontend_static_plug_test.exs b/test/pleroma/web/plugs/frontend_static_plug_test.exs
index 100b83d6a..4152cdefe 100644
--- a/test/pleroma/web/plugs/frontend_static_plug_test.exs
+++ b/test/pleroma/web/plugs/frontend_static_plug_test.exs
@@ -103,6 +103,6 @@ test "api routes are detected correctly" do
       "check_password"
     ]
 
-    assert expected_routes == Pleroma.Web.get_api_routes()
+    assert expected_routes == Pleroma.Web.Router.get_api_routes()
   end
 end

From 69aed310de48ccd80326c7b639d8e179dfd21ba8 Mon Sep 17 00:00:00 2001
From: Snow <xxnmacsjuyidktezdy@awdrt.com>
Date: Sat, 29 May 2021 02:22:33 +0000
Subject: [PATCH 219/339] Adding description

---
 config/description.exs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/config/description.exs b/config/description.exs
index f00c53d28..1c3e3f900 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -682,7 +682,7 @@
       %{
         key: :allow_relay,
         type: :boolean,
-        description: "Enable Pleroma's Relay, which makes it possible to follow a whole instance"
+        description: "Enable Pleroma's Relay, which makes it possible to follow a whole instance. (Important!) This will increase the visibility of your instance."
       },
       %{
         key: :public,

From 3ebede4b514a69b41b34a0fe8e8fc27ce94a2071 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Thu, 20 May 2021 17:23:02 -0500
Subject: [PATCH 220/339] Gun: make Gun.API a runtime dep Speed up
 recompilation by breaking a compile-time cycle

---
 lib/pleroma/gun.ex | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/lib/pleroma/gun.ex b/lib/pleroma/gun.ex
index f9c828fac..bef1c9872 100644
--- a/lib/pleroma/gun.ex
+++ b/lib/pleroma/gun.ex
@@ -11,9 +11,7 @@ defmodule Pleroma.Gun do
   @callback await(pid(), reference()) :: {:response, :fin, 200, []}
   @callback set_owner(pid(), pid()) :: :ok
 
-  @api Pleroma.Config.get([Pleroma.Gun], Pleroma.Gun.API)
-
-  defp api, do: @api
+  defp api, do: Pleroma.Config.get([Pleroma.Gun], Pleroma.Gun.API)
 
   def open(host, port, opts), do: api().open(host, port, opts)
 

From 0ada3fe823a3c2e6c5835431bdacfbdb8b3d02a7 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Fri, 21 May 2021 12:31:28 -0500
Subject: [PATCH 221/339] Gun: use runtime deps in ConnectionPool Speed up
 recompilation time by breaking compile-time cycles

---
 lib/pleroma/gun/connection_pool/reclaimer.ex |  6 +++---
 lib/pleroma/gun/connection_pool/worker.ex    | 10 +++++-----
 lib/pleroma/http/adapter_helper/gun.ex       |  4 ++--
 3 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/lib/pleroma/gun/connection_pool/reclaimer.ex b/lib/pleroma/gun/connection_pool/reclaimer.ex
index c37b62bf2..4c643d7cb 100644
--- a/lib/pleroma/gun/connection_pool/reclaimer.ex
+++ b/lib/pleroma/gun/connection_pool/reclaimer.ex
@@ -5,11 +5,11 @@
 defmodule Pleroma.Gun.ConnectionPool.Reclaimer do
   use GenServer, restart: :temporary
 
-  @registry Pleroma.Gun.ConnectionPool
+  defp registry, do: Pleroma.Gun.ConnectionPool
 
   def start_monitor do
     pid =
-      case :gen_server.start(__MODULE__, [], name: {:via, Registry, {@registry, "reclaimer"}}) do
+      case :gen_server.start(__MODULE__, [], name: {:via, Registry, {registry(), "reclaimer"}}) do
         {:ok, pid} ->
           pid
 
@@ -46,7 +46,7 @@ def handle_continue(:reclaim, _) do
     #   {worker_pid, crf, last_reference} end)
     unused_conns =
       Registry.select(
-        @registry,
+        registry(),
         [
           {{:_, :"$1", {:_, :"$2", :"$3", :"$4"}}, [{:==, :"$2", []}], [{{:"$1", :"$3", :"$4"}}]}
         ]
diff --git a/lib/pleroma/gun/connection_pool/worker.ex b/lib/pleroma/gun/connection_pool/worker.ex
index 02bfff274..a3fa75386 100644
--- a/lib/pleroma/gun/connection_pool/worker.ex
+++ b/lib/pleroma/gun/connection_pool/worker.ex
@@ -6,10 +6,10 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
   alias Pleroma.Gun
   use GenServer, restart: :temporary
 
-  @registry Pleroma.Gun.ConnectionPool
+  defp registry, do: Pleroma.Gun.ConnectionPool
 
   def start_link([key | _] = opts) do
-    GenServer.start_link(__MODULE__, opts, name: {:via, Registry, {@registry, key}})
+    GenServer.start_link(__MODULE__, opts, name: {:via, Registry, {registry(), key}})
   end
 
   @impl true
@@ -24,7 +24,7 @@ def handle_continue({:connect, [key, uri, opts, client_pid]}, _) do
       time = :erlang.monotonic_time(:millisecond)
 
       {_, _} =
-        Registry.update_value(@registry, key, fn _ ->
+        Registry.update_value(registry(), key, fn _ ->
           {conn_pid, [client_pid], 1, time}
         end)
 
@@ -65,7 +65,7 @@ def handle_call(:add_client, {client_pid, _}, %{key: key, protocol: protocol} =
     time = :erlang.monotonic_time(:millisecond)
 
     {{conn_pid, used_by, _, _}, _} =
-      Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} ->
+      Registry.update_value(registry(), key, fn {conn_pid, used_by, crf, last_reference} ->
         {conn_pid, [client_pid | used_by], crf(time - last_reference, crf), time}
       end)
 
@@ -92,7 +92,7 @@ def handle_call(:add_client, {client_pid, _}, %{key: key, protocol: protocol} =
   @impl true
   def handle_call(:remove_client, {client_pid, _}, %{key: key} = state) do
     {{_conn_pid, used_by, _crf, _last_reference}, _} =
-      Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} ->
+      Registry.update_value(registry(), key, fn {conn_pid, used_by, crf, last_reference} ->
         {conn_pid, List.delete(used_by, client_pid), crf, last_reference}
       end)
 
diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex
index 82c7fd654..251539f34 100644
--- a/lib/pleroma/http/adapter_helper/gun.ex
+++ b/lib/pleroma/http/adapter_helper/gun.ex
@@ -54,8 +54,8 @@ def pool_timeout(pool) do
     Config.get([:pools, pool, :recv_timeout], default)
   end
 
-  @prefix Pleroma.Gun.ConnectionPool
   def limiter_setup do
+    prefix = Pleroma.Gun.ConnectionPool
     wait = Config.get([:connections_pool, :connection_acquisition_wait])
     retries = Config.get([:connections_pool, :connection_acquisition_retries])
 
@@ -66,7 +66,7 @@ def limiter_setup do
       max_waiting = Keyword.get(opts, :max_waiting, 10)
 
       result =
-        ConcurrentLimiter.new(:"#{@prefix}.#{name}", max_running, max_waiting,
+        ConcurrentLimiter.new(:"#{prefix}.#{name}", max_running, max_waiting,
           wait: wait,
           max_retries: retries
         )

From 32d263cb905dd7fffd43a4955295af0b2b378537 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Fri, 21 May 2021 13:04:57 -0500
Subject: [PATCH 222/339] Config: use runtime deps instead of module attributes
 Speeds up recompilation time by breaking compile-time cycles

---
 lib/pleroma/config/loader.ex        | 28 +++++++++++----------
 lib/pleroma/config/transfer_task.ex | 38 +++++++++++++++--------------
 2 files changed, 35 insertions(+), 31 deletions(-)

diff --git a/lib/pleroma/config/loader.ex b/lib/pleroma/config/loader.ex
index b64d06707..9489f58c4 100644
--- a/lib/pleroma/config/loader.ex
+++ b/lib/pleroma/config/loader.ex
@@ -3,19 +3,21 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Config.Loader do
-  @reject_keys [
-    Pleroma.Repo,
-    Pleroma.Web.Endpoint,
-    :env,
-    :configurable_from_database,
-    :database,
-    :swarm
-  ]
+  defp reject_keys,
+    do: [
+      Pleroma.Repo,
+      Pleroma.Web.Endpoint,
+      :env,
+      :configurable_from_database,
+      :database,
+      :swarm
+    ]
 
-  @reject_groups [
-    :postgrex,
-    :tesla
-  ]
+  defp reject_groups,
+    do: [
+      :postgrex,
+      :tesla
+    ]
 
   if Code.ensure_loaded?(Config.Reader) do
     @reader Config.Reader
@@ -52,7 +54,7 @@ defp filter(configs) do
   @spec filter_group(atom(), keyword()) :: keyword()
   def filter_group(group, configs) do
     Enum.reject(configs[group], fn {key, _v} ->
-      key in @reject_keys or group in @reject_groups or
+      key in reject_keys() or group in reject_groups() or
         (group == :phoenix and key == :serve_endpoints)
     end)
   end
diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex
index aad45aab8..1e3ae82d0 100644
--- a/lib/pleroma/config/transfer_task.ex
+++ b/lib/pleroma/config/transfer_task.ex
@@ -13,23 +13,25 @@ defmodule Pleroma.Config.TransferTask do
 
   @type env() :: :test | :benchmark | :dev | :prod
 
-  @reboot_time_keys [
-    {:pleroma, :hackney_pools},
-    {:pleroma, :chat},
-    {:pleroma, Oban},
-    {:pleroma, :rate_limit},
-    {:pleroma, :markup},
-    {:pleroma, :streamer},
-    {:pleroma, :pools},
-    {:pleroma, :connections_pool}
-  ]
+  defp reboot_time_keys,
+    do: [
+      {:pleroma, :hackney_pools},
+      {:pleroma, :chat},
+      {:pleroma, Oban},
+      {:pleroma, :rate_limit},
+      {:pleroma, :markup},
+      {:pleroma, :streamer},
+      {:pleroma, :pools},
+      {:pleroma, :connections_pool}
+    ]
 
-  @reboot_time_subkeys [
-    {:pleroma, Pleroma.Captcha, [:seconds_valid]},
-    {:pleroma, Pleroma.Upload, [:proxy_remote]},
-    {:pleroma, :instance, [:upload_limit]},
-    {:pleroma, :gopher, [:enabled]}
-  ]
+  defp reboot_time_subkeys,
+    do: [
+      {:pleroma, Pleroma.Captcha, [:seconds_valid]},
+      {:pleroma, Pleroma.Upload, [:proxy_remote]},
+      {:pleroma, :instance, [:upload_limit]},
+      {:pleroma, :gopher, [:enabled]}
+    ]
 
   def start_link(restart_pleroma? \\ true) do
     load_and_update_env([], restart_pleroma?)
@@ -165,12 +167,12 @@ def pleroma_need_restart?(group, key, value) do
   end
 
   defp group_and_key_need_reboot?(group, key) do
-    Enum.any?(@reboot_time_keys, fn {g, k} -> g == group and k == key end)
+    Enum.any?(reboot_time_keys(), fn {g, k} -> g == group and k == key end)
   end
 
   defp group_and_subkey_need_reboot?(group, key, value) do
     Keyword.keyword?(value) and
-      Enum.any?(@reboot_time_subkeys, fn {g, k, subkeys} ->
+      Enum.any?(reboot_time_subkeys(), fn {g, k, subkeys} ->
         g == group and k == key and
           Enum.any?(Keyword.keys(value), &(&1 in subkeys))
       end)

From c9e4200ed2167772294fceb4f282979b5ea04981 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Sat, 22 May 2021 14:59:12 -0500
Subject: [PATCH 223/339] Create real Views for all Controllers This makes
 views depend on each other at runtime instead of compile-time

---
 .../web/admin_api/controllers/o_auth_app_controller.ex |  1 -
 lib/pleroma/web/admin_api/views/o_auth_app_view.ex     | 10 ++++++++++
 .../controllers/follow_request_controller.ex           |  1 -
 .../web/mastodon_api/controllers/media_controller.ex   |  1 -
 .../mastodon_api/controllers/timeline_controller.ex    |  2 --
 .../web/mastodon_api/views/follow_request_view.ex      | 10 ++++++++++
 lib/pleroma/web/mastodon_api/views/media_view.ex       | 10 ++++++++++
 lib/pleroma/web/mastodon_api/views/timeline_view.ex    | 10 ++++++++++
 .../web/pleroma_api/controllers/account_controller.ex  |  1 -
 .../pleroma_api/controllers/conversation_controller.ex |  1 -
 .../pleroma_api/controllers/notification_controller.ex |  2 --
 lib/pleroma/web/pleroma_api/views/account_view.ex      | 10 ++++++++++
 lib/pleroma/web/pleroma_api/views/conversation_view.ex | 10 ++++++++++
 lib/pleroma/web/pleroma_api/views/notification_view.ex | 10 ++++++++++
 lib/pleroma/web/static_fe/static_fe_controller.ex      |  1 -
 15 files changed, 70 insertions(+), 10 deletions(-)
 create mode 100644 lib/pleroma/web/admin_api/views/o_auth_app_view.ex
 create mode 100644 lib/pleroma/web/mastodon_api/views/follow_request_view.ex
 create mode 100644 lib/pleroma/web/mastodon_api/views/media_view.ex
 create mode 100644 lib/pleroma/web/mastodon_api/views/timeline_view.ex
 create mode 100644 lib/pleroma/web/pleroma_api/views/account_view.ex
 create mode 100644 lib/pleroma/web/pleroma_api/views/conversation_view.ex
 create mode 100644 lib/pleroma/web/pleroma_api/views/notification_view.ex

diff --git a/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex b/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex
index 005fe67e2..51b17d392 100644
--- a/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex
@@ -13,7 +13,6 @@ defmodule Pleroma.Web.AdminAPI.OAuthAppController do
   require Logger
 
   plug(Pleroma.Web.ApiSpec.CastAndValidate)
-  plug(:put_view, Pleroma.Web.MastodonAPI.AppView)
 
   plug(
     OAuthScopesPlug,
diff --git a/lib/pleroma/web/admin_api/views/o_auth_app_view.ex b/lib/pleroma/web/admin_api/views/o_auth_app_view.ex
new file mode 100644
index 000000000..af046f343
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/o_auth_app_view.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.OAuthAppView do
+  use Pleroma.Web, :view
+  alias Pleroma.Web.MastodonAPI
+
+  def render(view, opts), do: MastodonAPI.AppView.render(view, opts)
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex
index 63d0e2c35..d915298f1 100644
--- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex
@@ -9,7 +9,6 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.Plugs.OAuthScopesPlug
 
-  plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)
   plug(Pleroma.Web.ApiSpec.CastAndValidate)
   plug(:assign_follower when action != :index)
 
diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
index d6949ed80..5918b288d 100644
--- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
@@ -13,7 +13,6 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
   plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:create, :create2])
   plug(Pleroma.Web.ApiSpec.CastAndValidate)
-  plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
 
   plug(OAuthScopesPlug, %{scopes: ["read:media"]} when action == :show)
   plug(OAuthScopesPlug, %{scopes: ["write:media"]} when action != :show)
diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
index cef299aa4..3f5849777 100644
--- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
@@ -37,8 +37,6 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
     when action in [:public, :hashtag]
   )
 
-  plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
-
   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TimelineOperation
 
   # GET /api/v1/timelines/home
diff --git a/lib/pleroma/web/mastodon_api/views/follow_request_view.ex b/lib/pleroma/web/mastodon_api/views/follow_request_view.ex
new file mode 100644
index 000000000..4c7d9fc65
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/follow_request_view.ex
@@ -0,0 +1,10 @@
+# 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.FollowRequestView do
+  use Pleroma.Web, :view
+  alias Pleroma.Web.MastodonAPI
+
+  def render(view, opts), do: MastodonAPI.AccountView.render(view, opts)
+end
diff --git a/lib/pleroma/web/mastodon_api/views/media_view.ex b/lib/pleroma/web/mastodon_api/views/media_view.ex
new file mode 100644
index 000000000..cf521887e
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/media_view.ex
@@ -0,0 +1,10 @@
+# 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.MediaView do
+  use Pleroma.Web, :view
+  alias Pleroma.Web.MastodonAPI
+
+  def render(view, opts), do: MastodonAPI.StatusView.render(view, opts)
+end
diff --git a/lib/pleroma/web/mastodon_api/views/timeline_view.ex b/lib/pleroma/web/mastodon_api/views/timeline_view.ex
new file mode 100644
index 000000000..91226d78e
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/timeline_view.ex
@@ -0,0 +1,10 @@
+# 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.TimelineView do
+  use Pleroma.Web, :view
+  alias Pleroma.Web.MastodonAPI
+
+  def render(view, opts), do: MastodonAPI.StatusView.render(view, opts)
+end
diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
index 165afd3b4..6e01c5497 100644
--- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
@@ -47,7 +47,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
   plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend)
 
   plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe])
-  plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)
 
   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAccountOperation
 
diff --git a/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex b/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex
index d285e4907..be2f4617d 100644
--- a/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex
@@ -13,7 +13,6 @@ defmodule Pleroma.Web.PleromaAPI.ConversationController do
   alias Pleroma.Web.Plugs.OAuthScopesPlug
 
   plug(Pleroma.Web.ApiSpec.CastAndValidate)
-  plug(:put_view, Pleroma.Web.MastodonAPI.ConversationView)
   plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:show, :statuses])
 
   plug(
diff --git a/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex
index 257bcd550..bcb3a9ae1 100644
--- a/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex
@@ -14,8 +14,6 @@ defmodule Pleroma.Web.PleromaAPI.NotificationController do
     %{scopes: ["write:notifications"]} when action == :mark_as_read
   )
 
-  plug(:put_view, Pleroma.Web.MastodonAPI.NotificationView)
-
   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaNotificationOperation
 
   def mark_as_read(%{assigns: %{user: user}, body_params: %{id: notification_id}} = conn, _) do
diff --git a/lib/pleroma/web/pleroma_api/views/account_view.ex b/lib/pleroma/web/pleroma_api/views/account_view.ex
new file mode 100644
index 000000000..28941f471
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/views/account_view.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.AccountView do
+  use Pleroma.Web, :view
+  alias Pleroma.Web.MastodonAPI
+
+  def render(view, opts), do: MastodonAPI.AccountView.render(view, opts)
+end
diff --git a/lib/pleroma/web/pleroma_api/views/conversation_view.ex b/lib/pleroma/web/pleroma_api/views/conversation_view.ex
new file mode 100644
index 000000000..173006360
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/views/conversation_view.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.ConversationView do
+  use Pleroma.Web, :view
+  alias Pleroma.Web.MastodonAPI
+
+  def render(view, opts), do: MastodonAPI.ConversationView.render(view, opts)
+end
diff --git a/lib/pleroma/web/pleroma_api/views/notification_view.ex b/lib/pleroma/web/pleroma_api/views/notification_view.ex
new file mode 100644
index 000000000..36b2fdfe8
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/views/notification_view.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.NotificationView do
+  use Pleroma.Web, :view
+  alias Pleroma.Web.MastodonAPI
+
+  def render(view, opts), do: MastodonAPI.NotificationView.render(view, opts)
+end
diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex
index fe485d10d..50f0927a3 100644
--- a/lib/pleroma/web/static_fe/static_fe_controller.ex
+++ b/lib/pleroma/web/static_fe/static_fe_controller.ex
@@ -14,7 +14,6 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
   alias Pleroma.Web.Router.Helpers
 
   plug(:put_layout, :static_fe)
-  plug(:put_view, Pleroma.Web.StaticFE.StaticFEView)
   plug(:assign_id)
 
   @page_keys ["max_id", "min_id", "limit", "since_id", "order"]

From 3ff9c5e2a67ab83c2abdb14cd246dea059079e75 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Sat, 22 May 2021 16:44:51 -0500
Subject: [PATCH 224/339] Break out activity-specific HTML functions into
 Pleroma.Activity.HTML Fixes cycles in
 lib/pleroma/ecto_type/activity_pub/object_validators/safe_text.ex

---
 lib/pleroma/activity/html.ex                  | 45 +++++++++++++++++++
 lib/pleroma/html.ex                           | 35 ---------------
 .../web/mastodon_api/views/status_view.ex     |  4 +-
 lib/pleroma/web/metadata/utils.ex             |  3 +-
 4 files changed, 49 insertions(+), 38 deletions(-)
 create mode 100644 lib/pleroma/activity/html.ex

diff --git a/lib/pleroma/activity/html.ex b/lib/pleroma/activity/html.ex
new file mode 100644
index 000000000..0bf393836
--- /dev/null
+++ b/lib/pleroma/activity/html.ex
@@ -0,0 +1,45 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Activity.HTML do
+  alias Pleroma.HTML
+  alias Pleroma.Object
+
+  @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
+
+  def get_cached_scrubbed_html_for_activity(
+        content,
+        scrubbers,
+        activity,
+        key \\ "",
+        callback \\ fn x -> x end
+      ) do
+    key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}"
+
+    @cachex.fetch!(:scrubber_cache, key, fn _key ->
+      object = Object.normalize(activity, fetch: false)
+      HTML.ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false, callback)
+    end)
+  end
+
+  def get_cached_stripped_html_for_activity(content, activity, key) do
+    get_cached_scrubbed_html_for_activity(
+      content,
+      FastSanitize.Sanitizer.StripTags,
+      activity,
+      key,
+      &HtmlEntities.decode/1
+    )
+  end
+
+  defp generate_scrubber_signature(scrubber) when is_atom(scrubber) do
+    generate_scrubber_signature([scrubber])
+  end
+
+  defp generate_scrubber_signature(scrubbers) do
+    Enum.reduce(scrubbers, "", fn scrubber, signature ->
+      "#{signature}#{to_string(scrubber)}"
+    end)
+  end
+end
diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex
index 2dfdca693..bee66169d 100644
--- a/lib/pleroma/html.ex
+++ b/lib/pleroma/html.ex
@@ -49,31 +49,6 @@ def filter_tags(html, scrubber) do
   def filter_tags(html), do: filter_tags(html, nil)
   def strip_tags(html), do: filter_tags(html, FastSanitize.Sanitizer.StripTags)
 
-  def get_cached_scrubbed_html_for_activity(
-        content,
-        scrubbers,
-        activity,
-        key \\ "",
-        callback \\ fn x -> x end
-      ) do
-    key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}"
-
-    @cachex.fetch!(:scrubber_cache, key, fn _key ->
-      object = Pleroma.Object.normalize(activity, fetch: false)
-      ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false, callback)
-    end)
-  end
-
-  def get_cached_stripped_html_for_activity(content, activity, key) do
-    get_cached_scrubbed_html_for_activity(
-      content,
-      FastSanitize.Sanitizer.StripTags,
-      activity,
-      key,
-      &HtmlEntities.decode/1
-    )
-  end
-
   def ensure_scrubbed_html(
         content,
         scrubbers,
@@ -92,16 +67,6 @@ def ensure_scrubbed_html(
     end
   end
 
-  defp generate_scrubber_signature(scrubber) when is_atom(scrubber) do
-    generate_scrubber_signature([scrubber])
-  end
-
-  defp generate_scrubber_signature(scrubbers) do
-    Enum.reduce(scrubbers, "", fn scrubber, signature ->
-      "#{signature}#{to_string(scrubber)}"
-    end)
-  end
-
   def extract_first_external_url_from_object(%{data: %{"content" => content}} = object)
       when is_binary(content) do
     unless object.data["fake"] do
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index bac897a57..da2cf0f95 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -254,7 +254,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
 
     content_html =
       content
-      |> HTML.get_cached_scrubbed_html_for_activity(
+      |> Activity.HTML.get_cached_scrubbed_html_for_activity(
         User.html_filter_policy(opts[:for]),
         activity,
         "mastoapi:content"
@@ -262,7 +262,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
 
     content_plaintext =
       content
-      |> HTML.get_cached_stripped_html_for_activity(
+      |> Activity.HTML.get_cached_stripped_html_for_activity(
         activity,
         "mastoapi:content"
       )
diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex
index de7195435..bc31d66b9 100644
--- a/lib/pleroma/web/metadata/utils.ex
+++ b/lib/pleroma/web/metadata/utils.ex
@@ -3,6 +3,7 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.Metadata.Utils do
+  alias Pleroma.Activity
   alias Pleroma.Emoji
   alias Pleroma.Formatter
   alias Pleroma.HTML
@@ -13,7 +14,7 @@ def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do
     # html content comes from DB already encoded, decode first and scrub after
     |> HtmlEntities.decode()
     |> String.replace(~r/<br\s?\/?>/, " ")
-    |> HTML.get_cached_stripped_html_for_activity(object, "metadata")
+    |> Activity.HTML.get_cached_stripped_html_for_activity(object, "metadata")
     |> Emoji.Formatter.demojify()
     |> HtmlEntities.decode()
     |> Formatter.truncate()

From fa543a936124abee524f9a103c17d2601176dcd4 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Fri, 21 May 2021 16:58:20 -0500
Subject: [PATCH 225/339] ActivityPub.Pipeline: switch to runtime deps Speed up
 recompilation by breaking compile-time cycles

---
 lib/pleroma/web/activity_pub/pipeline.ex | 31 +++++++++++-------------
 1 file changed, 14 insertions(+), 17 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex
index 195596f94..f04557a47 100644
--- a/lib/pleroma/web/activity_pub/pipeline.ex
+++ b/lib/pleroma/web/activity_pub/pipeline.ex
@@ -7,26 +7,23 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
   alias Pleroma.Config
   alias Pleroma.Object
   alias Pleroma.Repo
-  alias Pleroma.Web.ActivityPub.ActivityPub
-  alias Pleroma.Web.ActivityPub.MRF
-  alias Pleroma.Web.ActivityPub.ObjectValidator
-  alias Pleroma.Web.ActivityPub.SideEffects
+  alias Pleroma.Web.ActivityPub
   alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.Federator
 
-  @side_effects Config.get([:pipeline, :side_effects], SideEffects)
-  @federator Config.get([:pipeline, :federator], Federator)
-  @object_validator Config.get([:pipeline, :object_validator], ObjectValidator)
-  @mrf Config.get([:pipeline, :mrf], MRF)
-  @activity_pub Config.get([:pipeline, :activity_pub], ActivityPub)
-  @config Config.get([:pipeline, :config], Config)
+  defp side_effects, do: Config.get([:pipeline, :side_effects], SideEffects)
+  defp federator, do: Config.get([:pipeline, :federator], Federator)
+  defp object_validator, do: Config.get([:pipeline, :object_validator], ObjectValidator)
+  defp mrf, do: Config.get([:pipeline, :mrf], MRF)
+  defp activity_pub, do: Config.get([:pipeline, :activity_pub], ActivityPub)
+  defp config, do: Config.get([:pipeline, :config], Config)
 
   @spec common_pipeline(map(), keyword()) ::
           {:ok, Activity.t() | Object.t(), keyword()} | {:error, any()}
   def common_pipeline(object, meta) do
     case Repo.transaction(fn -> do_common_pipeline(object, meta) end) do
       {:ok, {:ok, activity, meta}} ->
-        @side_effects.handle_after_transaction(meta)
+        side_effects().handle_after_transaction(meta)
         {:ok, activity, meta}
 
       {:ok, value} ->
@@ -42,13 +39,13 @@ def common_pipeline(object, meta) do
 
   def do_common_pipeline(object, meta) do
     with {_, {:ok, validated_object, meta}} <-
-           {:validate_object, @object_validator.validate(object, meta)},
+           {:validate_object, object_validator().validate(object, meta)},
          {_, {:ok, mrfd_object, meta}} <-
-           {:mrf_object, @mrf.pipeline_filter(validated_object, meta)},
+           {:mrf_object, mrf().pipeline_filter(validated_object, meta)},
          {_, {:ok, activity, meta}} <-
-           {:persist_object, @activity_pub.persist(mrfd_object, meta)},
+           {:persist_object, activity_pub().persist(mrfd_object, meta)},
          {_, {:ok, activity, meta}} <-
-           {:execute_side_effects, @side_effects.handle(activity, meta)},
+           {:execute_side_effects, side_effects().handle(activity, meta)},
          {_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do
       {:ok, activity, meta}
     else
@@ -61,7 +58,7 @@ defp maybe_federate(%Object{}, _), do: {:ok, :not_federated}
 
   defp maybe_federate(%Activity{} = activity, meta) do
     with {:ok, local} <- Keyword.fetch(meta, :local) do
-      do_not_federate = meta[:do_not_federate] || !@config.get([:instance, :federating])
+      do_not_federate = meta[:do_not_federate] || !config().get([:instance, :federating])
 
       if !do_not_federate and local and not Visibility.is_local_public?(activity) do
         activity =
@@ -71,7 +68,7 @@ defp maybe_federate(%Activity{} = activity, meta) do
             activity
           end
 
-        @federator.publish(activity)
+        federator().publish(activity)
         {:ok, :federated}
       else
         {:ok, :not_federated}

From 0204ceff7f66cffd87f06926ad856742940e02ff Mon Sep 17 00:00:00 2001
From: shibao <shibao@bubbletea.dev>
Date: Sun, 30 May 2021 10:27:58 -0400
Subject: [PATCH 226/339] Add ffmpeg

---
 Dockerfile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Dockerfile b/Dockerfile
index b1b5171af..db1a6b457 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -33,7 +33,7 @@ ARG DATA=/var/lib/pleroma
 
 RUN echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\
 	apk update &&\
-	apk add exiftool imagemagick libmagic ncurses postgresql-client &&\
+	apk add exiftool ffmpeg imagemagick libmagic ncurses postgresql-client &&\
 	adduser --system --shell /bin/false --home ${HOME} pleroma &&\
 	mkdir -p ${DATA}/uploads &&\
 	mkdir -p ${DATA}/static &&\

From 721c966842c2f9b4f4d6f227ecf3de69d2e66346 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Fri, 21 May 2021 17:37:34 -0500
Subject: [PATCH 227/339] FrontendStatic: make Router a runtime dep Speeds up
 recompilation by removing compile-time cycles

---
 lib/pleroma/web/plugs/frontend_static.ex | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/lib/pleroma/web/plugs/frontend_static.ex b/lib/pleroma/web/plugs/frontend_static.ex
index e7c943b41..ebe7eaf86 100644
--- a/lib/pleroma/web/plugs/frontend_static.ex
+++ b/lib/pleroma/web/plugs/frontend_static.ex
@@ -10,8 +10,6 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do
   """
   @behaviour Plug
 
-  @api_routes Pleroma.Web.Router.get_api_routes()
-
   def file_path(path, frontend_type \\ :primary) do
     if configuration = Pleroma.Config.get([:frontends, frontend_type]) do
       instance_static_path = Pleroma.Config.get([:instance, :static_dir], "instance/static")
@@ -55,10 +53,13 @@ defp invalid_path?([h | _], _match) when h in [".", "..", ""], do: true
   defp invalid_path?([h | t], match), do: String.contains?(h, match) or invalid_path?(t)
   defp invalid_path?([], _match), do: false
 
-  defp api_route?([h | _]) when h in @api_routes, do: true
-  defp api_route?([_ | t]), do: api_route?(t)
   defp api_route?([]), do: false
 
+  defp api_route?([h | t]) do
+    api_routes = Pleroma.Web.Router.get_api_routes()
+    if h in api_routes, do: true, else: api_route?(t)
+  end
+
   defp call_static(conn, opts, from) do
     opts = Map.put(opts, :from, from)
     Plug.Static.call(conn, opts)

From 0107ec63a24784a78f81e3267230e26bf8337ca2 Mon Sep 17 00:00:00 2001
From: Snow <build-a-website@protonmail.com>
Date: Mon, 15 Mar 2021 15:10:17 +0000
Subject: [PATCH 228/339] Added translation using Weblate (Chinese
 (Traditional))

---
 priv/gettext/zh_Hant/LC_MESSAGES/errors.po | 578 +++++++++++++++++++++
 1 file changed, 578 insertions(+)
 create mode 100644 priv/gettext/zh_Hant/LC_MESSAGES/errors.po

diff --git a/priv/gettext/zh_Hant/LC_MESSAGES/errors.po b/priv/gettext/zh_Hant/LC_MESSAGES/errors.po
new file mode 100644
index 000000000..63e0d36b4
--- /dev/null
+++ b/priv/gettext/zh_Hant/LC_MESSAGES/errors.po
@@ -0,0 +1,578 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-03-15 15:10+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: zh_Hant\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 2.5.1\n"
+
+## This file is a PO Template file.
+##
+## `msgid`s here are often extracted from source code.
+## Add new translations manually only if they're dynamic
+## translations that can't be statically extracted.
+##
+## Run `mix gettext.extract` to bring this file up to
+## date. Leave `msgstr`s empty as changing them here as no
+## effect: edit them in PO (`.po`) files instead.
+## From Ecto.Changeset.cast/4
+msgid "can't be blank"
+msgstr ""
+
+## From Ecto.Changeset.unique_constraint/3
+msgid "has already been taken"
+msgstr ""
+
+## From Ecto.Changeset.put_change/3
+msgid "is invalid"
+msgstr ""
+
+## From Ecto.Changeset.validate_format/3
+msgid "has invalid format"
+msgstr ""
+
+## From Ecto.Changeset.validate_subset/3
+msgid "has an invalid entry"
+msgstr ""
+
+## From Ecto.Changeset.validate_exclusion/3
+msgid "is reserved"
+msgstr ""
+
+## From Ecto.Changeset.validate_confirmation/3
+msgid "does not match confirmation"
+msgstr ""
+
+## From Ecto.Changeset.no_assoc_constraint/3
+msgid "is still associated with this entry"
+msgstr ""
+
+msgid "are still associated with this entry"
+msgstr ""
+
+## From Ecto.Changeset.validate_length/3
+msgid "should be %{count} character(s)"
+msgid_plural "should be %{count} character(s)"
+msgstr[0] ""
+
+msgid "should have %{count} item(s)"
+msgid_plural "should have %{count} item(s)"
+msgstr[0] ""
+
+msgid "should be at least %{count} character(s)"
+msgid_plural "should be at least %{count} character(s)"
+msgstr[0] ""
+
+msgid "should have at least %{count} item(s)"
+msgid_plural "should have at least %{count} item(s)"
+msgstr[0] ""
+
+msgid "should be at most %{count} character(s)"
+msgid_plural "should be at most %{count} character(s)"
+msgstr[0] ""
+
+msgid "should have at most %{count} item(s)"
+msgid_plural "should have at most %{count} item(s)"
+msgstr[0] ""
+
+## From Ecto.Changeset.validate_number/3
+msgid "must be less than %{number}"
+msgstr ""
+
+msgid "must be greater than %{number}"
+msgstr ""
+
+msgid "must be less than or equal to %{number}"
+msgstr ""
+
+msgid "must be greater than or equal to %{number}"
+msgstr ""
+
+msgid "must be equal to %{number}"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:505
+#, elixir-format
+msgid "Account not found"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:339
+#, elixir-format
+msgid "Already voted"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:359
+#, elixir-format
+msgid "Bad request"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426
+#, elixir-format
+msgid "Can't delete object"
+msgstr ""
+
+#: lib/pleroma/web/controller_helper.ex:105
+#: lib/pleroma/web/controller_helper.ex:111
+#, elixir-format
+msgid "Can't display this activity"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:285
+#, elixir-format
+msgid "Can't find user"
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61
+#, elixir-format
+msgid "Can't get favorites"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438
+#, elixir-format
+msgid "Can't like object"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:563
+#, elixir-format
+msgid "Cannot post an empty status without attachments"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:511
+#, elixir-format
+msgid "Comment must be up to %{max_size} characters"
+msgstr ""
+
+#: lib/pleroma/config/config_db.ex:191
+#, elixir-format
+msgid "Config with params %{params} not found"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:181
+#: lib/pleroma/web/common_api/common_api.ex:185
+#, elixir-format
+msgid "Could not delete"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:231
+#, elixir-format
+msgid "Could not favorite"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:453
+#, elixir-format
+msgid "Could not pin"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:278
+#, elixir-format
+msgid "Could not unfavorite"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:463
+#, elixir-format
+msgid "Could not unpin"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:216
+#, elixir-format
+msgid "Could not unrepeat"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:512
+#: lib/pleroma/web/common_api/common_api.ex:521
+#, elixir-format
+msgid "Could not update state"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207
+#, elixir-format
+msgid "Error."
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:106
+#, elixir-format
+msgid "Invalid CAPTCHA"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116
+#: lib/pleroma/web/oauth/oauth_controller.ex:568
+#, elixir-format
+msgid "Invalid credentials"
+msgstr ""
+
+#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
+#, elixir-format
+msgid "Invalid credentials."
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:355
+#, elixir-format
+msgid "Invalid indices"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29
+#, elixir-format
+msgid "Invalid parameters"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:414
+#, elixir-format
+msgid "Invalid password."
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220
+#, elixir-format
+msgid "Invalid request"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:109
+#, elixir-format
+msgid "Kocaptcha service unavailable"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112
+#, elixir-format
+msgid "Missing parameters"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:547
+#, elixir-format
+msgid "No such conversation"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456
+#, elixir-format
+msgid "No such permission_group"
+msgstr ""
+
+#: lib/pleroma/plugs/uploaded_media.ex:84
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11
+#: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143
+#, elixir-format
+msgid "Not found"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:331
+#, elixir-format
+msgid "Poll's author can't vote"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:306
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
+#, elixir-format
+msgid "Record not found"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35
+#: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36
+#: lib/pleroma/web/ostatus/ostatus_controller.ex:149
+#, elixir-format
+msgid "Something went wrong"
+msgstr ""
+
+#: lib/pleroma/web/common_api/activity_draft.ex:107
+#, elixir-format
+msgid "The message visibility must be direct"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:573
+#, elixir-format
+msgid "The status is over the character limit"
+msgstr ""
+
+#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
+#, elixir-format
+msgid "This resource requires authentication."
+msgstr ""
+
+#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
+#, elixir-format
+msgid "Throttled"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:356
+#, elixir-format
+msgid "Too many choices"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443
+#, elixir-format
+msgid "Unhandled activity type"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485
+#, elixir-format
+msgid "You can't revoke your own admin status."
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:221
+#: lib/pleroma/web/oauth/oauth_controller.ex:308
+#, elixir-format
+msgid "Your account is currently disabled"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:183
+#: lib/pleroma/web/oauth/oauth_controller.ex:331
+#, elixir-format
+msgid "Your login is missing a confirmed e-mail address"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390
+#, elixir-format
+msgid "can't read inbox of %{nickname} as %{as_nickname}"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473
+#, elixir-format
+msgid "can't update outbox of %{nickname} as %{as_nickname}"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:471
+#, elixir-format
+msgid "conversation is already muted"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492
+#, elixir-format
+msgid "error"
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32
+#, elixir-format
+msgid "mascots can only be images"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62
+#, elixir-format
+msgid "not found"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:394
+#, elixir-format
+msgid "Bad OAuth request."
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:115
+#, elixir-format
+msgid "CAPTCHA already used"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:112
+#, elixir-format
+msgid "CAPTCHA expired"
+msgstr ""
+
+#: lib/pleroma/plugs/uploaded_media.ex:57
+#, elixir-format
+msgid "Failed"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:410
+#, elixir-format
+msgid "Failed to authenticate: %{message}."
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:441
+#, elixir-format
+msgid "Failed to set up user account."
+msgstr ""
+
+#: lib/pleroma/plugs/oauth_scopes_plug.ex:38
+#, elixir-format
+msgid "Insufficient permissions: %{permissions}."
+msgstr ""
+
+#: lib/pleroma/plugs/uploaded_media.ex:104
+#, elixir-format
+msgid "Internal Error"
+msgstr ""
+
+#: lib/pleroma/web/oauth/fallback_controller.ex:22
+#: lib/pleroma/web/oauth/fallback_controller.ex:29
+#, elixir-format
+msgid "Invalid Username/Password"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:118
+#, elixir-format
+msgid "Invalid answer data"
+msgstr ""
+
+#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33
+#, elixir-format
+msgid "Nodeinfo schema version not handled"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:172
+#, elixir-format
+msgid "This action is outside the authorized scopes"
+msgstr ""
+
+#: lib/pleroma/web/oauth/fallback_controller.ex:14
+#, elixir-format
+msgid "Unknown error, please check the details and try again."
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:119
+#: lib/pleroma/web/oauth/oauth_controller.ex:158
+#, elixir-format
+msgid "Unlisted redirect_uri."
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:390
+#, elixir-format
+msgid "Unsupported OAuth provider: %{provider}."
+msgstr ""
+
+#: lib/pleroma/uploaders/uploader.ex:72
+#, elixir-format
+msgid "Uploader callback timeout"
+msgstr ""
+
+#: lib/pleroma/web/uploader_controller.ex:23
+#, elixir-format
+msgid "bad request"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:103
+#, elixir-format
+msgid "CAPTCHA Error"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:290
+#, elixir-format
+msgid "Could not add reaction emoji"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:301
+#, elixir-format
+msgid "Could not remove reaction emoji"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:129
+#, elixir-format
+msgid "Invalid CAPTCHA (Missing parameter: %{name})"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
+#, elixir-format
+msgid "List not found"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123
+#, elixir-format
+msgid "Missing parameter: %{name}"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:210
+#: lib/pleroma/web/oauth/oauth_controller.ex:321
+#, elixir-format
+msgid "Password reset is required"
+msgstr ""
+
+#: lib/pleroma/tests/auth_test_controller.ex:9
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/config_controller.ex:6 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6 lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex:6 lib/pleroma/web/admin_api/controllers/relay_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/report_controller.ex:6 lib/pleroma/web/admin_api/controllers/status_controller.ex:6
+#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/embed_controller.ex:6
+#: lib/pleroma/web/fallback_redirect_controller.ex:6 lib/pleroma/web/feed/tag_controller.ex:6
+#: lib/pleroma/web/feed/user_controller.ex:6 lib/pleroma/web/mailer/subscription_controller.ex:2
+#: lib/pleroma/web/masto_fe_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14
+#: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8
+#: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7
+#: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6
+#: lib/pleroma/web/media_proxy/media_proxy_controller.ex:6 lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6
+#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6 lib/pleroma/web/oauth/fallback_controller.ex:6
+#: lib/pleroma/web/oauth/mfa_controller.ex:10 lib/pleroma/web/oauth/oauth_controller.ex:6
+#: lib/pleroma/web/ostatus/ostatus_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/chat_controller.ex:5 lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:2 lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_controller.ex:6
+#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6
+#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6 lib/pleroma/web/twitter_api/twitter_api_controller.ex:6
+#: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_controller.ex:6
+#, elixir-format
+msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
+msgstr ""
+
+#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
+#, elixir-format
+msgid "Two-factor authentication enabled, you must use a access token."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210
+#, elixir-format
+msgid "Unexpected error occurred while adding file to pack."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138
+#, elixir-format
+msgid "Unexpected error occurred while creating pack."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278
+#, elixir-format
+msgid "Unexpected error occurred while removing file from pack."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250
+#, elixir-format
+msgid "Unexpected error occurred while updating file in pack."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179
+#, elixir-format
+msgid "Unexpected error occurred while updating pack metadata."
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
+#, elixir-format
+msgid "Web push subscription is disabled on this Pleroma instance"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451
+#, elixir-format
+msgid "You can't revoke your own admin/moderator status."
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126
+#, elixir-format
+msgid "authorization required for timeline view"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24
+#, elixir-format
+msgid "Access denied"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282
+#, elixir-format
+msgid "This API requires an authenticated user"
+msgstr ""
+
+#: lib/pleroma/plugs/user_is_admin_plug.ex:21
+#, elixir-format
+msgid "User is not an admin."
+msgstr ""

From b3209c31bc1582665399ffe65785857b40b2733a Mon Sep 17 00:00:00 2001
From: Snow <build-a-website@protonmail.com>
Date: Thu, 25 Mar 2021 06:35:52 +0000
Subject: [PATCH 229/339] Translated using Weblate (Chinese (Traditional))

Currently translated at 0.9% (1 of 106 strings)

Translation: Pleroma/Pleroma backend
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma/zh_Hant/
---
 priv/gettext/zh_Hant/LC_MESSAGES/errors.po | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/priv/gettext/zh_Hant/LC_MESSAGES/errors.po b/priv/gettext/zh_Hant/LC_MESSAGES/errors.po
index 63e0d36b4..8e6905976 100644
--- a/priv/gettext/zh_Hant/LC_MESSAGES/errors.po
+++ b/priv/gettext/zh_Hant/LC_MESSAGES/errors.po
@@ -3,14 +3,16 @@ msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2021-03-15 15:10+0000\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: Automatically generated\n"
-"Language-Team: none\n"
+"PO-Revision-Date: 2021-03-25 13:08+0000\n"
+"Last-Translator: Snow <build-a-website@protonmail.com>\n"
+"Language-Team: Chinese (Traditional) <https://translate.pleroma.social/"
+"projects/pleroma/pleroma/zh_Hant/>\n"
 "Language: zh_Hant\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Translate Toolkit 2.5.1\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Weblate 4.0.4\n"
 
 ## This file is a PO Template file.
 ##
@@ -23,7 +25,7 @@ msgstr ""
 ## effect: edit them in PO (`.po`) files instead.
 ## From Ecto.Changeset.cast/4
 msgid "can't be blank"
-msgstr ""
+msgstr "不能為空"
 
 ## From Ecto.Changeset.unique_constraint/3
 msgid "has already been taken"

From 2fde1f25492da61c531c086589cbf4a56eef5001 Mon Sep 17 00:00:00 2001
From: Snow <build-a-website@protonmail.com>
Date: Tue, 11 May 2021 01:39:31 +0000
Subject: [PATCH 230/339] Translated using Weblate (Chinese (Traditional))

Currently translated at 5.6% (6 of 106 strings)

Translation: Pleroma/Pleroma backend
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma/zh_Hant/
---
 priv/gettext/zh_Hant/LC_MESSAGES/errors.po | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/priv/gettext/zh_Hant/LC_MESSAGES/errors.po b/priv/gettext/zh_Hant/LC_MESSAGES/errors.po
index 8e6905976..9678ca297 100644
--- a/priv/gettext/zh_Hant/LC_MESSAGES/errors.po
+++ b/priv/gettext/zh_Hant/LC_MESSAGES/errors.po
@@ -3,7 +3,7 @@ msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2021-03-15 15:10+0000\n"
-"PO-Revision-Date: 2021-03-25 13:08+0000\n"
+"PO-Revision-Date: 2021-05-12 01:41+0000\n"
 "Last-Translator: Snow <build-a-website@protonmail.com>\n"
 "Language-Team: Chinese (Traditional) <https://translate.pleroma.social/"
 "projects/pleroma/pleroma/zh_Hant/>\n"
@@ -29,7 +29,7 @@ msgstr "不能為空"
 
 ## From Ecto.Changeset.unique_constraint/3
 msgid "has already been taken"
-msgstr ""
+msgstr "已被占用"
 
 ## From Ecto.Changeset.put_change/3
 msgid "is invalid"
@@ -45,7 +45,7 @@ msgstr ""
 
 ## From Ecto.Changeset.validate_exclusion/3
 msgid "is reserved"
-msgstr ""
+msgstr "是被保留的"
 
 ## From Ecto.Changeset.validate_confirmation/3
 msgid "does not match confirmation"
@@ -85,10 +85,10 @@ msgstr[0] ""
 
 ## From Ecto.Changeset.validate_number/3
 msgid "must be less than %{number}"
-msgstr ""
+msgstr "必須小於{number}%"
 
 msgid "must be greater than %{number}"
-msgstr ""
+msgstr "must be greater than {number}%"
 
 msgid "must be less than or equal to %{number}"
 msgstr ""
@@ -200,7 +200,7 @@ msgstr ""
 #: lib/pleroma/web/twitter_api/twitter_api.ex:106
 #, elixir-format
 msgid "Invalid CAPTCHA"
-msgstr ""
+msgstr "無效的驗證碼"
 
 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116
 #: lib/pleroma/web/oauth/oauth_controller.ex:568

From 03232a82235be5f8811ce79c5c4afc830b12472a Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Wed, 26 May 2021 06:14:45 +0200
Subject: [PATCH 231/339] Changing references of freenode to libera.chat

---
 README.md                            | 4 ++--
 docs/installation/alpine_linux_en.md | 2 +-
 docs/installation/arch_linux_en.md   | 2 +-
 docs/installation/debian_based_en.md | 2 +-
 docs/installation/debian_based_jp.md | 4 ++--
 docs/installation/freebsd_en.md      | 2 +-
 docs/installation/gentoo_en.md       | 2 +-
 docs/installation/netbsd_en.md       | 4 +---
 docs/installation/openbsd_en.md      | 2 +-
 docs/installation/openbsd_fi.md      | 4 ++--
 docs/installation/otp_en.md          | 4 ++--
 11 files changed, 15 insertions(+), 17 deletions(-)

diff --git a/README.md b/README.md
index 7a05b9e48..6aa36d89a 100644
--- a/README.md
+++ b/README.md
@@ -50,5 +50,5 @@ If you are not developing Pleroma, it is better to use the OTP release, which co
 - Latest Git revision: <https://docs-develop.pleroma.social>
 
 ## Community Channels
-* IRC: **#pleroma** and **#pleroma-dev** on freenode, webchat is available at <https://irc.pleroma.social>
-* Matrix: <https://matrix.to/#/#freenode_#pleroma:matrix.org> and <https://matrix.to/#/#freenode_#pleroma-dev:matrix.org>
+* IRC: **#pleroma** and **#pleroma-dev** on libera.chat, webchat is available at <https://irc.pleroma.social>
+* Matrix: [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) and [#pleroma-dev:libera.chat](https://matrix.to/#/#pleroma-dev:libera.chat)
diff --git a/docs/installation/alpine_linux_en.md b/docs/installation/alpine_linux_en.md
index 7eb1718f2..a449db39b 100644
--- a/docs/installation/alpine_linux_en.md
+++ b/docs/installation/alpine_linux_en.md
@@ -240,4 +240,4 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
 
 ## Questions
 
-Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
diff --git a/docs/installation/arch_linux_en.md b/docs/installation/arch_linux_en.md
index da78c3205..44b00aed8 100644
--- a/docs/installation/arch_linux_en.md
+++ b/docs/installation/arch_linux_en.md
@@ -215,4 +215,4 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
 
 ## Questions
 
-Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
diff --git a/docs/installation/debian_based_en.md b/docs/installation/debian_based_en.md
index c5687a01e..ca983274e 100644
--- a/docs/installation/debian_based_en.md
+++ b/docs/installation/debian_based_en.md
@@ -202,4 +202,4 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
 
 ## Questions
 
-Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
diff --git a/docs/installation/debian_based_jp.md b/docs/installation/debian_based_jp.md
index c4bbd4780..dedf555d6 100644
--- a/docs/installation/debian_based_jp.md
+++ b/docs/installation/debian_based_jp.md
@@ -191,5 +191,5 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
 
 インストールについて質問がある、もしくは、うまくいかないときは、以下のところで質問できます。
 
-* [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org)
-* **Freenode** の **#pleroma** IRCチャンネル
+* [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat)
+* **libera.chat** の **#pleroma** IRCチャンネル
diff --git a/docs/installation/freebsd_en.md b/docs/installation/freebsd_en.md
index 2dc466eb8..bdf0dd24f 100644
--- a/docs/installation/freebsd_en.md
+++ b/docs/installation/freebsd_en.md
@@ -213,4 +213,4 @@ incorrect timestamps. You should have ntpd running.
 
 ## Questions
 
-Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
diff --git a/docs/installation/gentoo_en.md b/docs/installation/gentoo_en.md
index f2380ab72..1df1fede2 100644
--- a/docs/installation/gentoo_en.md
+++ b/docs/installation/gentoo_en.md
@@ -298,4 +298,4 @@ If you opted to allow sudo for the `pleroma` user but would like to remove the a
 
 ## Questions
 
-Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
diff --git a/docs/installation/netbsd_en.md b/docs/installation/netbsd_en.md
index 233cf28b7..780ad2d8d 100644
--- a/docs/installation/netbsd_en.md
+++ b/docs/installation/netbsd_en.md
@@ -193,8 +193,6 @@ Run `# /etc/rc.d/pleroma start` to start Pleroma.
 
 Restart nginx with `# /etc/rc.d/nginx restart` and you should be up and running.
 
-If you need further help, contact niaa on freenode.
-
 Make sure your time is in sync, or other instances will receive your posts with
 incorrect timestamps. You should have ntpd running.
 
@@ -208,4 +206,4 @@ incorrect timestamps. You should have ntpd running.
 
 ## Questions
 
-Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
diff --git a/docs/installation/openbsd_en.md b/docs/installation/openbsd_en.md
index 0e1269ca5..a805a9235 100644
--- a/docs/installation/openbsd_en.md
+++ b/docs/installation/openbsd_en.md
@@ -264,4 +264,4 @@ LC_ALL=en_US.UTF-8 MIX_ENV=prod mix pleroma.user new <username> <your@emailaddre
 
 ## Questions
 
-Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
diff --git a/docs/installation/openbsd_fi.md b/docs/installation/openbsd_fi.md
index a61434147..3c40b2d1a 100644
--- a/docs/installation/openbsd_fi.md
+++ b/docs/installation/openbsd_fi.md
@@ -10,8 +10,8 @@ suositeltavaa tehdä komennon `doas` avulla, katso `doas (1)` ja `doas.conf (5)`
 Tästä eteenpäin oletuksena on, että domain "esimerkki.com" osoittaa
 serverin IP-osoitteeseen.
 
-Jos asennuksen kanssa on ongelmia, IRC-kanava #pleroma Freenodessa tai
-Matrix-kanava #freenode_#pleroma:matrix.org ovat hyviä paikkoja löytää apua
+Jos asennuksen kanssa on ongelmia, IRC-kanava #pleroma Libera.chat tai
+Matrix-kanava #pleroma:libera.chat ovat hyviä paikkoja löytää apua
 (englanniksi), `/msg eal kukkuu` jos haluat välttämättä puhua härmää.
 
 Asenna tarvittava ohjelmisto:
diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md
index 13f9636f3..8e43e3239 100644
--- a/docs/installation/otp_en.md
+++ b/docs/installation/otp_en.md
@@ -232,7 +232,7 @@ At this point if you open your (sub)domain in a browser you should see a 502 err
 
 If everything worked, you should see Pleroma-FE when visiting your domain. If that didn't happen, try reviewing the installation steps, starting Pleroma in the foreground and seeing if there are any errrors.
 
-Still doesn't work? Feel free to contact us on [#pleroma on freenode](https://irc.pleroma.social) or via matrix at <https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org>, you can also [file an issue on our Gitlab](https://git.pleroma.social/pleroma/pleroma-support/issues/new)
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC, you can also [file an issue on our Gitlab](https://git.pleroma.social/pleroma/pleroma-support/issues/new).
 
 ## Post installation
 
@@ -301,4 +301,4 @@ This will create an account withe the username of 'joeuser' with the email addre
 
 ## Questions
 
-Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC, you can also [file an issue on our Gitlab](https://git.pleroma.social/pleroma/pleroma-support/issues/new).

From da1ee5c46ac4296232ac5cd43f4357136608af48 Mon Sep 17 00:00:00 2001
From: Guy Sheffer <guysoft@gmail.com>
Date: Mon, 31 May 2021 19:17:49 +0300
Subject: [PATCH 232/339] Add Raspberry Pi install instructions

---
 README.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/README.md b/README.md
index 7a05b9e48..0099fe990 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,9 @@ Currently Pleroma is not packaged by any OS/Distros, but if you want to package
 ### Docker
 While we don’t provide docker files, other people have written very good ones. Take a look at <https://github.com/angristan/docker-pleroma> or <https://glitch.sh/sn0w/pleroma-docker>.
 
+### Raspberry Pi
+Community maintained Raspberry Pi image that you can flash and run Pleroma on your Raspberry Pi. Available here <https://github.com/guysoft/PleromaPi>.
+
 ### Compilation Troubleshooting
 If you ever encounter compilation issues during the updating of Pleroma, you can try these commands and see if they fix things:
 

From 10dfe814795f16d6c32f5b6a7421e3e7c597f1ad Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Mon, 31 May 2021 13:39:15 -0500
Subject: [PATCH 233/339] Pleroma.Constants.as_local_public/0 -->
 Pleroma.Web.ActivityPub.Utils.as_local_public/0 Move as_local_public/0 to
 stop making modules depend on Web at compile-time

---
 lib/pleroma/constants.ex                                    | 2 --
 lib/pleroma/web/activity_pub/builder.ex                     | 2 +-
 .../activity_pub/object_validators/announce_validator.ex    | 2 +-
 lib/pleroma/web/activity_pub/utils.ex                       | 2 ++
 lib/pleroma/web/activity_pub/visibility.ex                  | 6 +++---
 lib/pleroma/web/common_api/utils.ex                         | 2 +-
 .../web/mastodon_api/controllers/status_controller_test.exs | 3 ++-
 7 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex
index b24338cc6..bf92f65cb 100644
--- a/lib/pleroma/constants.ex
+++ b/lib/pleroma/constants.ex
@@ -27,6 +27,4 @@ defmodule Pleroma.Constants do
     do:
       ~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css)
   )
-
-  def as_local_public, do: Pleroma.Web.base_url() <> "/#Public"
 end
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index f56bfc600..f74888b67 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -223,7 +223,7 @@ def announce(actor, object, options \\ []) do
           [actor.follower_address]
 
         public? and Visibility.is_local_public?(object) ->
-          [actor.follower_address, object.data["actor"], Pleroma.Constants.as_local_public()]
+          [actor.follower_address, object.data["actor"], Utils.as_local_public()]
 
         public? ->
           [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
diff --git a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
index b08a33e68..004500742 100644
--- a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
@@ -68,7 +68,7 @@ def validate_announcable(cng) do
          false <- Visibility.is_public?(object) do
       same_actor = object.data["actor"] == actor.ap_id
       recipients = get_field(cng, :to) ++ get_field(cng, :cc)
-      local_public = Pleroma.Constants.as_local_public()
+      local_public = Utils.as_local_public()
 
       is_public =
         Enum.member?(recipients, Pleroma.Constants.as_public()) or
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index a4dc469dc..984f39aa7 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -38,6 +38,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   @supported_report_states ~w(open closed resolved)
   @valid_visibilities ~w(public unlisted private direct)
 
+  def as_local_public, do: Web.base_url() <> "/#Public"
+
   # Some implementations send the actor URI as the actor field, others send the entire actor object,
   # so figure out what the actor's URI is based on what we have.
   def get_ap_id(%{"id" => id} = _), do: id
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 00234c0b0..2be59144d 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -20,14 +20,14 @@ def is_public?(%{"directMessage" => true}), do: false
 
   def is_public?(data) do
     Utils.label_in_message?(Pleroma.Constants.as_public(), data) or
-      Utils.label_in_message?(Pleroma.Constants.as_local_public(), data)
+      Utils.label_in_message?(Utils.as_local_public(), data)
   end
 
   def is_local_public?(%Object{data: data}), do: is_local_public?(data)
   def is_local_public?(%Activity{data: data}), do: is_local_public?(data)
 
   def is_local_public?(data) do
-    Utils.label_in_message?(Pleroma.Constants.as_local_public(), data) and
+    Utils.label_in_message?(Utils.as_local_public(), data) and
       not Utils.label_in_message?(Pleroma.Constants.as_public(), data)
   end
 
@@ -127,7 +127,7 @@ def get_visibility(object) do
       Pleroma.Constants.as_public() in cc ->
         "unlisted"
 
-      Pleroma.Constants.as_local_public() in to ->
+      Utils.as_local_public() in to ->
         "local"
 
       # this should use the sql for the object's activity
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 9587dfa25..93bb8e8fa 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -69,7 +69,7 @@ def get_to_and_cc(%{visibility: visibility} = draft) when visibility in ["public
     to =
       case visibility do
         "public" -> [Pleroma.Constants.as_public() | draft.mentions]
-        "local" -> [Pleroma.Constants.as_local_public() | draft.mentions]
+        "local" -> [Utils.as_local_public() | draft.mentions]
       end
 
     cc = [draft.user.follower_address]
diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
index e76c2760d..fe0a5c28d 100644
--- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
@@ -14,6 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
   alias Pleroma.Tests.ObanHelpers
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
+  alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.CommonAPI
 
   import Pleroma.Factory
@@ -1875,7 +1876,7 @@ test "posting a local only status" do
         "visibility" => "local"
       })
 
-    local = Pleroma.Constants.as_local_public()
+    local = Utils.as_local_public()
 
     assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
              json_response(conn_one, 200)

From 51a9f97e87823cbd9e92c375f4bc4c0bfa8b79db Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Mon, 31 May 2021 15:09:11 -0500
Subject: [PATCH 234/339] Deprecate Pleroma.Web.base_url/0 Use
 Pleroma.Web.Endpoint.url/0 directly instead. Reduces compiler cycles.

---
 benchmarks/load_testing/activities.ex         |  2 +-
 lib/pleroma/application.ex                    |  2 +-
 lib/pleroma/constants.ex                      |  2 +-
 lib/pleroma/emails/admin_email.ex             |  4 +-
 lib/pleroma/emoji/formatter.ex                |  4 +-
 lib/pleroma/formatter.ex                      |  2 +-
 lib/pleroma/object.ex                         |  2 +-
 lib/pleroma/upload.ex                         |  4 +-
 lib/pleroma/user.ex                           |  8 ++--
 lib/pleroma/web.ex                            |  4 --
 .../web/activity_pub/mrf/no_empty_policy.ex   |  4 +-
 lib/pleroma/web/activity_pub/publisher.ex     |  2 +-
 lib/pleroma/web/activity_pub/utils.ex         |  5 +-
 lib/pleroma/web/feed/feed_view.ex             |  4 +-
 .../controllers/search_controller.ex          |  4 +-
 .../mastodon_api/views/custom_emoji_view.ex   |  4 +-
 .../web/mastodon_api/views/instance_view.ex   |  6 +--
 .../web/mastodon_api/views/status_view.ex     |  2 +-
 lib/pleroma/web/media_proxy.ex                |  8 ++--
 .../web/nodeinfo/nodeinfo_controller.ex       |  6 +--
 .../templates/feed/feed/_activity.atom.eex    |  2 +-
 .../web/templates/feed/feed/_activity.rss.eex |  2 +-
 .../feed/feed/_tag_activity.atom.eex          |  2 +-
 .../password/reset_failed.html.eex            |  2 +-
 .../password/reset_success.html.eex           |  2 +-
 .../web/twitter_api/views/util_view.ex        |  4 +-
 lib/pleroma/web/views/masto_fe_view.ex        |  2 +-
 lib/pleroma/web/web_finger.ex                 |  4 +-
 test/pleroma/user_test.exs                    |  2 +-
 .../activity_pub/mrf/simple_policy_test.exs   |  2 +-
 .../web/activity_pub/publisher_test.exs       |  2 +-
 .../o_auth_app_controller_test.exs            |  6 +--
 .../controllers/user_controller_test.exs      |  4 +-
 test/pleroma/web/common_api_test.exs          |  2 +-
 test/pleroma/web/feed/tag_controller_test.exs |  4 +-
 .../pleroma/web/feed/user_controller_test.exs |  4 +-
 .../controllers/instance_controller_test.exs  |  4 +-
 .../controllers/search_controller_test.exs    | 46 +++++++++----------
 .../mastodon_api/views/account_view_test.exs  |  6 +--
 test/pleroma/web/media_proxy_test.exs         |  2 +-
 .../web/o_status/o_status_controller_test.exs |  2 +-
 .../web_finger/web_finger_controller_test.exs |  2 +-
 test/pleroma/web/web_finger_test.exs          |  2 +-
 43 files changed, 93 insertions(+), 96 deletions(-)

diff --git a/benchmarks/load_testing/activities.ex b/benchmarks/load_testing/activities.ex
index f5c7bfce8..b9f6b24da 100644
--- a/benchmarks/load_testing/activities.ex
+++ b/benchmarks/load_testing/activities.ex
@@ -299,7 +299,7 @@ defp insert_activity(:attachment, visibility, group, users, _opts) do
       "url" => [
         %{
           "href" =>
-            "#{Pleroma.Web.base_url()}/media/b1b873552422a07bf53af01f3c231c841db4dfc42c35efde681abaf0f2a4eab7.jpg",
+            "#{Pleroma.Web.Endpoint.url()}/media/b1b873552422a07bf53af01f3c231c841db4dfc42c35efde681abaf0f2a4eab7.jpg",
           "mediaType" => "image/jpeg",
           "type" => "Link"
         }
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index c853a2bb4..e67646a9a 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -25,7 +25,7 @@ def user_agent do
     if Process.whereis(Pleroma.Web.Endpoint) do
       case Config.get([:http, :user_agent], :default) do
         :default ->
-          info = "#{Pleroma.Web.base_url()} <#{Config.get([:instance, :email], "")}>"
+          info = "#{Pleroma.Web.Endpoint.url()} <#{Config.get([:instance, :email], "")}>"
           named_version() <> "; " <> info
 
         custom ->
diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex
index b24338cc6..bdca8279c 100644
--- a/lib/pleroma/constants.ex
+++ b/lib/pleroma/constants.ex
@@ -28,5 +28,5 @@ defmodule Pleroma.Constants do
       ~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css)
   )
 
-  def as_local_public, do: Pleroma.Web.base_url() <> "/#Public"
+  def as_local_public, do: Pleroma.Web.Endpoint.url() <> "/#Public"
 end
diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex
index 5fe74e2f7..88bc78aec 100644
--- a/lib/pleroma/emails/admin_email.ex
+++ b/lib/pleroma/emails/admin_email.ex
@@ -73,7 +73,7 @@ def report(to, reporter, account, statuses, comment) do
     #{comment_html}
     #{statuses_html}
     <p>
-    <a href="#{Pleroma.Web.base_url()}/pleroma/admin/#/reports/index">View Reports in AdminFE</a>
+    <a href="#{Pleroma.Web.Endpoint.url()}/pleroma/admin/#/reports/index">View Reports in AdminFE</a>
     """
 
     new()
@@ -87,7 +87,7 @@ def new_unapproved_registration(to, account) do
     html_body = """
     <p>New account for review: <a href="#{account.ap_id}">@#{account.nickname}</a></p>
     <blockquote>#{HTML.strip_tags(account.registration_reason)}</blockquote>
-    <a href="#{Pleroma.Web.base_url()}/pleroma/admin/#/users/#{account.id}/">Visit AdminFE</a>
+    <a href="#{Pleroma.Web.Endpoint.url()}/pleroma/admin/#/users/#{account.id}/">Visit AdminFE</a>
     """
 
     new()
diff --git a/lib/pleroma/emoji/formatter.ex b/lib/pleroma/emoji/formatter.ex
index 50150e951..191451952 100644
--- a/lib/pleroma/emoji/formatter.ex
+++ b/lib/pleroma/emoji/formatter.ex
@@ -5,7 +5,7 @@
 defmodule Pleroma.Emoji.Formatter do
   alias Pleroma.Emoji
   alias Pleroma.HTML
-  alias Pleroma.Web
+  alias Pleroma.Web.Endpoint
   alias Pleroma.Web.MediaProxy
 
   def emojify(text) do
@@ -44,7 +44,7 @@ def get_emoji_map(text) when is_binary(text) do
     Emoji.get_all()
     |> Enum.filter(fn {emoji, %Emoji{}} -> String.contains?(text, ":#{emoji}:") end)
     |> Enum.reduce(%{}, fn {name, %Emoji{file: file}}, acc ->
-      Map.put(acc, name, to_string(URI.merge(Web.base_url(), file)))
+      Map.put(acc, name, to_string(URI.merge(Endpoint.url(), file)))
     end)
   end
 
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index 7a08e48a9..535ad5f10 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -62,7 +62,7 @@ def mention_handler("@" <> nickname, buffer, opts, acc) do
 
   def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do
     tag = String.downcase(tag)
-    url = "#{Pleroma.Web.base_url()}/tag/#{tag}"
+    url = "#{Pleroma.Web.Endpoint.url()}/tag/#{tag}"
 
     link =
       Phoenix.HTML.Tag.content_tag(:a, tag_text,
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index aaf123840..f0e15f0f7 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -325,7 +325,7 @@ def update_data(%Object{data: data} = object, attrs \\ %{}) do
   end
 
   def local?(%Object{data: %{"id" => id}}) do
-    String.starts_with?(id, Pleroma.Web.base_url() <> "/")
+    String.starts_with?(id, Pleroma.Web.Endpoint.url() <> "/")
   end
 
   def replies(object, opts \\ []) do
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index 654711351..b32131bb6 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -225,7 +225,7 @@ def base_url do
 
     case uploader do
       Pleroma.Uploaders.Local ->
-        upload_base_url || Pleroma.Web.base_url() <> "/media/"
+        upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/"
 
       Pleroma.Uploaders.S3 ->
         bucket = Config.get([Pleroma.Uploaders.S3, :bucket])
@@ -251,7 +251,7 @@ def base_url do
         end
 
       _ ->
-        public_endpoint || upload_base_url || Pleroma.Web.base_url() <> "/media/"
+        public_endpoint || upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/"
     end
   end
 end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 9942617d8..4c697cb1b 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -27,13 +27,13 @@ defmodule Pleroma.User do
   alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.UserRelationship
-  alias Pleroma.Web
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Builder
   alias Pleroma.Web.ActivityPub.Pipeline
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
+  alias Pleroma.Web.Endpoint
   alias Pleroma.Web.OAuth
   alias Pleroma.Web.RelMe
   alias Pleroma.Workers.BackgroundWorker
@@ -359,7 +359,7 @@ def avatar_url(user, options \\ []) do
 
       _ ->
         unless options[:no_default] do
-          Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png")
+          Config.get([:assets, :default_user_avatar], "#{Endpoint.url()}/images/avi.png")
         end
     end
   end
@@ -367,12 +367,12 @@ def avatar_url(user, options \\ []) do
   def banner_url(user, options \\ []) do
     case user.banner do
       %{"url" => [%{"href" => href} | _]} -> href
-      _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
+      _ -> !options[:no_default] && "#{Endpoint.url()}/images/banner.png"
     end
   end
 
   # Should probably be renamed or removed
-  def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
+  def ap_id(%User{nickname: nickname}), do: "#{Endpoint.url()}/users/#{nickname}"
 
   def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
   def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex
index 397e4d1e7..d26931af9 100644
--- a/lib/pleroma/web.ex
+++ b/lib/pleroma/web.ex
@@ -231,8 +231,4 @@ def call(%Plug.Conn{} = conn, options) do
   defmacro __using__(which) when is_atom(which) do
     apply(__MODULE__, which, [])
   end
-
-  def base_url do
-    Pleroma.Web.Endpoint.url()
-  end
 end
diff --git a/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex
index 32bb1b645..f4c5db05c 100644
--- a/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do
   @moduledoc "Filter local activities which have no content"
   @behaviour Pleroma.Web.ActivityPub.MRF
 
-  alias Pleroma.Web
+  alias Pleroma.Web.Endpoint
 
   @impl true
   def filter(%{"actor" => actor} = object) do
@@ -24,7 +24,7 @@ def filter(%{"actor" => actor} = object) do
   def filter(object), do: {:ok, object}
 
   defp is_local?(actor) do
-    if actor |> String.starts_with?("#{Web.base_url()}") do
+    if actor |> String.starts_with?("#{Endpoint.url()}") do
       true
     else
       false
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index b12b2fc24..590beef64 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -272,7 +272,7 @@ def gather_webfinger_links(%User{} = user) do
       },
       %{
         "rel" => "http://ostatus.org/schema/1.0/subscribe",
-        "template" => "#{Pleroma.Web.base_url()}/ostatus_subscribe?acct={uri}"
+        "template" => "#{Pleroma.Web.Endpoint.url()}/ostatus_subscribe?acct={uri}"
       }
     ]
   end
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index a4dc469dc..0b5f496e3 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -12,7 +12,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   alias Pleroma.Object
   alias Pleroma.Repo
   alias Pleroma.User
-  alias Pleroma.Web
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.AdminAPI.AccountView
@@ -107,7 +106,7 @@ def make_json_ld_header do
     %{
       "@context" => [
         "https://www.w3.org/ns/activitystreams",
-        "#{Web.base_url()}/schemas/litepub-0.1.jsonld",
+        "#{Endpoint.url()}/schemas/litepub-0.1.jsonld",
         %{
           "@language" => "und"
         }
@@ -132,7 +131,7 @@ def generate_object_id do
   end
 
   def generate_id(type) do
-    "#{Web.base_url()}/#{type}/#{UUID.generate()}"
+    "#{Endpoint.url()}/#{type}/#{UUID.generate()}"
   end
 
   def get_notified_from_object(%{"type" => type} = object) when type in @supported_object_types do
diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex
index df97d2f46..51254ad93 100644
--- a/lib/pleroma/web/feed/feed_view.ex
+++ b/lib/pleroma/web/feed/feed_view.ex
@@ -51,10 +51,10 @@ def most_recent_update(activities, user) do
   def feed_logo do
     case Pleroma.Config.get([:feed, :logo]) do
       nil ->
-        "#{Pleroma.Web.base_url()}/static/logo.svg"
+        "#{Pleroma.Web.Endpoint.url()}/static/logo.svg"
 
       logo ->
-        "#{Pleroma.Web.base_url()}#{logo}"
+        "#{Pleroma.Web.Endpoint.url()}#{logo}"
     end
     |> MediaProxy.url()
   end
diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
index af93e453d..64b177eb3 100644
--- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
@@ -8,8 +8,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
   alias Pleroma.Activity
   alias Pleroma.Repo
   alias Pleroma.User
-  alias Pleroma.Web
   alias Pleroma.Web.ControllerHelper
+  alias Pleroma.Web.Endpoint
   alias Pleroma.Web.MastodonAPI.AccountView
   alias Pleroma.Web.MastodonAPI.StatusView
   alias Pleroma.Web.Plugs.OAuthScopesPlug
@@ -108,7 +108,7 @@ defp resource_search(_, "statuses", query, options) do
   end
 
   defp resource_search(:v2, "hashtags", query, options) do
-    tags_path = Web.base_url() <> "/tag/"
+    tags_path = Endpoint.url() <> "/tag/"
 
     query
     |> prepare_tags(options)
diff --git a/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex b/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex
index 40e314164..7d2d605e9 100644
--- a/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex
@@ -6,14 +6,14 @@ defmodule Pleroma.Web.MastodonAPI.CustomEmojiView do
   use Pleroma.Web, :view
 
   alias Pleroma.Emoji
-  alias Pleroma.Web
+  alias Pleroma.Web.Endpoint
 
   def render("index.json", %{custom_emojis: custom_emojis}) do
     render_many(custom_emojis, __MODULE__, "show.json")
   end
 
   def render("show.json", %{custom_emoji: {shortcode, %Emoji{file: relative_url, tags: tags}}}) do
-    url = Web.base_url() |> URI.merge(relative_url) |> to_string()
+    url = Endpoint.url() |> URI.merge(relative_url) |> to_string()
 
     %{
       "shortcode" => shortcode,
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
index 73205fb6d..510cac236 100644
--- a/lib/pleroma/web/mastodon_api/views/instance_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -14,7 +14,7 @@ def render("show.json", _) do
     instance = Config.get(:instance)
 
     %{
-      uri: Pleroma.Web.base_url(),
+      uri: Pleroma.Web.Endpoint.url(),
       title: Keyword.get(instance, :name),
       description: Keyword.get(instance, :description),
       version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
@@ -23,7 +23,7 @@ def render("show.json", _) do
         streaming_api: Pleroma.Web.Endpoint.websocket_url()
       },
       stats: Pleroma.Stats.get_stats(),
-      thumbnail: Pleroma.Web.base_url() <> Keyword.get(instance, :instance_thumbnail),
+      thumbnail: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :instance_thumbnail),
       languages: ["en"],
       registrations: Keyword.get(instance, :registrations_open),
       approval_required: Keyword.get(instance, :account_approval_required),
@@ -34,7 +34,7 @@ def render("show.json", _) do
       avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit),
       background_upload_limit: Keyword.get(instance, :background_upload_limit),
       banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
-      background_image: Pleroma.Web.base_url() <> Keyword.get(instance, :background_image),
+      background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image),
       chat_limit: Keyword.get(instance, :chat_limit),
       description_limit: Keyword.get(instance, :description_limit),
       pleroma: %{
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index da2cf0f95..e8de1ed28 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -485,7 +485,7 @@ def render_content(object), do: object.data["content"] || ""
   def build_tags(object_tags) when is_list(object_tags) do
     object_tags
     |> Enum.filter(&is_binary/1)
-    |> Enum.map(&%{name: &1, url: "#{Pleroma.Web.base_url()}/tag/#{URI.encode(&1)}"})
+    |> Enum.map(&%{name: &1, url: "#{Pleroma.Web.Endpoint.url()}/tag/#{URI.encode(&1)}"})
   end
 
   def build_tags(_), do: []
diff --git a/lib/pleroma/web/media_proxy.ex b/lib/pleroma/web/media_proxy.ex
index 27f337138..7df591201 100644
--- a/lib/pleroma/web/media_proxy.ex
+++ b/lib/pleroma/web/media_proxy.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.MediaProxy do
   alias Pleroma.Config
   alias Pleroma.Helpers.UriHelper
   alias Pleroma.Upload
-  alias Pleroma.Web
+  alias Pleroma.Web.Endpoint
   alias Pleroma.Web.MediaProxy.Invalidation
 
   @base64_opts [padding: false]
@@ -69,7 +69,7 @@ def enabled?, do: Config.get([:media_proxy, :enabled], false)
   #   non-local non-whitelisted URLs through it and be sure that body size constraint is preserved.
   def preview_enabled?, do: enabled?() and !!Config.get([:media_preview_proxy, :enabled])
 
-  def local?(url), do: String.starts_with?(url, Web.base_url())
+  def local?(url), do: String.starts_with?(url, Endpoint.url())
 
   def whitelisted?(url) do
     %{host: domain} = URI.parse(url)
@@ -122,7 +122,7 @@ def decode_url(sig, url) do
   end
 
   defp signed_url(url) do
-    :crypto.hmac(:sha, Config.get([Web.Endpoint, :secret_key_base]), url)
+    :crypto.hmac(:sha, Config.get([Endpoint, :secret_key_base]), url)
   end
 
   def filename(url_or_path) do
@@ -130,7 +130,7 @@ def filename(url_or_path) do
   end
 
   def base_url do
-    Config.get([:media_proxy, :base_url], Web.base_url())
+    Config.get([:media_proxy, :base_url], Endpoint.url())
   end
 
   defp proxy_url(path, sig_base64, url_base64, filename) do
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
index bca94d236..69ec27ba0 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
   use Pleroma.Web, :controller
 
-  alias Pleroma.Web
+  alias Pleroma.Web.Endpoint
   alias Pleroma.Web.Nodeinfo.Nodeinfo
 
   def schemas(conn, _params) do
@@ -13,11 +13,11 @@ def schemas(conn, _params) do
       links: [
         %{
           rel: "http://nodeinfo.diaspora.software/ns/schema/2.0",
-          href: Web.base_url() <> "/nodeinfo/2.0.json"
+          href: Endpoint.url() <> "/nodeinfo/2.0.json"
         },
         %{
           rel: "http://nodeinfo.diaspora.software/ns/schema/2.1",
-          href: Web.base_url() <> "/nodeinfo/2.1.json"
+          href: Endpoint.url() <> "/nodeinfo/2.1.json"
         }
       ]
     }
diff --git a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
index 3fd150c4e..ca31223fc 100644
--- a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
@@ -38,7 +38,7 @@
     <%= if id == Pleroma.Constants.as_public() do %>
       <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
     <% else %>
-      <%= unless Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) do %>
+      <%= unless Regex.match?(~r/^#{Pleroma.Web.Endpoint.url()}.+followers$/, id) do %>
         <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="<%= id %>"/>
       <% end %>
     <% end %>
diff --git a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
index 947bbb099..01dddba07 100644
--- a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
+++ b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
@@ -38,7 +38,7 @@
     <%= if id == Pleroma.Constants.as_public() do %>
       <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection">http://activityschema.org/collection/public</link>
     <% else %>
-      <%= unless Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) do %>
+      <%= unless Regex.match?(~r/^#{Pleroma.Web.Endpoint.url()}.+followers$/, id) do %>
         <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person"><%= id %></link>
       <% end %>
     <% end %>
diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex
index cf5874a91..9ae28b48a 100644
--- a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex
@@ -33,7 +33,7 @@
           ostatus:object-type="http://activitystrea.ms/schema/1.0/collection"
           href="http://activityschema.org/collection/public"/>
       <% else %>
-        <%= unless Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) do %>
+        <%= unless Regex.match?(~r/^#{Pleroma.Web.Endpoint.url()}.+followers$/, id) do %>
           <link rel="mentioned"
             ostatus:object-type="http://activitystrea.ms/schema/1.0/person"
             href="<%= id %>" />
diff --git a/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex
index df037c01e..4ed4ac8bc 100644
--- a/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex
@@ -1,2 +1,2 @@
 <h2>Password reset failed</h2>
-<h3><a href="<%= Pleroma.Web.base_url() %>">Homepage</a></h3>
+<h3><a href="<%= Pleroma.Web.Endpoint.url() %>">Homepage</a></h3>
diff --git a/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex
index f30ba3274..086d4e08b 100644
--- a/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex
@@ -1,2 +1,2 @@
 <h2>Password changed!</h2>
-<h3><a href="<%= Pleroma.Web.base_url() %>">Homepage</a></h3>
+<h3><a href="<%= Pleroma.Web.Endpoint.url() %>">Homepage</a></h3>
diff --git a/lib/pleroma/web/twitter_api/views/util_view.ex b/lib/pleroma/web/twitter_api/views/util_view.ex
index 9b13c09b3..87cb79dd7 100644
--- a/lib/pleroma/web/twitter_api/views/util_view.ex
+++ b/lib/pleroma/web/twitter_api/views/util_view.ex
@@ -6,14 +6,14 @@ defmodule Pleroma.Web.TwitterAPI.UtilView do
   use Pleroma.Web, :view
   import Phoenix.HTML.Form
   alias Pleroma.Config
-  alias Pleroma.Web
+  alias Pleroma.Web.Endpoint
 
   def status_net_config(instance) do
     """
     <config>
     <site>
     <name>#{Keyword.get(instance, :name)}</name>
-    <site>#{Web.base_url()}</site>
+    <site>#{Endpoint.url()}</site>
     <textlimit>#{Keyword.get(instance, :limit)}</textlimit>
     <closed>#{!Keyword.get(instance, :registrations_open)}</closed>
     </site>
diff --git a/lib/pleroma/web/views/masto_fe_view.ex b/lib/pleroma/web/views/masto_fe_view.ex
index 82b301949..63a9c8179 100644
--- a/lib/pleroma/web/views/masto_fe_view.ex
+++ b/lib/pleroma/web/views/masto_fe_view.ex
@@ -78,7 +78,7 @@ def render("manifest.json", _params) do
       theme_color: Config.get([:manifest, :theme_color]),
       background_color: Config.get([:manifest, :background_color]),
       display: "standalone",
-      scope: Pleroma.Web.base_url(),
+      scope: Pleroma.Web.Endpoint.url(),
       start_url: Routes.masto_fe_path(Pleroma.Web.Endpoint, :index, ["getting-started"]),
       categories: [
         "social"
diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex
index 15002b29f..74b236aba 100644
--- a/lib/pleroma/web/web_finger.ex
+++ b/lib/pleroma/web/web_finger.ex
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.WebFinger do
   alias Pleroma.HTTP
   alias Pleroma.User
-  alias Pleroma.Web
+  alias Pleroma.Web.Endpoint
   alias Pleroma.Web.Federator.Publisher
   alias Pleroma.Web.XML
   alias Pleroma.XmlBuilder
@@ -13,7 +13,7 @@ defmodule Pleroma.Web.WebFinger do
   require Logger
 
   def host_meta do
-    base_url = Web.base_url()
+    base_url = Endpoint.url()
 
     {
       :XRD,
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index 6f5bcab57..79c7d7ed1 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -151,7 +151,7 @@ test "untagging a user" do
   test "ap_id returns the activity pub id for the user" do
     user = UserBuilder.build()
 
-    expected_ap_id = "#{Pleroma.Web.base_url()}/users/#{user.nickname}"
+    expected_ap_id = "#{Pleroma.Web.Endpoint.url()}/users/#{user.nickname}"
 
     assert expected_ap_id == User.ap_id(user)
   end
diff --git a/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs b/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs
index f48e5b39b..ebd38caca 100644
--- a/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs
+++ b/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs
@@ -504,7 +504,7 @@ test "it rejects the deletion" do
 
   defp build_local_message do
     %{
-      "actor" => "#{Pleroma.Web.base_url()}/users/alice",
+      "actor" => "#{Pleroma.Web.Endpoint.url()}/users/alice",
       "to" => [],
       "cc" => []
     }
diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs
index f0ce3d7f2..89f3ad411 100644
--- a/test/pleroma/web/activity_pub/publisher_test.exs
+++ b/test/pleroma/web/activity_pub/publisher_test.exs
@@ -38,7 +38,7 @@ test "it returns links" do
         },
         %{
           "rel" => "http://ostatus.org/schema/1.0/subscribe",
-          "template" => "#{Pleroma.Web.base_url()}/ostatus_subscribe?acct={uri}"
+          "template" => "#{Pleroma.Web.Endpoint.url()}/ostatus_subscribe?acct={uri}"
         }
       ]
 
diff --git a/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs b/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs
index 8c7b63f34..d9b25719a 100644
--- a/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs
@@ -8,7 +8,7 @@ defmodule Pleroma.Web.AdminAPI.OAuthAppControllerTest do
 
   import Pleroma.Factory
 
-  alias Pleroma.Web
+  alias Pleroma.Web.Endpoint
 
   setup do
     admin = insert(:user, is_admin: true)
@@ -36,7 +36,7 @@ test "errors", %{conn: conn} do
     end
 
     test "success", %{conn: conn} do
-      base_url = Web.base_url()
+      base_url = Endpoint.url()
       app_name = "Trusted app"
 
       response =
@@ -58,7 +58,7 @@ test "success", %{conn: conn} do
     end
 
     test "with trusted", %{conn: conn} do
-      base_url = Web.base_url()
+      base_url = Endpoint.url()
       app_name = "Trusted app"
 
       response =
diff --git a/test/pleroma/web/admin_api/controllers/user_controller_test.exs b/test/pleroma/web/admin_api/controllers/user_controller_test.exs
index beb8a5d58..af295be42 100644
--- a/test/pleroma/web/admin_api/controllers/user_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/user_controller_test.exs
@@ -14,9 +14,9 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
   alias Pleroma.Repo
   alias Pleroma.Tests.ObanHelpers
   alias Pleroma.User
-  alias Pleroma.Web
   alias Pleroma.Web.ActivityPub.Relay
   alias Pleroma.Web.CommonAPI
+  alias Pleroma.Web.Endpoint
   alias Pleroma.Web.MediaProxy
 
   setup_all do
@@ -403,7 +403,7 @@ test "renders users array for the first page", %{conn: conn, admin: admin} do
     end
 
     test "pagination works correctly with service users", %{conn: conn} do
-      service1 = User.get_or_create_service_actor_by_ap_id(Web.base_url() <> "/meido", "meido")
+      service1 = User.get_or_create_service_actor_by_ap_id(Endpoint.url() <> "/meido", "meido")
 
       insert_list(25, :user)
 
diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs
index adfe58def..5ab3a48ad 100644
--- a/test/pleroma/web/common_api_test.exs
+++ b/test/pleroma/web/common_api_test.exs
@@ -514,7 +514,7 @@ test "it adds an emoji on an external site" do
       {:ok, activity} = CommonAPI.post(user, %{status: "hey :blank:"})
 
       assert %{"blank" => url} = Object.normalize(activity).data["emoji"]
-      assert url == "#{Pleroma.Web.base_url()}/emoji/blank.png"
+      assert url == "#{Pleroma.Web.Endpoint.url()}/emoji/blank.png"
     end
 
     test "deactivated users can't post" do
diff --git a/test/pleroma/web/feed/tag_controller_test.exs b/test/pleroma/web/feed/tag_controller_test.exs
index 5c9201de1..140cdb8bf 100644
--- a/test/pleroma/web/feed/tag_controller_test.exs
+++ b/test/pleroma/web/feed/tag_controller_test.exs
@@ -127,10 +127,10 @@ test "gets a feed (RSS)", %{conn: conn} do
              "These are public toots tagged with #pleromaart. You can interact with them if you have an account anywhere in the fediverse."
 
     assert xpath(xml, ~x"//channel/link/text()") ==
-             '#{Pleroma.Web.base_url()}/tags/pleromaart.rss'
+             '#{Pleroma.Web.Endpoint.url()}/tags/pleromaart.rss'
 
     assert xpath(xml, ~x"//channel/webfeeds:logo/text()") ==
-             '#{Pleroma.Web.base_url()}/static/logo.svg'
+             '#{Pleroma.Web.Endpoint.url()}/static/logo.svg'
 
     assert xpath(xml, ~x"//channel/item/title/text()"l) == [
              '42 This is :moominmamm...',
diff --git a/test/pleroma/web/feed/user_controller_test.exs b/test/pleroma/web/feed/user_controller_test.exs
index 408653d92..6f6ff433f 100644
--- a/test/pleroma/web/feed/user_controller_test.exs
+++ b/test/pleroma/web/feed/user_controller_test.exs
@@ -217,7 +217,9 @@ test "with non-html / non-json format, it redirects to user feed in atom format"
         |> get("/users/#{user.nickname}")
 
       assert conn.status == 302
-      assert redirected_to(conn) == "#{Pleroma.Web.base_url()}/users/#{user.nickname}/feed.atom"
+
+      assert redirected_to(conn) ==
+               "#{Pleroma.Web.Endpoint.url()}/users/#{user.nickname}/feed.atom"
     end
 
     test "with non-html / non-json format, it returns error when user is not found", %{conn: conn} do
diff --git a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs
index b99856659..f137743be 100644
--- a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs
@@ -14,8 +14,8 @@ test "get instance information", %{conn: conn} do
     assert result = json_response_and_validate_schema(conn, 200)
 
     email = Pleroma.Config.get([:instance, :email])
-    thumbnail = Pleroma.Web.base_url() <> Pleroma.Config.get([:instance, :instance_thumbnail])
-    background = Pleroma.Web.base_url() <> Pleroma.Config.get([:instance, :background_image])
+    thumbnail = Pleroma.Web.Endpoint.url() <> Pleroma.Config.get([:instance, :instance_thumbnail])
+    background = Pleroma.Web.Endpoint.url() <> Pleroma.Config.get([:instance, :background_image])
 
     # Note: not checking for "max_toot_chars" since it's optional
     assert %{
diff --git a/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs
index 1dd0fa3b8..7b0bbd8bd 100644
--- a/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs
@@ -6,8 +6,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
   use Pleroma.Web.ConnCase
 
   alias Pleroma.Object
-  alias Pleroma.Web
   alias Pleroma.Web.CommonAPI
+  alias Pleroma.Web.Endpoint
   import Pleroma.Factory
   import ExUnit.CaptureLog
   import Tesla.Mock
@@ -61,7 +61,7 @@ test "search", %{conn: conn} do
       assert account["id"] == to_string(user_three.id)
 
       assert results["hashtags"] == [
-               %{"name" => "private", "url" => "#{Web.base_url()}/tag/private"}
+               %{"name" => "private", "url" => "#{Endpoint.url()}/tag/private"}
              ]
 
       [status] = results["statuses"]
@@ -72,7 +72,7 @@ test "search", %{conn: conn} do
         |> json_response_and_validate_schema(200)
 
       assert results["hashtags"] == [
-               %{"name" => "天子", "url" => "#{Web.base_url()}/tag/天子"}
+               %{"name" => "天子", "url" => "#{Endpoint.url()}/tag/天子"}
              ]
 
       [status] = results["statuses"]
@@ -87,8 +87,8 @@ test "constructs hashtags from search query", %{conn: conn} do
         |> json_response_and_validate_schema(200)
 
       assert results["hashtags"] == [
-               %{"name" => "explicit", "url" => "#{Web.base_url()}/tag/explicit"},
-               %{"name" => "hashtags", "url" => "#{Web.base_url()}/tag/hashtags"}
+               %{"name" => "explicit", "url" => "#{Endpoint.url()}/tag/explicit"},
+               %{"name" => "hashtags", "url" => "#{Endpoint.url()}/tag/hashtags"}
              ]
 
       results =
@@ -97,9 +97,9 @@ test "constructs hashtags from search query", %{conn: conn} do
         |> json_response_and_validate_schema(200)
 
       assert results["hashtags"] == [
-               %{"name" => "john", "url" => "#{Web.base_url()}/tag/john"},
-               %{"name" => "doe", "url" => "#{Web.base_url()}/tag/doe"},
-               %{"name" => "JohnDoe", "url" => "#{Web.base_url()}/tag/JohnDoe"}
+               %{"name" => "john", "url" => "#{Endpoint.url()}/tag/john"},
+               %{"name" => "doe", "url" => "#{Endpoint.url()}/tag/doe"},
+               %{"name" => "JohnDoe", "url" => "#{Endpoint.url()}/tag/JohnDoe"}
              ]
 
       results =
@@ -108,9 +108,9 @@ test "constructs hashtags from search query", %{conn: conn} do
         |> json_response_and_validate_schema(200)
 
       assert results["hashtags"] == [
-               %{"name" => "accident", "url" => "#{Web.base_url()}/tag/accident"},
-               %{"name" => "prone", "url" => "#{Web.base_url()}/tag/prone"},
-               %{"name" => "AccidentProne", "url" => "#{Web.base_url()}/tag/AccidentProne"}
+               %{"name" => "accident", "url" => "#{Endpoint.url()}/tag/accident"},
+               %{"name" => "prone", "url" => "#{Endpoint.url()}/tag/prone"},
+               %{"name" => "AccidentProne", "url" => "#{Endpoint.url()}/tag/AccidentProne"}
              ]
 
       results =
@@ -119,7 +119,7 @@ test "constructs hashtags from search query", %{conn: conn} do
         |> json_response_and_validate_schema(200)
 
       assert results["hashtags"] == [
-               %{"name" => "shpuld", "url" => "#{Web.base_url()}/tag/shpuld"}
+               %{"name" => "shpuld", "url" => "#{Endpoint.url()}/tag/shpuld"}
              ]
 
       results =
@@ -136,18 +136,18 @@ test "constructs hashtags from search query", %{conn: conn} do
         |> json_response_and_validate_schema(200)
 
       assert results["hashtags"] == [
-               %{"name" => "nascar", "url" => "#{Web.base_url()}/tag/nascar"},
-               %{"name" => "ban", "url" => "#{Web.base_url()}/tag/ban"},
-               %{"name" => "display", "url" => "#{Web.base_url()}/tag/display"},
-               %{"name" => "confederate", "url" => "#{Web.base_url()}/tag/confederate"},
-               %{"name" => "flag", "url" => "#{Web.base_url()}/tag/flag"},
-               %{"name" => "all", "url" => "#{Web.base_url()}/tag/all"},
-               %{"name" => "events", "url" => "#{Web.base_url()}/tag/events"},
-               %{"name" => "properties", "url" => "#{Web.base_url()}/tag/properties"},
+               %{"name" => "nascar", "url" => "#{Endpoint.url()}/tag/nascar"},
+               %{"name" => "ban", "url" => "#{Endpoint.url()}/tag/ban"},
+               %{"name" => "display", "url" => "#{Endpoint.url()}/tag/display"},
+               %{"name" => "confederate", "url" => "#{Endpoint.url()}/tag/confederate"},
+               %{"name" => "flag", "url" => "#{Endpoint.url()}/tag/flag"},
+               %{"name" => "all", "url" => "#{Endpoint.url()}/tag/all"},
+               %{"name" => "events", "url" => "#{Endpoint.url()}/tag/events"},
+               %{"name" => "properties", "url" => "#{Endpoint.url()}/tag/properties"},
                %{
                  "name" => "NascarBanDisplayConfederateFlagAllEventsProperties",
                  "url" =>
-                   "#{Web.base_url()}/tag/NascarBanDisplayConfederateFlagAllEventsProperties"
+                   "#{Endpoint.url()}/tag/NascarBanDisplayConfederateFlagAllEventsProperties"
                }
              ]
     end
@@ -163,8 +163,8 @@ test "supports pagination of hashtags search results", %{conn: conn} do
         |> json_response_and_validate_schema(200)
 
       assert results["hashtags"] == [
-               %{"name" => "text", "url" => "#{Web.base_url()}/tag/text"},
-               %{"name" => "with", "url" => "#{Web.base_url()}/tag/with"}
+               %{"name" => "text", "url" => "#{Endpoint.url()}/tag/text"},
+               %{"name" => "with", "url" => "#{Endpoint.url()}/tag/with"}
              ]
     end
 
diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs
index 5373a17c3..28eb4f1d0 100644
--- a/test/pleroma/web/mastodon_api/views/account_view_test.exs
+++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs
@@ -562,12 +562,12 @@ test "uses mediaproxy urls when it's enabled (regardless of media preview proxy
       AccountView.render("show.json", %{user: user, skip_visibility_check: true})
       |> Enum.all?(fn
         {key, url} when key in [:avatar, :avatar_static, :header, :header_static] ->
-          String.starts_with?(url, Pleroma.Web.base_url())
+          String.starts_with?(url, Pleroma.Web.Endpoint.url())
 
         {:emojis, emojis} ->
           Enum.all?(emojis, fn %{url: url, static_url: static_url} ->
-            String.starts_with?(url, Pleroma.Web.base_url()) &&
-              String.starts_with?(static_url, Pleroma.Web.base_url())
+            String.starts_with?(url, Pleroma.Web.Endpoint.url()) &&
+              String.starts_with?(static_url, Pleroma.Web.Endpoint.url())
           end)
 
         _ ->
diff --git a/test/pleroma/web/media_proxy_test.exs b/test/pleroma/web/media_proxy_test.exs
index 7411d0a7a..254ac3266 100644
--- a/test/pleroma/web/media_proxy_test.exs
+++ b/test/pleroma/web/media_proxy_test.exs
@@ -42,7 +42,7 @@ test "encodes and decodes URL" do
 
       assert String.starts_with?(
                encoded,
-               Config.get([:media_proxy, :base_url], Pleroma.Web.base_url())
+               Config.get([:media_proxy, :base_url], Pleroma.Web.Endpoint.url())
              )
 
       assert String.ends_with?(encoded, "/logo.png")
diff --git a/test/pleroma/web/o_status/o_status_controller_test.exs b/test/pleroma/web/o_status/o_status_controller_test.exs
index 2038f4ddd..81d669837 100644
--- a/test/pleroma/web/o_status/o_status_controller_test.exs
+++ b/test/pleroma/web/o_status/o_status_controller_test.exs
@@ -182,7 +182,7 @@ test "render html for redirect for html format", %{conn: conn} do
         |> response(200)
 
       assert resp =~
-               "<meta content=\"#{Pleroma.Web.base_url()}/notice/#{note_activity.id}\" property=\"og:url\">"
+               "<meta content=\"#{Pleroma.Web.Endpoint.url()}/notice/#{note_activity.id}\" property=\"og:url\">"
 
       user = insert(:user)
 
diff --git a/test/pleroma/web/web_finger/web_finger_controller_test.exs b/test/pleroma/web/web_finger/web_finger_controller_test.exs
index 7059850bd..2421c5800 100644
--- a/test/pleroma/web/web_finger/web_finger_controller_test.exs
+++ b/test/pleroma/web/web_finger/web_finger_controller_test.exs
@@ -25,7 +25,7 @@ test "GET host-meta" do
 
     assert response.resp_body ==
              ~s(<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" template="#{
-               Pleroma.Web.base_url()
+               Pleroma.Web.Endpoint.url()
              }/.well-known/webfinger?resource={uri}" type="application/xrd+xml" /></XRD>)
   end
 
diff --git a/test/pleroma/web/web_finger_test.exs b/test/pleroma/web/web_finger_test.exs
index 84477d5a1..7b90c5457 100644
--- a/test/pleroma/web/web_finger_test.exs
+++ b/test/pleroma/web/web_finger_test.exs
@@ -17,7 +17,7 @@ defmodule Pleroma.Web.WebFingerTest do
     test "returns a link to the xml lrdd" do
       host_info = WebFinger.host_meta()
 
-      assert String.contains?(host_info, Pleroma.Web.base_url())
+      assert String.contains?(host_info, Pleroma.Web.Endpoint.url())
     end
   end
 

From ff00b354fa5067c898e860e275748dd757cb04cd Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Mon, 3 Aug 2020 17:08:35 -0500
Subject: [PATCH 235/339] Rename the non-federating Chat feature to Shout

---
 CHANGELOG.md                                       |  1 +
 config/config.exs                                  |  4 ++--
 config/description.exs                             |  8 ++++----
 lib/pleroma/config/transfer_task.ex                |  2 +-
 lib/pleroma/web/channels/user_socket.ex            |  4 ++--
 .../web/mastodon_api/views/instance_view.ex        |  6 +++---
 .../web/{chat_channel.ex => shout_channel.ex}      | 14 +++++++-------
 test/pleroma/config/transfer_task_test.exs         |  8 ++++----
 .../controllers/config_controller_test.exs         | 12 ++++++------
 .../controllers/instance_controller_test.exs       |  2 +-
 ...chat_channel_test.exs => shout_channel_test.ex} | 10 +++++-----
 11 files changed, 36 insertions(+), 35 deletions(-)
 rename lib/pleroma/web/{chat_channel.ex => shout_channel.ex} (78%)
 rename test/pleroma/web/{chat_channel_test.exs => shout_channel_test.ex} (80%)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index feac7b1c3..1c08710a3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ### Changed
 
+- **Breaking:** Configuration: `:chat, enabled` moved to `:shout, enabled` and `:instance, chat_limit` moved to `:instance, shout_limit`
 - The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
 - HTTPSecurityPlug now sends a response header to opt out of Google's FLoC (Federated Learning of Cohorts) targeted advertising.
 - Email address is now returned if requesting user is the owner of the user account so it can be exposed in client and FE user settings UIs.
diff --git a/config/config.exs b/config/config.exs
index d333c618e..8c8ed5224 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -190,7 +190,7 @@
   instance_thumbnail: "/instance/thumbnail.jpeg",
   limit: 5_000,
   description_limit: 5_000,
-  chat_limit: 5_000,
+  shout_limit: 5_000,
   remote_limit: 100_000,
   upload_limit: 16_000_000,
   avatar_upload_limit: 2_000_000,
@@ -457,7 +457,7 @@
   image_quality: 85,
   min_content_length: 100 * 1024
 
-config :pleroma, :chat, enabled: true
+config :pleroma, :shoutbox, enabled: true
 
 config :phoenix, :format_encoders, json: Jason
 
diff --git a/config/description.exs b/config/description.exs
index f00c53d28..040deab96 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -545,9 +545,9 @@
         ]
       },
       %{
-        key: :chat_limit,
+        key: :shout_limit,
         type: :integer,
-        description: "Character limit of the instance chat messages",
+        description: "Character limit of the instance shout messages",
         suggestions: [
           5_000
         ]
@@ -2652,9 +2652,9 @@
   },
   %{
     group: :pleroma,
-    key: :chat,
+    key: :shout,
     type: :group,
-    description: "Pleroma chat settings",
+    description: "Pleroma shout settings",
     children: [
       %{
         key: :enabled,
diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex
index 1e3ae82d0..d5c6081a2 100644
--- a/lib/pleroma/config/transfer_task.ex
+++ b/lib/pleroma/config/transfer_task.ex
@@ -16,7 +16,7 @@ defmodule Pleroma.Config.TransferTask do
   defp reboot_time_keys,
     do: [
       {:pleroma, :hackney_pools},
-      {:pleroma, :chat},
+      {:pleroma, :shout},
       {:pleroma, Oban},
       {:pleroma, :rate_limit},
       {:pleroma, :markup},
diff --git a/lib/pleroma/web/channels/user_socket.ex b/lib/pleroma/web/channels/user_socket.ex
index 1c09b6768..130809bb7 100644
--- a/lib/pleroma/web/channels/user_socket.ex
+++ b/lib/pleroma/web/channels/user_socket.ex
@@ -8,7 +8,7 @@ defmodule Pleroma.Web.UserSocket do
 
   ## Channels
   # channel "room:*", Pleroma.Web.RoomChannel
-  channel("chat:*", Pleroma.Web.ChatChannel)
+  channel("shout:*", Pleroma.Web.ShoutChannel)
 
   # Socket params are passed from the client and can
   # be used to verify and authenticate a user. After
@@ -22,7 +22,7 @@ defmodule Pleroma.Web.UserSocket do
   # See `Phoenix.Token` documentation for examples in
   # performing token verification on connect.
   def connect(%{"token" => token}, socket) do
-    with true <- Pleroma.Config.get([:chat, :enabled]),
+    with true <- Pleroma.Config.get([:shout, :enabled]),
          {:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84_600),
          %User{} = user <- Pleroma.User.get_cached_by_id(user_id) do
       {:ok, assign(socket, :user_name, user.nickname)}
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
index 005705d97..75964f176 100644
--- a/lib/pleroma/web/mastodon_api/views/instance_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -37,7 +37,7 @@ def render("show.json", _) do
       background_upload_limit: Keyword.get(instance, :background_upload_limit),
       banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
       background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image),
-      chat_limit: Keyword.get(instance, :chat_limit),
+      shout_limit: Keyword.get(instance, :shout_limit),
       description_limit: Keyword.get(instance, :description_limit),
       pleroma: %{
         metadata: %{
@@ -69,8 +69,8 @@ def features do
       if Config.get([:gopher, :enabled]) do
         "gopher"
       end,
-      if Config.get([:chat, :enabled]) do
-        "chat"
+      if Config.get([:shout, :enabled]) do
+        "shout"
       end,
       if Config.get([:instance, :allow_relay]) do
         "relay"
diff --git a/lib/pleroma/web/chat_channel.ex b/lib/pleroma/web/shout_channel.ex
similarity index 78%
rename from lib/pleroma/web/chat_channel.ex
rename to lib/pleroma/web/shout_channel.ex
index 4008129e9..1d97858d6 100644
--- a/lib/pleroma/web/chat_channel.ex
+++ b/lib/pleroma/web/shout_channel.ex
@@ -2,31 +2,31 @@
 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.Web.ChatChannel do
+defmodule Pleroma.Web.ShoutChannel do
   use Phoenix.Channel
 
   alias Pleroma.User
-  alias Pleroma.Web.ChatChannel.ChatChannelState
   alias Pleroma.Web.MastodonAPI.AccountView
+  alias Pleroma.Web.ShoutChannel.ShoutChannelState
 
-  def join("chat:public", _message, socket) do
+  def join("shout:public", _message, socket) do
     send(self(), :after_join)
     {:ok, socket}
   end
 
   def handle_info(:after_join, socket) do
-    push(socket, "messages", %{messages: ChatChannelState.messages()})
+    push(socket, "messages", %{messages: ShoutChannelState.messages()})
     {:noreply, socket}
   end
 
   def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} = socket) do
     text = String.trim(text)
 
-    if String.length(text) in 1..Pleroma.Config.get([:instance, :chat_limit]) do
+    if String.length(text) in 1..Pleroma.Config.get([:instance, :shout_limit]) do
       author = User.get_cached_by_nickname(user_name)
       author_json = AccountView.render("show.json", user: author, skip_visibility_check: true)
 
-      message = ChatChannelState.add_message(%{text: text, author: author_json})
+      message = ShoutChannelState.add_message(%{text: text, author: author_json})
 
       broadcast!(socket, "new_msg", message)
     end
@@ -35,7 +35,7 @@ def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}}
   end
 end
 
-defmodule Pleroma.Web.ChatChannel.ChatChannelState do
+defmodule Pleroma.Web.ShoutChannel.ShoutChannelState do
   use Agent
 
   @max_messages 20
diff --git a/test/pleroma/config/transfer_task_test.exs b/test/pleroma/config/transfer_task_test.exs
index 8ae5d3b81..7d51fd84c 100644
--- a/test/pleroma/config/transfer_task_test.exs
+++ b/test/pleroma/config/transfer_task_test.exs
@@ -93,8 +93,8 @@ test "don't restart if no reboot time settings were changed" do
     end
 
     test "on reboot time key" do
-      clear_config(:chat)
-      insert(:config, key: :chat, value: [enabled: false])
+      clear_config(:shout)
+      insert(:config, key: :shout, value: [enabled: false])
       assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted"
     end
 
@@ -105,10 +105,10 @@ test "on reboot time subkey" do
     end
 
     test "don't restart pleroma on reboot time key and subkey if there is false flag" do
-      clear_config(:chat)
+      clear_config(:shout)
       clear_config(Pleroma.Captcha)
 
-      insert(:config, key: :chat, value: [enabled: false])
+      insert(:config, key: :shout, value: [enabled: false])
       insert(:config, key: Pleroma.Captcha, value: [seconds_valid: 60])
 
       refute String.contains?(
diff --git a/test/pleroma/web/admin_api/controllers/config_controller_test.exs b/test/pleroma/web/admin_api/controllers/config_controller_test.exs
index c39c1b1e1..d8ca07cd3 100644
--- a/test/pleroma/web/admin_api/controllers/config_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/config_controller_test.exs
@@ -409,7 +409,7 @@ test "saving config with partial update", %{conn: conn} do
     end
 
     test "saving config which need pleroma reboot", %{conn: conn} do
-      clear_config([:chat, :enabled], true)
+      clear_config([:shout, :enabled], true)
 
       assert conn
              |> put_req_header("content-type", "application/json")
@@ -417,7 +417,7 @@ test "saving config which need pleroma reboot", %{conn: conn} do
                "/api/pleroma/admin/config",
                %{
                  configs: [
-                   %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
+                   %{group: ":pleroma", key: ":shout", value: [%{"tuple" => [":enabled", true]}]}
                  ]
                }
              )
@@ -426,7 +426,7 @@ test "saving config which need pleroma reboot", %{conn: conn} do
                  %{
                    "db" => [":enabled"],
                    "group" => ":pleroma",
-                   "key" => ":chat",
+                   "key" => ":shout",
                    "value" => [%{"tuple" => [":enabled", true]}]
                  }
                ],
@@ -454,7 +454,7 @@ test "saving config which need pleroma reboot", %{conn: conn} do
     end
 
     test "update setting which need reboot, don't change reboot flag until reboot", %{conn: conn} do
-      clear_config([:chat, :enabled], true)
+      clear_config([:shout, :enabled], true)
 
       assert conn
              |> put_req_header("content-type", "application/json")
@@ -462,7 +462,7 @@ test "update setting which need reboot, don't change reboot flag until reboot",
                "/api/pleroma/admin/config",
                %{
                  configs: [
-                   %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
+                   %{group: ":pleroma", key: ":shout", value: [%{"tuple" => [":enabled", true]}]}
                  ]
                }
              )
@@ -471,7 +471,7 @@ test "update setting which need reboot, don't change reboot flag until reboot",
                  %{
                    "db" => [":enabled"],
                    "group" => ":pleroma",
-                   "key" => ":chat",
+                   "key" => ":shout",
                    "value" => [%{"tuple" => [":enabled", true]}]
                  }
                ],
diff --git a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs
index f137743be..e76cbc75b 100644
--- a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs
@@ -38,7 +38,7 @@ test "get instance information", %{conn: conn} do
              "background_upload_limit" => _,
              "banner_upload_limit" => _,
              "background_image" => from_config_background,
-             "chat_limit" => _,
+             "shout_limit" => _,
              "description_limit" => _
            } = result
 
diff --git a/test/pleroma/web/chat_channel_test.exs b/test/pleroma/web/shout_channel_test.ex
similarity index 80%
rename from test/pleroma/web/chat_channel_test.exs
rename to test/pleroma/web/shout_channel_test.ex
index 29999701c..ba6730ceb 100644
--- a/test/pleroma/web/chat_channel_test.exs
+++ b/test/pleroma/web/shout_channel_test.ex
@@ -2,9 +2,9 @@
 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.Web.ChatChannelTest do
+defmodule Pleroma.Web.ShoutChannelTest do
   use Pleroma.Web.ChannelCase
-  alias Pleroma.Web.ChatChannel
+  alias Pleroma.Web.ShoutChannel
   alias Pleroma.Web.UserSocket
 
   import Pleroma.Factory
@@ -14,7 +14,7 @@ defmodule Pleroma.Web.ChatChannelTest do
 
     {:ok, _, socket} =
       socket(UserSocket, "", %{user_name: user.nickname})
-      |> subscribe_and_join(ChatChannel, "chat:public")
+      |> subscribe_and_join(ShoutChannel, "shout:public")
 
     {:ok, socket: socket}
   end
@@ -25,7 +25,7 @@ test "it broadcasts a message", %{socket: socket} do
   end
 
   describe "message lengths" do
-    setup do: clear_config([:instance, :chat_limit])
+    setup do: clear_config([:instance, :shout_limit])
 
     test "it ignores messages of length zero", %{socket: socket} do
       push(socket, "new_msg", %{"text" => ""})
@@ -33,7 +33,7 @@ test "it ignores messages of length zero", %{socket: socket} do
     end
 
     test "it ignores messages above a certain length", %{socket: socket} do
-      clear_config([:instance, :chat_limit], 2)
+      Pleroma.Config.put([:instance, :shout_limit], 2)
       push(socket, "new_msg", %{"text" => "123"})
       refute_broadcast("new_msg", %{text: "123"})
     end

From 68aa56b9e4fbf4697d5ff0c41cf2fe7230738fe6 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Mon, 3 Aug 2020 17:58:27 -0500
Subject: [PATCH 236/339] Just call it shout

---
 config/config.exs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/config/config.exs b/config/config.exs
index 8c8ed5224..e2bf0cfc1 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -457,7 +457,7 @@
   image_quality: 85,
   min_content_length: 100 * 1024
 
-config :pleroma, :shoutbox, enabled: true
+config :pleroma, :shout, enabled: true
 
 config :phoenix, :format_encoders, json: Jason
 

From 36fe8950f78b32e4ae8b845c099498a9acda7183 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Mon, 3 Aug 2020 18:00:16 -0500
Subject: [PATCH 237/339] Update PleromaFE settings for the old chat box

---
 config/description.exs | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/config/description.exs b/config/description.exs
index 040deab96..a17d222ce 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -1182,7 +1182,7 @@
             alwaysShowSubjectInput: true,
             background: "/static/aurora_borealis.jpg",
             collapseMessageWithSubject: false,
-            disableChat: false,
+            disableShout: false,
             greentext: false,
             hideFilteredStatuses: false,
             hideMutedPosts: false,
@@ -1230,10 +1230,10 @@
               "When a message has a subject (aka Content Warning), collapse it by default"
           },
           %{
-            key: :disableChat,
-            label: "PleromaFE Chat",
+            key: :disableShout,
+            label: "PleromaFE Shout",
             type: :boolean,
-            description: "Disables PleromaFE Chat component"
+            description: "Disables PleromaFE Shout component"
           },
           %{
             key: :greentext,

From a3cff596592ae70701afae2e293eab03fe5408df Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Mon, 3 Aug 2020 18:34:58 -0500
Subject: [PATCH 238/339] Ensure we actually start ShoutChannel

---
 lib/pleroma/application.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index f4d22373a..afb8cfb8a 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -239,7 +239,7 @@ defp background_migrators do
 
   defp chat_child(true) do
     [
-      Pleroma.Web.ChatChannel.ChatChannelState,
+      Pleroma.Web.ShoutChannel.ShoutChannelState,
       {Phoenix.PubSub, [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]}
     ]
   end

From 4a181982c34c774c9ed4b76ce1d95f6c33fce9d5 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Mon, 3 Aug 2020 18:41:49 -0500
Subject: [PATCH 239/339] More confusingly named legacy chat code renamed to
 shout

---
 lib/pleroma/application.ex | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index afb8cfb8a..9824e0a4a 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -102,7 +102,7 @@ def start(_type, _args) do
         ] ++
         task_children(@mix_env) ++
         dont_run_in_test(@mix_env) ++
-        chat_child(chat_enabled?()) ++
+        shout_child(shout_enabled?()) ++
         [Pleroma.Gopher.Server]
 
     # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
@@ -216,7 +216,7 @@ def build_cachex(type, opts),
       type: :worker
     }
 
-  defp chat_enabled?, do: Config.get([:chat, :enabled])
+  defp shout_enabled?, do: Config.get([:shout, :enabled])
 
   defp dont_run_in_test(env) when env in [:test, :benchmark], do: []
 
@@ -237,14 +237,14 @@ defp background_migrators do
     ]
   end
 
-  defp chat_child(true) do
+  defp shout_child(true) do
     [
       Pleroma.Web.ShoutChannel.ShoutChannelState,
       {Phoenix.PubSub, [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]}
     ]
   end
 
-  defp chat_child(_), do: []
+  defp shout_child(_), do: []
 
   defp task_children(:test) do
     [

From d6432a65da7ad11f1383d465370c11de5a2d7ddc Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Tue, 4 Aug 2020 10:42:52 -0500
Subject: [PATCH 240/339] Move shout configuration from :instance, update docs
 and changelog

---
 CHANGELOG.md                                        | 2 +-
 config/config.exs                                   | 5 +++--
 docs/configuration/cheatsheet.md                    | 6 +++---
 lib/pleroma/web/mastodon_api/views/instance_view.ex | 2 +-
 lib/pleroma/web/shout_channel.ex                    | 2 +-
 test/pleroma/web/shout_channel_test.ex              | 4 ++--
 6 files changed, 11 insertions(+), 10 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1c08710a3..6e27c4561 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ### Changed
 
-- **Breaking:** Configuration: `:chat, enabled` moved to `:shout, enabled` and `:instance, chat_limit` moved to `:instance, shout_limit`
+- **Breaking:** Configuration: `:chat, enabled` moved to `:shout, enabled` and `:instance, chat_limit` moved to `:shout, limit`
 - The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
 - HTTPSecurityPlug now sends a response header to opt out of Google's FLoC (Federated Learning of Cohorts) targeted advertising.
 - Email address is now returned if requesting user is the owner of the user account so it can be exposed in client and FE user settings UIs.
diff --git a/config/config.exs b/config/config.exs
index e2bf0cfc1..2f8a18788 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -190,7 +190,6 @@
   instance_thumbnail: "/instance/thumbnail.jpeg",
   limit: 5_000,
   description_limit: 5_000,
-  shout_limit: 5_000,
   remote_limit: 100_000,
   upload_limit: 16_000_000,
   avatar_upload_limit: 2_000_000,
@@ -457,7 +456,9 @@
   image_quality: 85,
   min_content_length: 100 * 1024
 
-config :pleroma, :shout, enabled: true
+config :pleroma, :shout,
+  enabled: true,
+  limit: 5_000
 
 config :phoenix, :format_encoders, json: Jason
 
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index 069421722..4e20309a1 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -8,9 +8,10 @@ For from source installations Pleroma configuration works by first importing the
 
 To add configuration to your config file, you can copy it from the base config. The latest version of it can be viewed [here](https://git.pleroma.social/pleroma/pleroma/blob/develop/config/config.exs). You can also use this file if you don't know how an option is supposed to be formatted.
 
-## :chat
+## :shout
 
-* `enabled` - Enables the backend chat. Defaults to `true`.
+* `enabled` - Enables the backend Shoutbox chat feature. Defaults to `true`.
+* `limit` - Shout character limit. Defaults to `5_000`
 
 ## :instance
 * `name`: The instance’s name.
@@ -19,7 +20,6 @@ To add configuration to your config file, you can copy it from the base config.
 * `description`: The instance’s description, can be seen in nodeinfo and ``/api/v1/instance``.
 * `limit`: Posts character limit (CW/Subject included in the counter).
 * `description_limit`: The character limit for image descriptions.
-* `chat_limit`: Character limit of the instance chat messages.
 * `remote_limit`: Hard character limit beyond which remote posts will be dropped.
 * `upload_limit`: File size limit of uploads (except for avatar, background, banner).
 * `avatar_upload_limit`: File size limit of user’s profile avatars.
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
index 75964f176..fcb4e2466 100644
--- a/lib/pleroma/web/mastodon_api/views/instance_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -37,7 +37,7 @@ def render("show.json", _) do
       background_upload_limit: Keyword.get(instance, :background_upload_limit),
       banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
       background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image),
-      shout_limit: Keyword.get(instance, :shout_limit),
+      shout_limit: Config.get([:shout, :limit]),
       description_limit: Keyword.get(instance, :description_limit),
       pleroma: %{
         metadata: %{
diff --git a/lib/pleroma/web/shout_channel.ex b/lib/pleroma/web/shout_channel.ex
index 1d97858d6..dc342fdfb 100644
--- a/lib/pleroma/web/shout_channel.ex
+++ b/lib/pleroma/web/shout_channel.ex
@@ -22,7 +22,7 @@ def handle_info(:after_join, socket) do
   def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} = socket) do
     text = String.trim(text)
 
-    if String.length(text) in 1..Pleroma.Config.get([:instance, :shout_limit]) do
+    if String.length(text) in 1..Pleroma.Config.get([:shout, :limit]) do
       author = User.get_cached_by_nickname(user_name)
       author_json = AccountView.render("show.json", user: author, skip_visibility_check: true)
 
diff --git a/test/pleroma/web/shout_channel_test.ex b/test/pleroma/web/shout_channel_test.ex
index ba6730ceb..b4d661689 100644
--- a/test/pleroma/web/shout_channel_test.ex
+++ b/test/pleroma/web/shout_channel_test.ex
@@ -25,7 +25,7 @@ test "it broadcasts a message", %{socket: socket} do
   end
 
   describe "message lengths" do
-    setup do: clear_config([:instance, :shout_limit])
+    setup do: clear_config([:shout, :limit])
 
     test "it ignores messages of length zero", %{socket: socket} do
       push(socket, "new_msg", %{"text" => ""})
@@ -33,7 +33,7 @@ test "it ignores messages of length zero", %{socket: socket} do
     end
 
     test "it ignores messages above a certain length", %{socket: socket} do
-      Pleroma.Config.put([:instance, :shout_limit], 2)
+      Pleroma.Config.put([:shout, :limit], 2)
       push(socket, "new_msg", %{"text" => "123"})
       refute_broadcast("new_msg", %{text: "123"})
     end

From 8ff2d8d17d057d5a2e0f5df603812155d6985df0 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Tue, 4 Aug 2020 10:45:28 -0500
Subject: [PATCH 241/339] Update description file for new shout config setting
 location

---
 config/description.exs | 19 ++++++++++---------
 1 file changed, 10 insertions(+), 9 deletions(-)

diff --git a/config/description.exs b/config/description.exs
index a17d222ce..7eecddaf5 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -544,14 +544,6 @@
           5_000
         ]
       },
-      %{
-        key: :shout_limit,
-        type: :integer,
-        description: "Character limit of the instance shout messages",
-        suggestions: [
-          5_000
-        ]
-      },
       %{
         key: :remote_limit,
         type: :integer,
@@ -2658,7 +2650,16 @@
     children: [
       %{
         key: :enabled,
-        type: :boolean
+        type: :boolean,
+        description: "Enables the backend Shoutbox chat feature."
+      },
+      %{
+        key: :limit,
+        type: :integer,
+        description: "Shout message character limit.",
+        suggestions: [
+          5_000
+        ]
       }
     ]
   },

From 01f796f8bbe98b2f004941d0a449035be6555c26 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Thu, 6 Aug 2020 16:37:17 -0500
Subject: [PATCH 242/339] Add a test for the migration

---
 ...200806175913_rename_instance_chat_test.exs | 21 +++++++++++++++++++
 1 file changed, 21 insertions(+)
 create mode 100644 test/migrations/20200806175913_rename_instance_chat_test.exs

diff --git a/test/migrations/20200806175913_rename_instance_chat_test.exs b/test/migrations/20200806175913_rename_instance_chat_test.exs
new file mode 100644
index 000000000..66341bd84
--- /dev/null
+++ b/test/migrations/20200806175913_rename_instance_chat_test.exs
@@ -0,0 +1,21 @@
+defmodule Pleroma.Repo.Migrations.RenameInstanceChatTest do
+  use Pleroma.DataCase
+  import Pleroma.Factory
+  import Pleroma.Tests.Helpers
+  alias Pleroma.ConfigDB
+
+  setup do: clear_config([:instance])
+  setup do: clear_config([:chat])
+  setup_all do: require_migration("20200806175913_rename_instance_chat")
+
+  test "up/0 migrates chat settings to shout", %{migration: migration} do
+    insert(:config, group: :pleroma, key: :instance, value: ["chat_limit: 6000"])
+    insert(:config, group: :pleroma, key: :chat, value: ["enabled: true"])
+
+    migration.up()
+
+    assert nil == ConfigDB.get_by_params(%{group: :pleroma, key: :chat})
+
+    assert %{value: [limit: 6000, enabled: true]} == ConfigDB.get_by_params(%{group: :pleroma, key: :shout})
+  end
+end

From e0bb6557739b9662bc7da785d060e302c4c61d61 Mon Sep 17 00:00:00 2001
From: Roman Chvanikov <chvanikoff@pm.me>
Date: Fri, 7 Aug 2020 12:18:36 +0300
Subject: [PATCH 243/339] Add RenameInstanceChat migration

---
 .../20200806175913_rename_instance_chat.exs   | 77 +++++++++++++++++++
 1 file changed, 77 insertions(+)
 create mode 100644 priv/repo/migrations/20200806175913_rename_instance_chat.exs

diff --git a/priv/repo/migrations/20200806175913_rename_instance_chat.exs b/priv/repo/migrations/20200806175913_rename_instance_chat.exs
new file mode 100644
index 000000000..31585efe8
--- /dev/null
+++ b/priv/repo/migrations/20200806175913_rename_instance_chat.exs
@@ -0,0 +1,77 @@
+defmodule Pleroma.Repo.Migrations.RenameInstanceChat do
+  use Ecto.Migration
+
+  alias Pleroma.ConfigDB
+
+  @instance_params %{group: :pleroma, key: :instance}
+  @shout_params %{group: :pleroma, key: :shout}
+  @chat_params %{group: :pleroma, key: :chat}
+
+  def up do
+    instance_updated? = maybe_update_instance_key(:up) != :noop
+    chat_updated? = maybe_update_chat_key(:up) != :noop
+
+    case Enum.any?([instance_updated?, chat_updated?]) do
+      true -> :ok
+      false -> :noop
+    end
+  end
+
+  def down do
+    instance_updated? = maybe_update_instance_key(:down) != :noop
+    chat_updated? = maybe_update_chat_key(:down) != :noop
+
+    case Enum.any?([instance_updated?, chat_updated?]) do
+      true -> :ok
+      false -> :noop
+    end
+  end
+
+  # pleroma.instance.chat_limit -> pleroma.shout.limit
+  defp maybe_update_instance_key(:up) do
+    with %ConfigDB{value: values} <- ConfigDB.get_by_params(@instance_params),
+         limit when is_integer(limit) <- values[:chat_limit] do
+      @shout_params |> Map.put(:value, limit: limit) |> ConfigDB.update_or_create()
+      @instance_params |> Map.put(:subkeys, [":chat_limit"]) |> ConfigDB.delete()
+    else
+      _ ->
+        :noop
+    end
+  end
+
+  # pleroma.shout.limit -> pleroma.instance.chat_limit
+  defp maybe_update_instance_key(:down) do
+    with %ConfigDB{value: values} <- ConfigDB.get_by_params(@shout_params),
+         limit when is_integer(limit) <- values[:limit] do
+      @instance_params |> Map.put(:value, chat_limit: limit) |> ConfigDB.update_or_create()
+      @shout_params |> Map.put(:subkeys, [":limit"]) |> ConfigDB.delete()
+    else
+      _ ->
+        :noop
+    end
+  end
+
+  # pleroma.chat.enabled -> pleroma.shout.enabled
+  defp maybe_update_chat_key(:up) do
+    with %ConfigDB{value: values} <- ConfigDB.get_by_params(@chat_params),
+         enabled? when is_boolean(enabled?) <- values[:enabled] do
+      @shout_params |> Map.put(:value, enabled: enabled?) |> ConfigDB.update_or_create()
+      @chat_params |> Map.put(:subkeys, [":enabled"]) |> ConfigDB.delete()
+    else
+      _ ->
+        :noop
+    end
+  end
+
+  # pleroma.shout.enabled -> pleroma.chat.enabled
+  defp maybe_update_chat_key(:down) do
+    with %ConfigDB{value: values} <- ConfigDB.get_by_params(@shout_params),
+         enabled? when is_boolean(enabled?) <- values[:enabled] do
+      @chat_params |> Map.put(:value, enabled: enabled?) |> ConfigDB.update_or_create()
+      @shout_params |> Map.put(:subkeys, [":enabled"]) |> ConfigDB.delete()
+    else
+      _ ->
+        :noop
+    end
+  end
+end

From d7dfa6d27cf140b9d0faed32c3a7a659a0f2e20e Mon Sep 17 00:00:00 2001
From: Roman Chvanikov <chvanikoff@pm.me>
Date: Fri, 7 Aug 2020 12:18:55 +0300
Subject: [PATCH 244/339] Update test for RenameInstanceChat migration

---
 ...200806175913_rename_instance_chat_test.exs | 43 ++++++++++++++++---
 1 file changed, 37 insertions(+), 6 deletions(-)

diff --git a/test/migrations/20200806175913_rename_instance_chat_test.exs b/test/migrations/20200806175913_rename_instance_chat_test.exs
index 66341bd84..acd45600c 100644
--- a/test/migrations/20200806175913_rename_instance_chat_test.exs
+++ b/test/migrations/20200806175913_rename_instance_chat_test.exs
@@ -8,14 +8,45 @@ defmodule Pleroma.Repo.Migrations.RenameInstanceChatTest do
   setup do: clear_config([:chat])
   setup_all do: require_migration("20200806175913_rename_instance_chat")
 
-  test "up/0 migrates chat settings to shout", %{migration: migration} do
-    insert(:config, group: :pleroma, key: :instance, value: ["chat_limit: 6000"])
-    insert(:config, group: :pleroma, key: :chat, value: ["enabled: true"])
+  describe "up/0" do
+    test "migrates chat settings to shout", %{migration: migration} do
+      insert(:config, group: :pleroma, key: :instance, value: [chat_limit: 6000])
+      insert(:config, group: :pleroma, key: :chat, value: [enabled: true])
 
-    migration.up()
+      assert migration.up() == :ok
 
-    assert nil == ConfigDB.get_by_params(%{group: :pleroma, key: :chat})
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :chat}) == nil
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :instance}) == nil
 
-    assert %{value: [limit: 6000, enabled: true]} == ConfigDB.get_by_params(%{group: :pleroma, key: :shout})
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :shout}).value == [
+               limit: 6000,
+               enabled: true
+             ]
+    end
+
+    test "does nothing when chat settings are not set", %{migration: migration} do
+      assert migration.up() == :noop
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :chat}) == nil
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :shout}) == nil
+    end
+  end
+
+  describe "down/0" do
+    test "migrates shout settings back to instance and chat", %{migration: migration} do
+      insert(:config, group: :pleroma, key: :shout, value: [limit: 42, enabled: true])
+
+      assert migration.down() == :ok
+
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :chat}).value == [enabled: true]
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :instance}).value == [chat_limit: 42]
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :shout}) == nil
+    end
+
+    test "does nothing when shout settings are not set", %{migration: migration} do
+      assert migration.down() == :noop
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :chat}) == nil
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :instance}) == nil
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :shout}) == nil
+    end
   end
 end

From 9ce2c017c0782c9ea4a0ca6009e82d034ac7915c Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Thu, 20 May 2021 14:30:29 -0500
Subject: [PATCH 245/339] We want clear_config/2 in all tests now

---
 test/pleroma/web/shout_channel_test.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/pleroma/web/shout_channel_test.ex b/test/pleroma/web/shout_channel_test.ex
index b4d661689..a266543d2 100644
--- a/test/pleroma/web/shout_channel_test.ex
+++ b/test/pleroma/web/shout_channel_test.ex
@@ -33,7 +33,7 @@ test "it ignores messages of length zero", %{socket: socket} do
     end
 
     test "it ignores messages above a certain length", %{socket: socket} do
-      Pleroma.Config.put([:shout, :limit], 2)
+      clear_config([:shout, :limit], 2)
       push(socket, "new_msg", %{"text" => "123"})
       refute_broadcast("new_msg", %{text: "123"})
     end

From d9513b11d3c18c4d30cdcab700bb5ed39b3356ea Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 28 May 2021 14:10:37 -0500
Subject: [PATCH 246/339] Forgot to move migration test when rebasing

---
 .../repo/migrations/rename_instance_chat_test.exs}                | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename test/{migrations/20200806175913_rename_instance_chat_test.exs => pleroma/repo/migrations/rename_instance_chat_test.exs} (100%)

diff --git a/test/migrations/20200806175913_rename_instance_chat_test.exs b/test/pleroma/repo/migrations/rename_instance_chat_test.exs
similarity index 100%
rename from test/migrations/20200806175913_rename_instance_chat_test.exs
rename to test/pleroma/repo/migrations/rename_instance_chat_test.exs

From 48a0ea2fc342c9a757222b0755a0cad9b725bdc7 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 1 Jun 2021 11:55:51 -0500
Subject: [PATCH 247/339] Wire up join requests to the old "chat:public"
 channel into the new "shout:public" channel

---
 lib/pleroma/web/shout_channel.ex | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lib/pleroma/web/shout_channel.ex b/lib/pleroma/web/shout_channel.ex
index dc342fdfb..70d9cc1e1 100644
--- a/lib/pleroma/web/shout_channel.ex
+++ b/lib/pleroma/web/shout_channel.ex
@@ -9,6 +9,9 @@ defmodule Pleroma.Web.ShoutChannel do
   alias Pleroma.Web.MastodonAPI.AccountView
   alias Pleroma.Web.ShoutChannel.ShoutChannelState
 
+  # Backwards compatibility
+  def join("chat:public", message, socket), do: join("shout:public", message, socket)
+
   def join("shout:public", _message, socket) do
     send(self(), :after_join)
     {:ok, socket}

From 2743c6669311ecb9a985a959dfd28b7aeed8783a Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 1 Jun 2021 12:57:18 -0500
Subject: [PATCH 248/339] Add "chat" back as a feature for backwards compat.

Legacy PleromaFE uses this to identify if ShoutBox is available.
---
 lib/pleroma/web/mastodon_api/views/instance_view.ex | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
index fcb4e2466..3528185d5 100644
--- a/lib/pleroma/web/mastodon_api/views/instance_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -69,6 +69,10 @@ def features do
       if Config.get([:gopher, :enabled]) do
         "gopher"
       end,
+      # backwards compat
+      if Config.get([:shout, :enabled]) do
+        "chat"
+      end,
       if Config.get([:shout, :enabled]) do
         "shout"
       end,

From a744c47e9a43a751438973a66b7201b006c6b944 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 1 Jun 2021 13:55:07 -0500
Subject: [PATCH 249/339] Remove deps from Streaming/Persisting behaviors
 Speeds up recompilation by limiting compile-time deps

---
 lib/pleroma/web/activity_pub/activity_pub/persisting.ex | 2 +-
 lib/pleroma/web/activity_pub/activity_pub/streaming.ex  | 8 ++------
 2 files changed, 3 insertions(+), 7 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/activity_pub/persisting.ex b/lib/pleroma/web/activity_pub/activity_pub/persisting.ex
index 5ec8b7bab..f39cd000a 100644
--- a/lib/pleroma/web/activity_pub/activity_pub/persisting.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub/persisting.ex
@@ -3,5 +3,5 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.ActivityPub.ActivityPub.Persisting do
-  @callback persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
+  @callback persist(map(), keyword()) :: {:ok, struct()}
 end
diff --git a/lib/pleroma/web/activity_pub/activity_pub/streaming.ex b/lib/pleroma/web/activity_pub/activity_pub/streaming.ex
index 983168bff..33c7bf2bc 100644
--- a/lib/pleroma/web/activity_pub/activity_pub/streaming.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub/streaming.ex
@@ -3,10 +3,6 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.ActivityPub.ActivityPub.Streaming do
-  alias Pleroma.Activity
-  alias Pleroma.Object
-  alias Pleroma.User
-
-  @callback stream_out(Activity.t()) :: any()
-  @callback stream_out_participations(Object.t(), User.t()) :: any()
+  @callback stream_out(struct()) :: any()
+  @callback stream_out_participations(struct(), struct()) :: any()
 end

From 8a5ceb7e53f1817f83a72b997f6b9daa7070972b Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Sat, 22 May 2021 15:30:14 -0500
Subject: [PATCH 250/339] Remove deps from Uploader behaviour Speeds up
 recompilation by limiting compile-time deps

---
 lib/pleroma/uploaders/uploader.ex | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/pleroma/uploaders/uploader.ex b/lib/pleroma/uploaders/uploader.ex
index 0be878ca2..deba548b7 100644
--- a/lib/pleroma/uploaders/uploader.ex
+++ b/lib/pleroma/uploaders/uploader.ex
@@ -35,7 +35,7 @@ defmodule Pleroma.Uploaders.Uploader do
 
   """
   @type file_spec :: {:file | :url, String.t()}
-  @callback put_file(Pleroma.Upload.t()) ::
+  @callback put_file(upload :: struct()) ::
               :ok | {:ok, file_spec()} | {:error, String.t()} | :wait_callback
 
   @callback delete_file(file :: String.t()) :: :ok | {:error, String.t()}
@@ -46,7 +46,7 @@ defmodule Pleroma.Uploaders.Uploader do
               | {:error, Plug.Conn.t(), String.t()}
   @optional_callbacks http_callback: 2
 
-  @spec put_file(module(), Pleroma.Upload.t()) :: {:ok, file_spec()} | {:error, String.t()}
+  @spec put_file(module(), upload :: struct()) :: {:ok, file_spec()} | {:error, String.t()}
   def put_file(uploader, upload) do
     case uploader.put_file(upload) do
       :ok -> {:ok, {:file, upload.path}}

From 0be7eada92d862277c3bf349ca5a3079ebacb700 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 1 Jun 2021 14:34:13 -0500
Subject: [PATCH 251/339] Keep original Shoutbox channel name as chat:public
 There is no sane / high level workaround for merging users who join
 shout:public and chat:public.

---
 lib/pleroma/web/channels/user_socket.ex | 2 +-
 lib/pleroma/web/shout_channel.ex        | 5 +----
 2 files changed, 2 insertions(+), 5 deletions(-)

diff --git a/lib/pleroma/web/channels/user_socket.ex b/lib/pleroma/web/channels/user_socket.ex
index 130809bb7..043206835 100644
--- a/lib/pleroma/web/channels/user_socket.ex
+++ b/lib/pleroma/web/channels/user_socket.ex
@@ -8,7 +8,7 @@ defmodule Pleroma.Web.UserSocket do
 
   ## Channels
   # channel "room:*", Pleroma.Web.RoomChannel
-  channel("shout:*", Pleroma.Web.ShoutChannel)
+  channel("chat:*", Pleroma.Web.ShoutChannel)
 
   # Socket params are passed from the client and can
   # be used to verify and authenticate a user. After
diff --git a/lib/pleroma/web/shout_channel.ex b/lib/pleroma/web/shout_channel.ex
index 70d9cc1e1..17caecb1a 100644
--- a/lib/pleroma/web/shout_channel.ex
+++ b/lib/pleroma/web/shout_channel.ex
@@ -9,10 +9,7 @@ defmodule Pleroma.Web.ShoutChannel do
   alias Pleroma.Web.MastodonAPI.AccountView
   alias Pleroma.Web.ShoutChannel.ShoutChannelState
 
-  # Backwards compatibility
-  def join("chat:public", message, socket), do: join("shout:public", message, socket)
-
-  def join("shout:public", _message, socket) do
+  def join("chat:public", _message, socket) do
     send(self(), :after_join)
     {:ok, socket}
   end

From 9879c18548c1b9f37df724259f65d5cd098f44c5 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Sat, 22 May 2021 14:48:13 -0500
Subject: [PATCH 252/339] Avoid `use Phoenix.Swoosh` to prevent recompiling the
 Endpoint Speeds up recompilation by fixing cycles in UserEmail

---
 lib/pleroma/emails/user_email.ex | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex
index 52f3d419d..e38c681ba 100644
--- a/lib/pleroma/emails/user_email.ex
+++ b/lib/pleroma/emails/user_email.ex
@@ -5,15 +5,22 @@
 defmodule Pleroma.Emails.UserEmail do
   @moduledoc "User emails"
 
-  use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email}
-
   alias Pleroma.Config
   alias Pleroma.User
   alias Pleroma.Web.Endpoint
   alias Pleroma.Web.Router
 
+  import Swoosh.Email
+  import Phoenix.Swoosh, except: [render_body: 3]
   import Pleroma.Config.Helpers, only: [instance_name: 0, sender: 0]
 
+  def render_body(email, template, assigns \\ %{}) do
+    email
+    |> put_new_layout({Pleroma.Web.LayoutView, :email})
+    |> put_new_view(Pleroma.Web.EmailView)
+    |> Phoenix.Swoosh.render_body(template, assigns)
+  end
+
   defp recipient(email, nil), do: email
   defp recipient(email, name), do: {name, email}
   defp recipient(%User{} = user), do: recipient(user.email, user.name)

From dcf84ac12e830ebc17f63e2fea0bd47c75e9f981 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 1 Jun 2021 16:53:32 -0500
Subject: [PATCH 253/339] disableChat / disableShout didn't actually do
 anything for PleromaFE

---
 config/description.exs | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/config/description.exs b/config/description.exs
index 7eecddaf5..c93a00782 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -1174,7 +1174,6 @@
             alwaysShowSubjectInput: true,
             background: "/static/aurora_borealis.jpg",
             collapseMessageWithSubject: false,
-            disableShout: false,
             greentext: false,
             hideFilteredStatuses: false,
             hideMutedPosts: false,
@@ -1221,12 +1220,6 @@
             description:
               "When a message has a subject (aka Content Warning), collapse it by default"
           },
-          %{
-            key: :disableShout,
-            label: "PleromaFE Shout",
-            type: :boolean,
-            description: "Disables PleromaFE Shout component"
-          },
           %{
             key: :greentext,
             label: "Greentext",

From 297feb73f4ea2de59cf3fc8f4beabe18e72a6311 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 2 Jun 2021 11:21:04 -0500
Subject: [PATCH 254/339] Formatting

---
 config/description.exs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/config/description.exs b/config/description.exs
index 1c3e3f900..abd802ac0 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -682,7 +682,8 @@
       %{
         key: :allow_relay,
         type: :boolean,
-        description: "Enable Pleroma's Relay, which makes it possible to follow a whole instance. (Important!) This will increase the visibility of your instance."
+        description:
+          "Enable Pleroma's Relay, which makes it possible to follow a whole instance. (Important!) This will increase the visibility of your instance."
       },
       %{
         key: :public,

From 679d4c23e93f86f3e3fec70e0ac9bf2c9cdf8819 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 2 Jun 2021 11:26:26 -0500
Subject: [PATCH 255/339] Update wording for relays in docs and config
 description

---
 config/description.exs           | 2 +-
 docs/configuration/cheatsheet.md | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/config/description.exs b/config/description.exs
index abd802ac0..bdde22f98 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -683,7 +683,7 @@
         key: :allow_relay,
         type: :boolean,
         description:
-          "Enable Pleroma's Relay, which makes it possible to follow a whole instance. (Important!) This will increase the visibility of your instance."
+          "Permits remote instances to subscribe to all public posts of your instance. (Important!) This may increase the visibility of your instance."
       },
       %{
         key: :public,
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index 069421722..366c60c73 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -37,7 +37,7 @@ To add configuration to your config file, you can copy it from the base config.
 * `federating`: Enable federation with other instances.
 * `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.
 * `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
-* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance.
+* `allow_relay`: Permits remote instances to subscribe to all public posts of your instance. This may increase the visibility of your instance.
 * `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. Note that there is a dependent setting restricting or allowing unauthenticated access to specific resources, see `restrict_unauthenticated` for more details.
 * `quarantined_instances`: List of ActivityPub instances where private (DMs, followers-only) activities will not be send.
 * `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML).

From e06466a5327ca2fa3cb7abd5f130c0a8a6b6fe27 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 2 Jun 2021 12:00:45 -0500
Subject: [PATCH 256/339] Skip build, test, analysis/lint when we don't make
 code changes

---
 .gitlab-ci.yml | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 78e715d47..8b2f11153 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -37,6 +37,11 @@ after_script:
 
 build:
   stage: build
+  only:
+    changes:
+      - "**/*.ex"
+      - "**/*.exs"
+      - "mix.lock"
   script:
   - mix compile --force
 
@@ -64,6 +69,11 @@ benchmark:
 
 unit-testing:
   stage: test
+  only:
+    changes:
+      - "**/*.ex"
+      - "**/*.exs"
+      - "mix.lock"
   retry: 2
   cache: &testing_cache_policy
     <<: *global_cache_policy
@@ -97,6 +107,11 @@ unit-testing:
 
 unit-testing-rum:
   stage: test
+  only:
+    changes:
+      - "**/*.ex"
+      - "**/*.exs"
+      - "mix.lock"
   retry: 2
   cache: *testing_cache_policy
   services:
@@ -115,12 +130,22 @@ unit-testing-rum:
 
 lint:
   stage: test
+  only:
+    changes:
+      - "**/*.ex"
+      - "**/*.exs"
+      - "mix.lock"
   cache: *testing_cache_policy
   script:
     - mix format --check-formatted
 
 analysis:
   stage: test
+  only:
+    changes:
+      - "**/*.ex"
+      - "**/*.exs"
+      - "mix.lock"
   cache: *testing_cache_policy
   script:
     - mix credo --strict --only=warnings,todo,fixme,consistency,readability

From 9f391da73d496cfd381b1bd55070512e5c462a0a Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 2 Jun 2021 12:09:41 -0500
Subject: [PATCH 257/339] Don't generate new specs unless they've changed.

---
 .gitlab-ci.yml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8b2f11153..b155c81bd 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -47,6 +47,10 @@ build:
 
 spec-build:
   stage: test
+  only:
+    changes:
+      - "lib/pleroma/web/api_spec/**/*.ex"
+      - "lib/pleroma/web/api_spec.ex"
   artifacts:
     paths:
     - spec.json

From 59af07f14908808d8e016f03854bcfd1eb8c8f0a Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Sat, 15 May 2021 12:40:37 +0200
Subject: [PATCH 258/339] Update all dependencies

---
 mix.lock | 78 +++++++++++++++++++++++++++++---------------------------
 1 file changed, 40 insertions(+), 38 deletions(-)

diff --git a/mix.lock b/mix.lock
index 55f73ad00..0b53c7aaf 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,30 +1,30 @@
 %{
   "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"},
-  "base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "3b29948de2013d3f93aa898c884a9dff847e7aec75d9d6d8c1dc4c61c2716c42"},
+  "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.2.0", "3df902b81ce7fa8867a2ae30d20a1da6877a2c056bfb116fd0bc8a5f0190cea4", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "762be3fcb779f08207531bc6612cca480a338e4b4357abb49f5ce00240a77d1e"},
+  "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.2.0", "a596476c781b0646e6cb5cd9751af2e2974c3e0d5498a8cab71807618b74fe2f", [: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", "aef93694067a43697ae0531727e097754a9e992a1e7946296f5969d6dd9ac986"},
+  "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.7", "1ca19eee705cde48c9e809e37fdd0730510752cc397745e550f6065a56a701e9", [:mix], [], "hexpm", "a2ae2c13d40e9c308387f1aceb14786dca019ebc2a11484fb2a9f797ea0aa0d8"},
+  "castore": {:hex, :castore, "0.1.10", "b01a007416a0ae4188e70b3b306236021b16c11474038ead7aff79dd75538c23", [:mix], [], "hexpm", "a48314e0cb45682db2ea27b8ebfa11bd6fa0a6e21a65e5772ad83ca136ff2665"},
   "certifi": {:git, "https://github.com/certifi/erlang-certifi", "e08b12e8993502240c25b78563993776f87ecd2a", [tag: "2.5.1"]},
   "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
-  "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"},
+  "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.2", "2b46083af45e4bc79632bd951550509395935d3e7973275b2b743bd63cc942ce", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f0d0e13f71c51fd4ef8b2c7e051388e4dfb267522a83a22392c856de7e46465f"},
-  "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"},
-  "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.0", "69fdb5cf92df6373e15675eb4018cf629f5d8e35e74841bb637d6596cb797bbc", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "42868c229d9a2900a1501c5d0355bfd46e24c862c322b0b4f5a6f14fe0216753"},
-  "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"},
-  "credo": {:hex, :credo, "1.4.1", "16392f1edd2cdb1de9fe4004f5ab0ae612c92e230433968eab00aafd976282fc", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "155f8a2989ad77504de5d8291fa0d41320fdcaa6a1030472e9967f285f8c7692"},
+  "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://git.pleroma.social/pleroma/elixir-libraries/crypt.git", "cf2aa3f11632e8b0634810a15b3e612c7526f6a3", [ref: "cf2aa3f11632e8b0634810a15b3e612c7526f6a3"]},
   "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
-  "db_connection": {:hex, :db_connection, "2.3.1", "4c9f3ed1ef37471cbdd2762d6655be11e38193904d9c5c1c9389f1b891a3088e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "abaab61780dde30301d840417890bd9f74131041afd02174cf4e10635b3a63f5"},
+  "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.15", "2c7f924bf495ec1f65bd144b355d0949a05a254d0ec561740308a54946a67888", [:mix], [{:earmark_parser, ">= 1.4.13", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "3b1209b85bc9f3586f370f7c363f6533788fb4e51db23aa79565875e7f9999ee"},
@@ -37,50 +37,52 @@
   "eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"},
   "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.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"},
+  "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.6", "41ab8b4caa48035c96d07faa035d2d9de6df480e7e084c054e662ac888dcd4d4", [: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, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "a541bd042c1ee26412bb1e749ddf2a1c327e4fb7e382b1cd227e1b00eed3d469"},
-  "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.2", "c0258bbdfea55de4f98f0b2f0ca61fe402cc696f573815134beb1866e778f47b", [: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", "0569f5b211b1a3b12b705fe2a9d0e237eb1360b9d76298028df2346cad13097a"},
+  "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.22.2", "03a2a58bdd2ba0d83d004507c4ee113b9c521956938298eba16e55cc4aba4a6c", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "cf60e1b3e2efe317095b6bb79651f83a2c1b3edcb4d319c421d7fcda8b3aff26"},
-  "ex_machina": {:hex, :ex_machina, "2.4.0", "09a34c5d371bfb5f78399029194a8ff67aff340ebe8ba19040181af35315eabb", [: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", "a20bc9ddc721b33ea913b93666c5d0bdca5cbad7a67540784ae277228832d72c"},
+  "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.27.0", "6b29a14283f1e2e8fad824bc930eaa9477c462022075df6bea8f0ad811c13599", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "583b8c13697c37179f1f82443bcc7ad2f76fbc0bf4c186606eebd658f7f2631b"},
+  "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": {:git, "https://github.com/ninenines/gun.git", "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", [ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327"]},
   "hackney": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/hackney.git", "7d7119f0651515d6d7669c78393fd90950a3ec6e", [ref: "7d7119f0651515d6d7669c78393fd90950a3ec6e"]},
-  "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
+  "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.0", "4e4b501a936dbf4cb5222597038a89ea10781776770d2e185849fa829686b34c", [:mix], [], "hexpm", "f8a7b3731e3fd17d38fa6e343fcad7b03d6874a3b0a108c8568a71ed9c2cf824"},
-  "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"},
+  "httpoison": {:hex, :httpoison, "1.8.0", "6b85dea15820b7804ef607ff78406ab449dd78bed923a49c7160e1886e987a3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "28089eaa98cf90c66265b6b5ad87c59a3729bea2e74e9d08f9b51eb9729b3c3a"},
   "idna": {:git, "https://github.com/benoitc/erlang-idna", "6cff72747821110169ecfac871b0c69e5064afff", [tag: "6.0.0"]},
   "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.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"},
-  "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
+  "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.0", "e0ea8de73ff44742d6a889721221f4c4eccaad5284957ee9832ffeb347602d54", [:mix], [], "hexpm", "4ccd958350aee7c51c89e21f05b15d30596ebbba707e051d21766be1809df2d7"},
   "majic": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", "289cda1b6d0d70ccb2ba508a2b0bd24638db2880", [ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"]},
-  "makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"},
+  "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.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"},
   "metrics": {:git, "https://github.com/benoitc/erlang-metrics", "c6eb4dcf29f9e907539915e2ab996f40c2ec7e8e", [tag: "1.0.1"]},
-  "mime": {:hex, :mime, "1.4.0", "5066f14944b470286146047d2f73518cf5cca82f8e4815cf35d196b58cf07c47", [:mix], [], "hexpm", "75fa42c4228ea9a23f70f123c74ba7cece6a03b1fd474fe13f6a7a85c6ea4ff6"},
+  "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
   "mimerl": {:git, "https://github.com/benoitc/mimerl", "5a1b22a8fada5b3b40438da00a6923cb87a42bbc", [tag: "1.2.0"]},
   "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
-  "mock": {:hex, :mock, "0.3.5", "feb81f52b8dcf0a0d65001d2fec459f6b6a8c22562d94a965862f6cc066b5431", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "6fae404799408300f863550392635d8f7e3da6b71abdd5c393faf41b131c8728"},
+  "mock": {:hex, :mock, "0.3.6", "e810a91fabc7adf63ab5fdbec5d9d3b492413b8cda5131a2a8aa34b4185eb9b4", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "bcf1d0a6826fb5aee01bae3d74474669a3fa8b2df274d094af54a25266a1ebd2"},
   "mogrify": {:hex, :mogrify, "0.7.4", "9b2496dde44b1ce12676f85d7dc531900939e6367bc537c7243a1b089435b32d", [:mix], [], "hexpm", "50d79e337fba6bc95bfbef918058c90f50b17eed9537771e61d4619488f099c3"},
   "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.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
+  "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm", "5c040b8469c1ff1b10093d3186e2e10dbe483cd73d79ec017993fb3985b8a9b3"},
   "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"},
@@ -88,37 +90,37 @@
   "p1_utils": {:hex, :p1_utils, "1.0.18", "3fe224de5b2e190d730a3c5da9d6e8540c96484cf4b4692921d1e28f0c32b01c", [:rebar3], [], "hexpm", "1fc8773a71a15553b179c986b22fbeead19b28fe486c332d4929700ffeb71f88"},
   "parse_trans": {:git, "https://github.com/uwiger/parse_trans.git", "76abb347c3c1d00fb0ccf9e4b43e22b3d2288484", [tag: "3.3.0"]},
   "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.6", "8298cdb4e0f943242ba8410780a6a69cbbe972fef199b341a36898dd751bdd66", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [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", "0dc4d39af1306b6aa5122729b0a95ca779e42c708c6fe7abbb3d336d5379e956"},
+  "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.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"},
+  "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.2", "43d3518349a22b8b1910ea28b4dd5119926d5017b3187db3fbd1a1e05769a851", [: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", "3e2ac4e883db7af0702d75ba00c19901760e8342b91f8f66e13941de552e777f"},
-  "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.4.0", "e936ef151751f386804c51f87f7300f5aaae6893cdad726559c3930c6c032948", [: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", "e25ddcfc06b1b76e55af79d078b03cbc86bbcb99ce4e5e0a5e4a8114ee039be6"},
-  "plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"},
+  "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.11.1", "f2992bac66fdae679453c9e86134a4201f6f43a687d8ff1cd1b2862d53c80259", [: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", "23524e4fefbb587c11f0833b3910bfb414bf2e2534d61928e920f54e3a1b881f"},
+  "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.7", "724410acd48abac529d0faa6c2a379fb8ae2088e31247687b16cacc0e0883372", [: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", "88310c010ff047cecd73d5ceca1d99205e4b1ab1b9abfdab7e00f5c9d20ef8f9"},
+  "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, "0.11.0", "61bad869a94534739dd4614a25a619bc5c47b9970e9a0ea5bef4628036fc7a16", [:rebar3], [], "hexpm", "57ee6ee6bdeb639661ffafb9acefe3c8f966e45394de6a766813bb9e1be4e54b"},
-  "prometheus": {:hex, :prometheus, "4.6.0", "20510f381db1ccab818b4cf2fac5fa6ab5cc91bc364a154399901c001465f46f", [:mix, :rebar3], [], "hexpm", "4905fd2992f8038eccd7aa0cd22f40637ed618c0bed1f75c05aacec15b7545de"},
+  "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.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
+  "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"]},
   "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
   "ssl_verify_fun": {:git, "https://github.com/deadtrickster/ssl_verify_fun.erl", "c5718226b0b9f3d1a38ef6ca3c3b4c75f53dda92", [tag: "1.1.4"]},
   "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
-  "swoosh": {:hex, :swoosh, "1.0.6", "6765e334c67dacabe721f0d701c7e5a6f06e4595c90df6f91e73ebd54d555833", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, 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", "7c50ef78e4acfd1cbd4907dc1fa87b5540675a6be9dc979d04890f49d7ec1830"},
+  "swoosh": {:hex, :swoosh, "1.3.8", "026d95852f21b20ac255a7f8ee2bf62f49ddccbd0ef00c96e10e117c4dc19c5a", [: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", "c76137424e285e1bb66354ef1d983d0ef55ce9676c1ded208a941b3b33897b78"},
   "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
-  "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
-  "tesla": {:hex, :tesla, "1.4.0", "1081bef0124b8bdec1c3d330bbe91956648fb008cf0d3950a369cda466a31a87", [: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", "bf1374a5569f5fca8e641363b63f7347d680d91388880979a33bc12a6eb3e0aa"},
-  "timex": {:hex, :timex, "3.7.3", "df8a2ea814749d700d6878ab9eacac9fdb498ecee2f507cb0002ec172bc24d0f", [: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", "8691c1d86ca3a7bc14a156e2199dc8927be95d1a8f0e3b69e4bb2d6262c53ac6"},
+  "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"},

From 166455c88441b22455d996ed528ed4804514a3c0 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Sat, 15 May 2021 13:08:00 +0200
Subject: [PATCH 259/339] mix: Switch hackney & gun to releases

---
 mix.exs  |  8 ++------
 mix.lock | 18 +++++++++---------
 2 files changed, 11 insertions(+), 15 deletions(-)

diff --git a/mix.exs b/mix.exs
index a4bacba8e..4ba698620 100644
--- a/mix.exs
+++ b/mix.exs
@@ -137,8 +137,7 @@ defp deps do
       {:tesla, "~> 1.4.0", override: true},
       {:castore, "~> 0.1"},
       {:cowlib, "~> 2.9", override: true},
-      {:gun,
-       github: "ninenines/gun", ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", override: true},
+      {:gun, "~> 2.0.0-rc.1", override: true},
       {:jason, "~> 1.2"},
       {:mogrify, "~> 0.7.4"},
       {:ex_aws, "~> 2.1.6"},
@@ -208,10 +207,7 @@ defp deps do
       {: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,
-       git: "https://git.pleroma.social/pleroma/elixir-libraries/hackney.git",
-       ref: "7d7119f0651515d6d7669c78393fd90950a3ec6e",
-       override: true},
+      {:hackney, "~> 1.17.0", override: true},
       {:mox, "~> 1.0", only: :test},
       {:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test}
     ] ++ oauth_deps()
diff --git a/mix.lock b/mix.lock
index 0b53c7aaf..b838d6f80 100644
--- a/mix.lock
+++ b/mix.lock
@@ -11,7 +11,7 @@
   "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": {:git, "https://github.com/certifi/erlang-certifi", "e08b12e8993502240c25b78563993776f87ecd2a", [tag: "2.5.1"]},
+  "certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"},
   "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"]},
@@ -55,13 +55,13 @@
   "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": {:git, "https://github.com/ninenines/gun.git", "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", [ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327"]},
-  "hackney": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/hackney.git", "7d7119f0651515d6d7669c78393fd90950a3ec6e", [ref: "7d7119f0651515d6d7669c78393fd90950a3ec6e"]},
+  "gun": {:hex, :gun, "2.0.0-rc.1", "b87d81dad83f41fa3f2cbf1a923eae44c5ce559a7006728d47888c3e7eb7a6ce", [:make, :rebar3], [{:cowlib, "2.10.1", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "459e7c843c894f69878df60378e7fa4a4b5504a00066c02138d084435c2c7968"},
+  "hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [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", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"},
   "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.0", "4e4b501a936dbf4cb5222597038a89ea10781776770d2e185849fa829686b34c", [:mix], [], "hexpm", "f8a7b3731e3fd17d38fa6e343fcad7b03d6874a3b0a108c8568a71ed9c2cf824"},
   "httpoison": {:hex, :httpoison, "1.8.0", "6b85dea15820b7804ef607ff78406ab449dd78bed923a49c7160e1886e987a3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "28089eaa98cf90c66265b6b5ad87c59a3729bea2e74e9d08f9b51eb9729b3c3a"},
-  "idna": {:git, "https://github.com/benoitc/erlang-idna", "6cff72747821110169ecfac871b0c69e5064afff", [tag: "6.0.0"]},
+  "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"},
@@ -74,9 +74,9 @@
   "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.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"},
-  "metrics": {:git, "https://github.com/benoitc/erlang-metrics", "c6eb4dcf29f9e907539915e2ab996f40c2ec7e8e", [tag: "1.0.1"]},
+  "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
   "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
-  "mimerl": {:git, "https://github.com/benoitc/mimerl", "5a1b22a8fada5b3b40438da00a6923cb87a42bbc", [tag: "1.2.0"]},
+  "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
   "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
   "mock": {:hex, :mock, "0.3.6", "e810a91fabc7adf63ab5fdbec5d9d3b492413b8cda5131a2a8aa34b4185eb9b4", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "bcf1d0a6826fb5aee01bae3d74474669a3fa8b2df274d094af54a25266a1ebd2"},
   "mogrify": {:hex, :mogrify, "0.7.4", "9b2496dde44b1ce12676f85d7dc531900939e6367bc537c7243a1b089435b32d", [:mix], [], "hexpm", "50d79e337fba6bc95bfbef918058c90f50b17eed9537771e61d4619488f099c3"},
@@ -88,7 +88,7 @@
   "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": {:git, "https://github.com/uwiger/parse_trans.git", "76abb347c3c1d00fb0ccf9e4b43e22b3d2288484", [tag: "3.3.0"]},
+  "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"},
@@ -114,7 +114,7 @@
   "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"]},
   "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
-  "ssl_verify_fun": {:git, "https://github.com/deadtrickster/ssl_verify_fun.erl", "c5718226b0b9f3d1a38ef6ca3c3b4c75f53dda92", [tag: "1.1.4"]},
+  "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.8", "026d95852f21b20ac255a7f8ee2bf62f49ddccbd0ef00c96e10e117c4dc19c5a", [: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", "c76137424e285e1bb66354ef1d983d0ef55ce9676c1ded208a941b3b33897b78"},
   "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
@@ -124,7 +124,7 @@
   "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": {:git, "https://github.com/benoitc/unicode_util_compat.git", "38d7bc105f51159e8ea3279c40121db9db1e652f", [tag: "0.3.1"]},
+  "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
   "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
   "web_push_encryption": {:hex, :web_push_encryption, "0.3.0", "598b5135e696fd1404dc8d0d7c0fa2c027244a4e5d5e5a98ba267f14fdeaabc8", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "f10bdd1afe527ede694749fb77a2f22f146a51b054c7fa541c9fd920fba7c875"},
   "websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []},

From 168687eef2d289703f9ec7fc8453d4beb6adf0e7 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Sat, 15 May 2021 13:15:02 +0200
Subject: [PATCH 260/339] media_proxy: switch from :crypto.hmac to :crypto.mac

---
 lib/pleroma/web/media_proxy.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/media_proxy.ex b/lib/pleroma/web/media_proxy.ex
index 5c32078aa..0b232f14b 100644
--- a/lib/pleroma/web/media_proxy.ex
+++ b/lib/pleroma/web/media_proxy.ex
@@ -127,7 +127,7 @@ def decode_url(encoded) do
   end
 
   defp signed_url(url) do
-    :crypto.hmac(:sha, Config.get([Endpoint, :secret_key_base]), url)
+    :crypto.mac(:hmac, :sha, Config.get([Endpoint, :secret_key_base]), url)
   end
 
   def filename(url_or_path) do

From 2768063387fcfb310eaacf517ad6fc3521e9eee6 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Sat, 22 May 2021 17:48:35 +0200
Subject: [PATCH 261/339] mix: Update dependencies

---
 mix.lock | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/mix.lock b/mix.lock
index b838d6f80..fa4249d43 100644
--- a/mix.lock
+++ b/mix.lock
@@ -55,7 +55,7 @@
   "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.1", "b87d81dad83f41fa3f2cbf1a923eae44c5ce559a7006728d47888c3e7eb7a6ce", [:make, :rebar3], [{:cowlib, "2.10.1", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "459e7c843c894f69878df60378e7fa4a4b5504a00066c02138d084435c2c7968"},
+  "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.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [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", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"},
   "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"},
@@ -73,12 +73,12 @@
   "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.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"},
+  "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.6", "e810a91fabc7adf63ab5fdbec5d9d3b492413b8cda5131a2a8aa34b4185eb9b4", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "bcf1d0a6826fb5aee01bae3d74474669a3fa8b2df274d094af54a25266a1ebd2"},
+  "mock": {:hex, :mock, "0.3.7", "75b3bbf1466d7e486ea2052a73c6e062c6256fb429d6797999ab02fa32f29e03", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4da49a4609e41fd99b7836945c26f373623ea968cfb6282742bcb94440cf7e5c"},
   "mogrify": {:hex, :mogrify, "0.7.4", "9b2496dde44b1ce12676f85d7dc531900939e6367bc537c7243a1b089435b32d", [:mix], [], "hexpm", "50d79e337fba6bc95bfbef918058c90f50b17eed9537771e61d4619488f099c3"},
   "mox": {:hex, :mox, "1.0.0", "4b3c7005173f47ff30641ba044eb0fe67287743eec9bd9545e37f3002b0a9f8b", [:mix], [], "hexpm", "201b0a20b7abdaaab083e9cf97884950f8a30a1350a1da403b3145e213c6f4df"},
   "myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
@@ -116,7 +116,7 @@
   "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.8", "026d95852f21b20ac255a7f8ee2bf62f49ddccbd0ef00c96e10e117c4dc19c5a", [: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", "c76137424e285e1bb66354ef1d983d0ef55ce9676c1ded208a941b3b33897b78"},
+  "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"},

From ab32ea44f0af54f9dd87f9a53378b1358f7ac1f8 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Sat, 22 May 2021 17:55:40 +0200
Subject: [PATCH 262/339] mix.exs: Apply OTP24 fixes to web_push_encryption

---
 lib/pleroma/http/web_push.ex | 4 ++--
 mix.exs                      | 3 ++-
 mix.lock                     | 2 +-
 3 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/lib/pleroma/http/web_push.ex b/lib/pleroma/http/web_push.ex
index 51f72fbf8..16bbe6e8c 100644
--- a/lib/pleroma/http/web_push.ex
+++ b/lib/pleroma/http/web_push.ex
@@ -5,8 +5,8 @@
 defmodule Pleroma.HTTP.WebPush do
   @moduledoc false
 
-  def post(url, payload, headers) do
+  def post(url, payload, headers, options \\ []) do
     list_headers = Map.to_list(headers)
-    Pleroma.HTTP.post(url, payload, list_headers)
+    Pleroma.HTTP.post(url, payload, list_headers, options)
   end
 end
diff --git a/mix.exs b/mix.exs
index 4ba698620..7ab8387f9 100644
--- a/mix.exs
+++ b/mix.exs
@@ -149,7 +149,8 @@ defp deps do
        git: "https://git.pleroma.social/pleroma/elixir-libraries/crypt.git",
        ref: "cf2aa3f11632e8b0634810a15b3e612c7526f6a3"},
       {:cors_plug, "~> 2.0"},
-      {:web_push_encryption, "~> 0.3"},
+      {: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"},
diff --git a/mix.lock b/mix.lock
index fa4249d43..aa09843f9 100644
--- a/mix.lock
+++ b/mix.lock
@@ -126,6 +126,6 @@
   "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"},
-  "web_push_encryption": {:hex, :web_push_encryption, "0.3.0", "598b5135e696fd1404dc8d0d7c0fa2c027244a4e5d5e5a98ba267f14fdeaabc8", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "f10bdd1afe527ede694749fb77a2f22f146a51b054c7fa541c9fd920fba7c875"},
+  "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", []},
 }

From 7c5e007b9c73a52a4c46674bd00ce32640c07cc3 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Sat, 22 May 2021 18:04:13 +0200
Subject: [PATCH 263/339] mix: Update pot to ~> 1.0

---
 mix.exs  | 2 +-
 mix.lock | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/mix.exs b/mix.exs
index 7ab8387f9..5d945bf5f 100644
--- a/mix.exs
+++ b/mix.exs
@@ -178,7 +178,7 @@ defp deps do
       {:quack, "~> 0.1.1"},
       {:joken, "~> 2.0"},
       {:benchee, "~> 1.0"},
-      {:pot, "~> 0.11"},
+      {: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"},
diff --git a/mix.lock b/mix.lock
index aa09843f9..1a0cae3ee 100644
--- a/mix.lock
+++ b/mix.lock
@@ -102,7 +102,7 @@
   "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, "0.11.0", "61bad869a94534739dd4614a25a619bc5c47b9970e9a0ea5bef4628036fc7a16", [:rebar3], [], "hexpm", "57ee6ee6bdeb639661ffafb9acefe3c8f966e45394de6a766813bb9e1be4e54b"},
+  "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"]},

From 5c3a0dd26e8ba818388ca6965e71600fd2ec07a1 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Mon, 31 May 2021 10:06:06 +0200
Subject: [PATCH 264/339] factory: Fix article_factory

---
 test/support/factory.ex | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/support/factory.ex b/test/support/factory.ex
index 5c4e65c81..c267dba4e 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -191,8 +191,8 @@ def direct_note_factory do
   end
 
   def article_factory do
-    note_factory()
-    |> Map.put("type", "Article")
+    %Pleroma.Object{data: data} = note_factory()
+    %Pleroma.Object{data: Map.merge(data, %{"type" => "Article"})}
   end
 
   def tombstone_factory do

From 24d66b60a0272ef4e78f2f9802682964059c44ce Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Mon, 31 May 2021 10:14:12 +0200
Subject: [PATCH 265/339] request_builder_test: mode :read got removed

---
 test/pleroma/http/request_builder_test.exs | 44 +++++++++++++---------
 1 file changed, 26 insertions(+), 18 deletions(-)

diff --git a/test/pleroma/http/request_builder_test.exs b/test/pleroma/http/request_builder_test.exs
index e9b0c4a8a..433beaac1 100644
--- a/test/pleroma/http/request_builder_test.exs
+++ b/test/pleroma/http/request_builder_test.exs
@@ -34,24 +34,32 @@ test "send custom user agent" do
 
   describe "add_param/4" do
     test "add file parameter" do
-      %Request{
-        body: %Tesla.Multipart{
-          boundary: _,
-          content_type_params: [],
-          parts: [
-            %Tesla.Multipart.Part{
-              body: %File.Stream{
-                line_or_bytes: 2048,
-                modes: [:raw, :read_ahead, :read, :binary],
-                path: "some-path/filename.png",
-                raw: true
-              },
-              dispositions: [name: "filename.png", filename: "filename.png"],
-              headers: []
-            }
-          ]
-        }
-      } = RequestBuilder.add_param(%Request{}, :file, "filename.png", "some-path/filename.png")
+      assert match?(
+               %Request{
+                 body: %Tesla.Multipart{
+                   boundary: _,
+                   content_type_params: [],
+                   parts: [
+                     %Tesla.Multipart.Part{
+                       body: %File.Stream{
+                         line_or_bytes: 2048,
+                         modes: [:raw, :read_ahead, :binary],
+                         path: "some-path/filename.png",
+                         raw: true
+                       },
+                       dispositions: [name: "filename.png", filename: "filename.png"],
+                       headers: []
+                     }
+                   ]
+                 }
+               },
+               RequestBuilder.add_param(
+                 %Request{},
+                 :file,
+                 "filename.png",
+                 "some-path/filename.png"
+               )
+             )
     end
 
     test "add key to body" do

From 11844084d0fce0bd94df66561c47ef21b7b38e1d Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Mon, 31 May 2021 10:41:31 +0200
Subject: [PATCH 266/339] =?UTF-8?q?MIME.valid=3F(type)=20=E2=86=92=20is=5F?=
 =?UTF-8?q?bitstring(type)=20&&=20MIME.extensions(type)=20!=3D=20[]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Since mime 1.6.0:

  warning: MIME.valid?/1 is deprecated. Use MIME.extensions(type) != [] instead

As for the bitstring(type) part it's because MIME.extensions only expects a string.
https://github.com/elixir-plug/mime/issues/43
---
 .../object_validators/attachment_validator.ex     |  2 +-
 lib/pleroma/web/activity_pub/transmogrifier.ex    | 15 +++++++++++----
 2 files changed, 12 insertions(+), 5 deletions(-)

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 bba2f5eb0..837787b9f 100644
--- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
@@ -61,7 +61,7 @@ def url_changeset(struct, data) do
   def fix_media_type(data) do
     data = Map.put_new(data, "mediaType", data["mimeType"])
 
-    if MIME.valid?(data["mediaType"]) do
+    if is_bitstring(data["mediaType"]) && MIME.extensions(data["mediaType"]) != [] do
       data
     else
       Map.put(data, "mediaType", "application/octet-stream")
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 7da29b197..51c0cc860 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -203,10 +203,17 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm
 
         media_type =
           cond do
-            is_map(url) && MIME.valid?(url["mediaType"]) -> url["mediaType"]
-            MIME.valid?(data["mediaType"]) -> data["mediaType"]
-            MIME.valid?(data["mimeType"]) -> data["mimeType"]
-            true -> nil
+            is_map(url) && MIME.extensions(url["mediaType"]) != [] ->
+              url["mediaType"]
+
+            is_bitstring(data["mediaType"]) && MIME.extensions(data["mediaType"]) != [] ->
+              data["mediaType"]
+
+            is_bitstring(data["mimeType"]) && MIME.extensions(data["mimeType"]) != [] ->
+              data["mimeType"]
+
+            true ->
+              nil
           end
 
         href =

From 2c401dafa1105b73f4b4141f96e8414612625420 Mon Sep 17 00:00:00 2001
From: io <eiy7rongai0g@paperboats.net>
Date: Fri, 4 Jun 2021 04:15:54 +0000
Subject: [PATCH 267/339] Improve opengraph embeds

This brings them more in line with Mastodon.
- Deduplicates display name from the title and content
- Removes arbitrary limits on the size of the embedded image
- Removes angled double quotes from embed descriptions. These would normally just indicate that the content is a quote, but that is already implied by the content being in an embed.
---
 .../web/metadata/providers/open_graph.ex      | 20 +++----------------
 .../web/metadata/providers/twitter_card.ex    | 13 ++++--------
 .../metadata/providers/twitter_card_test.exs  | 10 ++++++----
 3 files changed, 13 insertions(+), 30 deletions(-)

diff --git a/lib/pleroma/web/metadata/providers/open_graph.ex b/lib/pleroma/web/metadata/providers/open_graph.ex
index 1687b2634..18ddde84b 100644
--- a/lib/pleroma/web/metadata/providers/open_graph.ex
+++ b/lib/pleroma/web/metadata/providers/open_graph.ex
@@ -19,31 +19,18 @@ def build_tags(%{
       }) do
     attachments = build_attachments(object)
     scrubbed_content = Utils.scrub_html_and_truncate(object)
-    # Zero width space
-    content =
-      if scrubbed_content != "" and scrubbed_content != "\u200B" do
-        ": “" <> scrubbed_content <> "”"
-      else
-        ""
-      end
 
-    # Most previews only show og:title which is inconvenient. Instagram
-    # hacks this by putting the description in the title and making the
-    # description longer prefixed by how many likes and shares the post
-    # has. Here we use the descriptive nickname in the title, and expand
-    # the full account & nickname in the description. We also use the cute^Wevil
-    # smart quotes around the status text like Instagram, too.
     [
       {:meta,
        [
          property: "og:title",
-         content: "#{user.name}" <> content
+         content: Utils.user_name_string(user)
        ], []},
       {:meta, [property: "og:url", content: url], []},
       {:meta,
        [
          property: "og:description",
-         content: "#{Utils.user_name_string(user)}" <> content
+         content: scrubbed_content
        ], []},
       {:meta, [property: "og:type", content: "website"], []}
     ] ++
@@ -95,8 +82,7 @@ defp build_attachments(%{data: %{"attachment" => attachments}}) do
             "image" ->
               [
                 {:meta, [property: "og:image", content: Utils.attachment_url(url["href"])], []},
-                {:meta, [property: "og:image:width", content: 150], []},
-                {:meta, [property: "og:image:height", content: 150], []}
+                {:meta, [property: "og:image:alt", content: attachment["name"]], []}
                 | acc
               ]
 
diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex
index 58fc05cf9..589989a9d 100644
--- a/lib/pleroma/web/metadata/providers/twitter_card.ex
+++ b/lib/pleroma/web/metadata/providers/twitter_card.ex
@@ -16,17 +16,10 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
   def build_tags(%{activity_id: id, object: object, user: user}) do
     attachments = build_attachments(id, object)
     scrubbed_content = Utils.scrub_html_and_truncate(object)
-    # Zero width space
-    content =
-      if scrubbed_content != "" and scrubbed_content != "\u200B" do
-        "“" <> scrubbed_content <> "”"
-      else
-        ""
-      end
 
     [
       title_tag(user),
-      {:meta, [property: "twitter:description", content: content], []}
+      {:meta, [property: "twitter:description", content: scrubbed_content], []}
     ] ++
       if attachments == [] or Metadata.activity_nsfw?(object) do
         [
@@ -91,7 +84,9 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
                 {:meta, [property: "twitter:card", content: "player"], []},
                 {:meta, [property: "twitter:player", content: player_url(id)], []},
                 {:meta, [property: "twitter:player:width", content: "480"], []},
-                {:meta, [property: "twitter:player:height", content: "480"], []}
+                {:meta, [property: "twitter:player:height", content: "480"], []},
+                {:meta, [property: "twitter:player:stream", content: url["href"]], []},
+                {:meta, [property: "twitter:player:stream:content_type", content: url["mediaType"]], []}
                 | acc
               ]
 
diff --git a/test/pleroma/web/metadata/providers/twitter_card_test.exs b/test/pleroma/web/metadata/providers/twitter_card_test.exs
index a35e44356..3a2f7ca31 100644
--- a/test/pleroma/web/metadata/providers/twitter_card_test.exs
+++ b/test/pleroma/web/metadata/providers/twitter_card_test.exs
@@ -46,7 +46,7 @@ test "it uses summary twittercard if post has no attachment" do
 
     assert [
              {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []},
-             {:meta, [property: "twitter:description", content: "“pleroma in a nutshell”"], []},
+             {:meta, [property: "twitter:description", content: "pleroma in a nutshell"], []},
              {:meta, [property: "twitter:image", content: "http://localhost:4001/images/avi.png"],
               []},
              {:meta, [property: "twitter:card", content: "summary"], []}
@@ -91,7 +91,7 @@ test "it renders avatar not attachment if post is nsfw and unfurl_nsfw is disabl
 
     assert [
              {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []},
-             {:meta, [property: "twitter:description", content: "“pleroma in a nutshell”"], []},
+             {:meta, [property: "twitter:description", content: "pleroma in a nutshell"], []},
              {:meta, [property: "twitter:image", content: "http://localhost:4001/images/avi.png"],
               []},
              {:meta, [property: "twitter:card", content: "summary"], []}
@@ -134,7 +134,7 @@ test "it renders supported types of attachments and skips unknown types" do
 
     assert [
              {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []},
-             {:meta, [property: "twitter:description", content: "“pleroma in a nutshell”"], []},
+             {:meta, [property: "twitter:description", content: "pleroma in a nutshell"], []},
              {:meta, [property: "twitter:card", content: "summary_large_image"], []},
              {:meta, [property: "twitter:player", content: "https://pleroma.gov/tenshi.png"], []},
              {:meta, [property: "twitter:card", content: "player"], []},
@@ -144,7 +144,9 @@ test "it renders supported types of attachments and skips unknown types" do
                 content: Router.Helpers.o_status_url(Endpoint, :notice_player, activity.id)
               ], []},
              {:meta, [property: "twitter:player:width", content: "480"], []},
-             {:meta, [property: "twitter:player:height", content: "480"], []}
+             {:meta, [property: "twitter:player:height", content: "480"], []},
+             {:meta, [property: "twitter:player:stream", content: "https://pleroma.gov/about/juche.webm"], []},
+             {:meta, [property: "twitter:player:stream:content_type", content: "video/webm"], []}
            ] == result
   end
 end

From f58928cf1c69adf9f16837e0ca86167b38375f94 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 4 Jun 2021 12:30:10 -0500
Subject: [PATCH 268/339] Add missing deprecation warning left out of !2842

---
 lib/pleroma/config/deprecation_warnings.ex    | 26 ++++++++++++++++++-
 .../config/deprecation_warnings_test.exs      | 10 +++++++
 2 files changed, 35 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex
index 24aa5993b..fedd58a7e 100644
--- a/lib/pleroma/config/deprecation_warnings.ex
+++ b/lib/pleroma/config/deprecation_warnings.ex
@@ -41,7 +41,8 @@ def warn do
          :ok <- check_gun_pool_options(),
          :ok <- check_activity_expiration_config(),
          :ok <- check_remote_ip_plug_name(),
-         :ok <- check_uploders_s3_public_endpoint() do
+         :ok <- check_uploders_s3_public_endpoint(),
+         :ok <- check_old_chat_shoutbox() do
       :ok
     else
       _ ->
@@ -215,4 +216,27 @@ def check_uploders_s3_public_endpoint do
       :ok
     end
   end
+
+  @spec check_old_chat_shoutbox() :: :ok | nil
+  def check_old_chat_shoutbox do
+    instance_config = Pleroma.Config.get([:instance])
+    chat_config = Pleroma.Config.get([:chat]) || []
+
+    use_old_config =
+      Keyword.has_key?(instance_config, :chat_limit) or
+        Keyword.has_key?(chat_config, :enabled)
+
+    if use_old_config do
+      Logger.error("""
+      !!!DEPRECATION WARNING!!!
+      Your config is using the old namespace for the Shoutbox configuration. You need to convert to the new namespace. e.g.,
+      \n* `config :pleroma, :chat, enabled` and `config :pleroma, :instance, chat_limit` are now equal to:
+      \n* `config :pleroma, :shout, enabled` and `config :pleroma, :shout, limit`
+      """)
+
+      :error
+    else
+      :ok
+    end
+  end
 end
diff --git a/test/pleroma/config/deprecation_warnings_test.exs b/test/pleroma/config/deprecation_warnings_test.exs
index 15f4982ea..ccf86634f 100644
--- a/test/pleroma/config/deprecation_warnings_test.exs
+++ b/test/pleroma/config/deprecation_warnings_test.exs
@@ -146,4 +146,14 @@ test "pool timeout" do
                "Your config is using old setting name `timeout` instead of `recv_timeout` in pool settings"
     end
   end
+
+  test "check_old_chat_shoutbox/0" do
+    clear_config([:instance, :chat_limit], 1_000)
+    clear_config([:chat, :enabled], true)
+
+    assert capture_log(fn ->
+             DeprecationWarnings.check_old_chat_shoutbox()
+           end) =~
+             "Your config is using the old namespace for the Shoutbox configuration."
+  end
 end

From 7d350b73f58664eb822efaa5f522fcf2bd38f669 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Fri, 4 Jun 2021 19:57:48 +0200
Subject: [PATCH 269/339] web endpoint: Use Config.get directly instead of a
 tuple

Fixes a lot of warnings like the following while running the testsuite:

  warning: passing a {module, function, args} tuple to Plug.Parsers.MULTIPART is deprecated. Please see Plug.Parsers.MULTIPART module docs for better approaches to configuration

This might mean no more dynamic configuration but there seems to be the same limitation two lines underneath anyway.
---
 lib/pleroma/web/endpoint.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index 8e274de88..7591d0ae5 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -102,7 +102,7 @@ defmodule Pleroma.Web.Endpoint do
   plug(Plug.Parsers,
     parsers: [
       :urlencoded,
-      {:multipart, length: {Config, :get, [[:instance, :upload_limit]]}},
+      {:multipart, length: Config.get([:instance, :upload_limit])},
       :json
     ],
     pass: ["*/*"],

From eb7313b0d364ce6a0298d43fc86403d2e7dfc739 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Wed, 21 Oct 2020 10:23:10 +0200
Subject: [PATCH 270/339] Pipeline Ingestion: Page

---
 lib/pleroma/web/activity_pub/activity_pub.ex  |  2 +-
 .../web/activity_pub/object_validator.ex      | 15 ++---
 ...ator.ex => article_note_page_validator.ex} |  4 +-
 lib/pleroma/web/activity_pub/side_effects.ex  |  2 +-
 .../web/activity_pub/transmogrifier.ex        | 62 +------------------
 test/fixtures/tesla_mock/lemmy-page.json      | 17 +++++
 test/fixtures/tesla_mock/lemmy-user.json      | 27 ++++++++
 ...s => article_note_page_validator_test.exs} |  6 +-
 .../transmogrifier/page_handling_test.exs     | 36 +++++++++++
 9 files changed, 96 insertions(+), 75 deletions(-)
 rename lib/pleroma/web/activity_pub/object_validators/{article_note_validator.ex => article_note_page_validator.ex} (96%)
 create mode 100644 test/fixtures/tesla_mock/lemmy-page.json
 create mode 100644 test/fixtures/tesla_mock/lemmy-user.json
 rename test/pleroma/web/activity_pub/object_validators/{article_note_validator_test.exs => article_note_page_validator_test.exs} (76%)
 create mode 100644 test/pleroma/web/activity_pub/transmogrifier/page_handling_test.exs

diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 18368943d..30b4f65d3 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -88,7 +88,7 @@ defp increase_replies_count_if_reply(%{
 
   defp increase_replies_count_if_reply(_create_data), do: :noop
 
-  @object_types ~w[ChatMessage Question Answer Audio Video Event Article Note]
+  @object_types ~w[ChatMessage Question Answer Audio Video Event Article Note Page]
   @impl true
   def persist(%{"type" => type} = object, meta) when type in @object_types do
     with {:ok, object} <- Object.create(object) do
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 248a12a36..e642916d8 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -20,7 +20,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
   alias Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
-  alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator
+  alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
@@ -102,7 +102,7 @@ def validate(
         %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
         meta
       )
-      when objtype in ~w[Question Answer Audio Video Event Article Note] do
+      when objtype in ~w[Question Answer Audio Video Event Article Note Page] do
     with {:ok, object_data} <- cast_and_apply(object),
          meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
          {:ok, create_activity} <-
@@ -115,15 +115,16 @@ def validate(
   end
 
   def validate(%{"type" => type} = object, meta)
-      when type in ~w[Event Question Audio Video Article Note] do
+      when type in ~w[Event Question Audio Video Article Note Page] do
     validator =
       case type do
         "Event" -> EventValidator
         "Question" -> QuestionValidator
         "Audio" -> AudioVideoValidator
         "Video" -> AudioVideoValidator
-        "Article" -> ArticleNoteValidator
-        "Note" -> ArticleNoteValidator
+        "Article" -> ArticleNotePageValidator
+        "Note" -> ArticleNotePageValidator
+        "Page" -> ArticleNotePageValidator
       end
 
     with {:ok, object} <-
@@ -195,8 +196,8 @@ def cast_and_apply(%{"type" => "Event"} = object) do
     EventValidator.cast_and_apply(object)
   end
 
-  def cast_and_apply(%{"type" => type} = object) when type in ~w[Article Note] do
-    ArticleNoteValidator.cast_and_apply(object)
+  def cast_and_apply(%{"type" => type} = object) when type in ~w[Article Note Page] do
+    ArticleNotePageValidator.cast_and_apply(object)
   end
 
   def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
similarity index 96%
rename from lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
rename to lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
index 193f85f49..0d987116c 100644
--- a/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
@@ -2,7 +2,7 @@
 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
   use Ecto.Schema
 
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
@@ -113,7 +113,7 @@ def changeset(struct, data) do
 
   defp validate_data(data_cng) do
     data_cng
-    |> validate_inclusion(:type, ["Article", "Note"])
+    |> validate_inclusion(:type, ["Article", "Note", "Page"])
     |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
     |> CommonValidations.validate_any_presence([:cc, :to])
     |> CommonValidations.validate_fields_match([:actor, :attributedTo])
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 674356d9a..3670de45c 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -436,7 +436,7 @@ def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do
   end
 
   def handle_object_creation(%{"type" => objtype} = object, meta)
-      when objtype in ~w[Audio Video Question Event Article Note] do
+      when objtype in ~w[Audio Video Question Event Article Note Page] do
     with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
       {:ok, object, meta}
     end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 51c0cc860..142af1a13 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -353,29 +353,6 @@ defp get_reported(objects) do
     end)
   end
 
-  # Compatibility wrapper for Mastodon votes
-  defp handle_create(%{"object" => %{"type" => "Answer"}} = data, _user) do
-    handle_incoming(data)
-  end
-
-  defp handle_create(%{"object" => object} = data, user) do
-    %{
-      to: data["to"],
-      object: object,
-      actor: user,
-      context: object["context"],
-      local: false,
-      published: data["published"],
-      additional:
-        Map.take(data, [
-          "cc",
-          "directMessage",
-          "id"
-        ])
-    }
-    |> ActivityPub.create()
-  end
-
   def handle_incoming(data, options \\ [])
 
   # Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
@@ -407,43 +384,6 @@ def handle_incoming(%{"id" => ""}, _options), do: :error
   def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id) < 8,
     do: :error
 
-  # TODO: validate those with a Ecto scheme
-  # - tags
-  # - emoji
-  def handle_incoming(
-        %{"type" => "Create", "object" => %{"type" => "Page"} = object} = data,
-        options
-      ) do
-    actor = Containment.get_actor(data)
-
-    with nil <- Activity.get_create_by_object_ap_id(object["id"]),
-         {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(actor) do
-      data =
-        data
-        |> Map.put("object", fix_object(object, options))
-        |> Map.put("actor", actor)
-        |> fix_addressing()
-
-      with {:ok, created_activity} <- handle_create(data, user) do
-        reply_depth = (options[:depth] || 0) + 1
-
-        if Federator.allowed_thread_distance?(reply_depth) do
-          for reply_id <- replies(object) do
-            Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{
-              "id" => reply_id,
-              "depth" => reply_depth
-            })
-          end
-        end
-
-        {:ok, created_activity}
-      end
-    else
-      %Activity{} = activity -> {:ok, activity}
-      _e -> :error
-    end
-  end
-
   def handle_incoming(
         %{"type" => "Listen", "object" => %{"type" => "Audio"} = object} = data,
         options
@@ -507,7 +447,7 @@ def handle_incoming(
         %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
         options
       )
-      when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note} do
+      when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note Page} do
     fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
 
     object =
diff --git a/test/fixtures/tesla_mock/lemmy-page.json b/test/fixtures/tesla_mock/lemmy-page.json
new file mode 100644
index 000000000..f07097a0e
--- /dev/null
+++ b/test/fixtures/tesla_mock/lemmy-page.json
@@ -0,0 +1,17 @@
+{
+  "commentsEnabled": true,
+  "sensitive": false,
+  "stickied": false,
+  "attributedTo": "https://enterprise.lemmy.ml/u/nutomic",
+  "summary": "Hello Federation!",
+  "url": "https://enterprise.lemmy.ml/pictrs/image/US52d9DPvf.jpg",
+  "image": {
+    "type": "Image",
+    "url": "https://enterprise.lemmy.ml/pictrs/image/lwFAcXHUjS.jpg"
+  },
+  "published": "2020-09-14T15:03:11.909105+00:00",
+  "to": "https://enterprise.lemmy.ml/c/main",
+  "@context": "https://www.w3.org/ns/activitystreams",
+  "id": "https://enterprise.lemmy.ml/post/3",
+  "type": "Page"
+}
diff --git a/test/fixtures/tesla_mock/lemmy-user.json b/test/fixtures/tesla_mock/lemmy-user.json
new file mode 100644
index 000000000..d0e9066ac
--- /dev/null
+++ b/test/fixtures/tesla_mock/lemmy-user.json
@@ -0,0 +1,27 @@
+{
+  "publicKey": {
+    "id": "https://enterprise.lemmy.ml/u/nutomic#main-key",
+    "owner": "https://enterprise.lemmy.ml/u/nutomic",
+    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvfwAYPxp1gOk2HcCRoUd\nupoecvmnpzRc5Gu6/N3YQyOyRsrYuiYLNQq2cgM3kcU80ZeEetkwkYgXkRJOKu/b\nBWb7i1zt2tdr5k6lUdW8dfCyjht8ooFPQdov8J3QYHfgBHyUYxuCNfSujryxx2wu\nLQcdjRQa5NIWcomSO8OXmCF5/Yhg2XWCbtnlxEq6Y+AFddr1mAlTOy5pBr5d+xZz\njLw/U3CioNJ79yGi/sJhgp6IyJqtUSoN3b4BgRIEts2QVvn44W1rQy9wCbRYQrO1\nBcB9Wel4k3rJJK8uHg+LpHVMaZppkNaWGkMBhMbzr8qmIlcNWNi7cbMK/p5vyviy\nSwIDAQAB\n-----END PUBLIC KEY-----\n"
+  },
+  "inbox": "https://enterprise.lemmy.ml/u/nutomic/inbox",
+  "preferredUsername": "Nutomic",
+  "endpoints": {
+    "sharedInbox": "https://enterprise.lemmy.ml/inbox"
+  },
+  "summary": "some bio",
+  "icon": {
+    "type": "Image",
+    "url": "https://enterprise.lemmy.ml/pictrs/image/F6Z7QcWZRJ.jpg"
+  },
+  "image": {
+    "type": "Image",
+    "url": "https://enterprise.lemmy.ml:/pictrs/image/Q79N9oCDEG.png"
+  },
+  "published": "2020-09-14T14:54:53.080949+00:00",
+  "updated": "2020-10-14T10:58:28.139178+00:00",
+  "@context": "https://www.w3.org/ns/activitystreams",
+  "id": "https://enterprise.lemmy.ml/u/nutomic",
+  "type": "Person",
+  "name": "nutomic"
+}
diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs
similarity index 76%
rename from test/pleroma/web/activity_pub/object_validators/article_note_validator_test.exs
rename to test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs
index e408c85c3..720c17d8d 100644
--- a/test/pleroma/web/activity_pub/object_validators/article_note_validator_test.exs
+++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs
@@ -2,10 +2,10 @@
 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidatorTest do
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest do
   use Pleroma.DataCase, async: true
 
-  alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator
+  alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator
   alias Pleroma.Web.ActivityPub.Utils
 
   import Pleroma.Factory
@@ -29,7 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidatorTest do
     end
 
     test "a basic note validates", %{note: note} do
-      %{valid?: true} = ArticleNoteValidator.cast_and_validate(note)
+      %{valid?: true} = ArticleNotePageValidator.cast_and_validate(note)
     end
   end
 end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/page_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/page_handling_test.exs
new file mode 100644
index 000000000..4ac71e066
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/page_handling_test.exs
@@ -0,0 +1,36 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.PageHandlingTest do
+  use Oban.Testing, repo: Pleroma.Repo
+  use Pleroma.DataCase
+
+  alias Pleroma.Object.Fetcher
+
+  test "Lemmy Page" do
+    Tesla.Mock.mock(fn
+      %{url: "https://enterprise.lemmy.ml/post/3"} ->
+        %Tesla.Env{
+          status: 200,
+          headers: [{"content-type", "application/activity+json"}],
+          body: File.read!("test/fixtures/tesla_mock/lemmy-page.json")
+        }
+
+      %{url: "https://enterprise.lemmy.ml/u/nutomic"} ->
+        %Tesla.Env{
+          status: 200,
+          headers: [{"content-type", "application/activity+json"}],
+          body: File.read!("test/fixtures/tesla_mock/lemmy-user.json")
+        }
+    end)
+
+    {:ok, object} = Fetcher.fetch_object_from_id("https://enterprise.lemmy.ml/post/3")
+
+    assert object.data["summary"] == "Hello Federation!"
+    assert object.data["published"] == "2020-09-14T15:03:11.909105Z"
+
+    # WAT
+    assert object.data["url"] == "https://enterprise.lemmy.ml/pictrs/image/US52d9DPvf.jpg"
+  end
+end

From d5daf59f8863e8762041becff0d0878edd15440e Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 4 Jun 2021 15:35:56 -0500
Subject: [PATCH 271/339] Fix warning for misuse of clear_config/2

The old warning message was producing an improperly formatted suggestion.
---
 test/support/helpers.ex | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/test/support/helpers.ex b/test/support/helpers.ex
index 856a6a376..34f1505d0 100644
--- a/test/support/helpers.ex
+++ b/test/support/helpers.ex
@@ -42,8 +42,7 @@ defmacro clear_config(config_path, temp_setting) do
     # Displaying a warning to prevent unintentional clearing of all but one keys in section
     if Keyword.keyword?(temp_setting) and length(temp_setting) == 1 do
       Logger.warn(
-        "Please change to `clear_config([section]); clear_config([section, key], value)`: " <>
-          "#{inspect(config_path)}, #{inspect(temp_setting)}"
+        "Please change `clear_config([section], key: value)` to `clear_config([section, key], value)`"
       )
     end
 

From eb150e7d883b3bb01991462e409969375d6b77da Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Fri, 4 Jun 2021 15:50:10 -0500
Subject: [PATCH 272/339] Document OTP 24 support so we remember to add it to
 the official release notes / announcement

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2d1ff5b7b..24c029bf3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Changed
 
 - **Breaking:** Configuration: `:chat, enabled` moved to `:shout, enabled` and `:instance, chat_limit` moved to `:shout, limit`
+- Support for Erlang/OTP 24
 - The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
 - HTTPSecurityPlug now sends a response header to opt out of Google's FLoC (Federated Learning of Cohorts) targeted advertising.
 - Email address is now returned if requesting user is the owner of the user account so it can be exposed in client and FE user settings UIs.

From 1c3fe43d231428fee392afd726363193fdcb8008 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Wed, 2 Jun 2021 16:34:32 -0500
Subject: [PATCH 273/339] ReverseProxy: create Client.Wrapper to call client
 from config Speeds up recompilation by reducing compile-time cycles

---
 lib/pleroma/reverse_proxy.ex                |  2 +-
 lib/pleroma/reverse_proxy/client.ex         | 18 -------------
 lib/pleroma/reverse_proxy/client/wrapper.ex | 29 +++++++++++++++++++++
 3 files changed, 30 insertions(+), 19 deletions(-)
 create mode 100644 lib/pleroma/reverse_proxy/client/wrapper.ex

diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex
index 406f7e2b8..ec69a1779 100644
--- a/lib/pleroma/reverse_proxy.ex
+++ b/lib/pleroma/reverse_proxy.ex
@@ -411,7 +411,7 @@ defp increase_read_duration(_) do
     {:ok, :no_duration_limit, :no_duration_limit}
   end
 
-  defp client, do: Pleroma.ReverseProxy.Client
+  defp client, do: Pleroma.ReverseProxy.Client.Wrapper
 
   defp track_failed_url(url, error, opts) do
     ttl =
diff --git a/lib/pleroma/reverse_proxy/client.ex b/lib/pleroma/reverse_proxy/client.ex
index 8698fa2e1..75243d2dc 100644
--- a/lib/pleroma/reverse_proxy/client.ex
+++ b/lib/pleroma/reverse_proxy/client.ex
@@ -17,22 +17,4 @@ defmodule Pleroma.ReverseProxy.Client do
   @callback stream_body(map()) :: {:ok, binary(), map()} | :done | {:error, atom() | String.t()}
 
   @callback close(reference() | pid() | map()) :: :ok
-
-  def request(method, url, headers, body \\ "", opts \\ []) do
-    client().request(method, url, headers, body, opts)
-  end
-
-  def stream_body(ref), do: client().stream_body(ref)
-
-  def close(ref), do: client().close(ref)
-
-  defp client do
-    :tesla
-    |> Application.get_env(:adapter)
-    |> client()
-  end
-
-  defp client(Tesla.Adapter.Hackney), do: Pleroma.ReverseProxy.Client.Hackney
-  defp client(Tesla.Adapter.Gun), do: Pleroma.ReverseProxy.Client.Tesla
-  defp client(_), do: Pleroma.Config.get!(Pleroma.ReverseProxy.Client)
 end
diff --git a/lib/pleroma/reverse_proxy/client/wrapper.ex b/lib/pleroma/reverse_proxy/client/wrapper.ex
new file mode 100644
index 000000000..06dd29fea
--- /dev/null
+++ b/lib/pleroma/reverse_proxy/client/wrapper.ex
@@ -0,0 +1,29 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ReverseProxy.Client.Wrapper do
+  @moduledoc "Meta-client that calls the appropriate client from the config."
+  @behaviour Pleroma.ReverseProxy.Client
+
+  @impl true
+  def request(method, url, headers, body \\ "", opts \\ []) do
+    client().request(method, url, headers, body, opts)
+  end
+
+  @impl true
+  def stream_body(ref), do: client().stream_body(ref)
+
+  @impl true
+  def close(ref), do: client().close(ref)
+
+  defp client do
+    :tesla
+    |> Application.get_env(:adapter)
+    |> client()
+  end
+
+  defp client(Tesla.Adapter.Hackney), do: Pleroma.ReverseProxy.Client.Hackney
+  defp client(Tesla.Adapter.Gun), do: Pleroma.ReverseProxy.Client.Tesla
+  defp client(_), do: Pleroma.Config.get!(Pleroma.ReverseProxy.Client)
+end

From 879c2db0bdb875fc2b3139cf60b1fd03bb66a01b Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Mon, 7 Jun 2021 11:18:14 -0500
Subject: [PATCH 274/339] Docs: /api/v1/pleroma/notification_settings -->
 /api/pleroma/notification_settings

---
 docs/development/API/pleroma_api.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/development/API/pleroma_api.md b/docs/development/API/pleroma_api.md
index d896f0ce7..8f6422da0 100644
--- a/docs/development/API/pleroma_api.md
+++ b/docs/development/API/pleroma_api.md
@@ -300,7 +300,7 @@ See [Admin-API](admin_api.md)
 * Note: Behaves exactly the same as `POST /api/v1/upload`.
   Can only accept images - any attempt to upload non-image files will be met with `HTTP 415 Unsupported Media Type`.
 
-## `/api/v1/pleroma/notification_settings`
+## `/api/pleroma/notification_settings`
 ### Updates user notification settings
 * Method `PUT`
 * Authentication: required

From fe4c4a7178ac4df76a9f4a83c05f8445c5ff9bf2 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Mon, 7 Jun 2021 14:22:08 -0500
Subject: [PATCH 275/339] MRF: create MRF.Policy behaviour separate from MRF
 module Speeds up recompilation by reducing compile-time deps

---
 docs/configuration/mrf.md                        |  2 +-
 lib/pleroma/web/activity_pub/mrf.ex              | 11 -----------
 .../mrf/activity_expiration_policy.ex            |  2 +-
 .../activity_pub/mrf/anti_followbot_policy.ex    |  2 +-
 .../activity_pub/mrf/anti_link_spam_policy.ex    |  2 +-
 lib/pleroma/web/activity_pub/mrf/drop_policy.ex  |  2 +-
 .../web/activity_pub/mrf/ensure_re_prepended.ex  |  2 +-
 .../web/activity_pub/mrf/follow_bot_policy.ex    |  2 +-
 .../mrf/force_bot_unlisted_policy.ex             |  2 +-
 .../web/activity_pub/mrf/hashtag_policy.ex       |  2 +-
 .../web/activity_pub/mrf/hellthread_policy.ex    |  2 +-
 .../web/activity_pub/mrf/keyword_policy.ex       |  2 +-
 .../mrf/media_proxy_warming_policy.ex            |  2 +-
 .../web/activity_pub/mrf/mention_policy.ex       |  2 +-
 .../web/activity_pub/mrf/no_empty_policy.ex      |  2 +-
 lib/pleroma/web/activity_pub/mrf/no_op_policy.ex |  2 +-
 .../mrf/no_placeholder_text_policy.ex            |  2 +-
 .../web/activity_pub/mrf/normalize_markup.ex     |  2 +-
 .../web/activity_pub/mrf/object_age_policy.ex    |  2 +-
 lib/pleroma/web/activity_pub/mrf/policy.ex       | 16 ++++++++++++++++
 .../web/activity_pub/mrf/reject_non_public.ex    |  2 +-
 .../web/activity_pub/mrf/simple_policy.ex        |  2 +-
 .../web/activity_pub/mrf/steal_emoji_policy.ex   |  2 +-
 .../web/activity_pub/mrf/subchain_policy.ex      |  2 +-
 lib/pleroma/web/activity_pub/mrf/tag_policy.ex   |  2 +-
 .../activity_pub/mrf/user_allow_list_policy.ex   |  2 +-
 .../web/activity_pub/mrf/vocabulary_policy.ex    |  2 +-
 test/fixtures/modules/good_mrf.ex                |  2 +-
 test/support/mrf_module_mock.ex                  |  2 +-
 29 files changed, 43 insertions(+), 38 deletions(-)
 create mode 100644 lib/pleroma/web/activity_pub/mrf/policy.ex

diff --git a/docs/configuration/mrf.md b/docs/configuration/mrf.md
index 9e8c0a2d7..5618634a2 100644
--- a/docs/configuration/mrf.md
+++ b/docs/configuration/mrf.md
@@ -82,7 +82,7 @@ For example, here is a sample policy module which rewrites all messages to "new
 ```elixir
 defmodule Pleroma.Web.ActivityPub.MRF.RewritePolicy do
   @moduledoc "MRF policy which rewrites all Notes to have 'new message content'."
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   # Catch messages which contain Note objects with actual data to filter.
   # Capture the object as `object`, the message content as `content` and the
diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex
index f2fec3ff6..250dac695 100644
--- a/lib/pleroma/web/activity_pub/mrf.ex
+++ b/lib/pleroma/web/activity_pub/mrf.ex
@@ -51,17 +51,6 @@ defmodule Pleroma.Web.ActivityPub.MRF do
 
   @required_description_keys [:key, :related_policy]
 
-  @callback filter(Map.t()) :: {:ok | :reject, Map.t()}
-  @callback describe() :: {:ok | :error, Map.t()}
-  @callback config_description() :: %{
-              optional(:children) => [map()],
-              key: atom(),
-              related_policy: String.t(),
-              label: String.t(),
-              description: String.t()
-            }
-  @optional_callbacks config_description: 0
-
   def filter(policies, %{} = message) do
     policies
     |> Enum.reduce({:ok, message}, fn
diff --git a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
index fc347236e..e78254280 100644
--- a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
@@ -4,7 +4,7 @@
 
 defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do
   @moduledoc "Adds expiration to all local Create activities"
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   @impl true
   def filter(activity) do
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
index b8bfdc3ce..851e95d22 100644
--- a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
@@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
 
   @moduledoc "Prevent followbots from following with a bit of heuristic"
 
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   # XXX: this should become User.normalize_by_ap_id() or similar, really.
   defp normalize_by_ap_id(%{"id" => id}), do: User.get_cached_by_ap_id(id)
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
index 40b19c3ab..cdf17fd28 100644
--- a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
   alias Pleroma.User
 
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   require Logger
 
diff --git a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex
index 378175205..b3ff86eed 100644
--- a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do
   require Logger
   @moduledoc "Drop and log everything received"
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   @impl true
   def filter(object) do
diff --git a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
index 2d3a10889..fad8d873b 100644
--- a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
+++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
   alias Pleroma.Object
 
   @moduledoc "Ensure a re: is prepended on replies to a post with a Subject"
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   @reply_prefix Regex.compile!("^re:[[:space:]]*", [:caseless])
 
diff --git a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
index 7307c9c14..7cf7de068 100644
--- a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
@@ -1,5 +1,5 @@
 defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
   alias Pleroma.Config
   alias Pleroma.User
   alias Pleroma.Web.CommonAPI
diff --git a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex
index 51dbb1ad4..11871375e 100644
--- a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex
@@ -4,7 +4,7 @@
 
 defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do
   alias Pleroma.User
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
   @moduledoc "Remove bot posts from federated timeline"
 
   require Pleroma.Constants
diff --git a/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex b/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex
index def0c437c..b7db4fa3d 100644
--- a/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex
@@ -14,7 +14,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
   Note: This MRF Policy is always enabled, if you want to disable it you have to set empty lists.
   """
 
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   defp check_reject(message, hashtags) do
     if Enum.any?(Config.get([:mrf_hashtag, :reject]), fn match -> match in hashtags end) do
diff --git a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
index 768a669f3..504bd4d57 100644
--- a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
 
   @moduledoc "Block messages with too much mentions (configurable)"
 
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   defp delist_message(message, threshold) when threshold > 0 do
     follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address
diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
index f91b51bcf..646008dd9 100644
--- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
@@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
 
   @moduledoc "Reject or Word-Replace messages with a keyword or regex"
 
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
   defp string_matches?(string, _) when not is_binary(string) do
     false
   end
diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
index 8dbf44071..25289d3a4 100644
--- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
@@ -4,7 +4,7 @@
 
 defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
   @moduledoc "Preloads any attachments in the MediaProxy cache by prefetching them"
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   alias Pleroma.HTTP
   alias Pleroma.Web.MediaProxy
diff --git a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
index 877277d4f..05b28e4f5 100644
--- a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
   @moduledoc "Block messages which mention a user"
 
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   @impl true
   def filter(%{"type" => "Create"} = message) do
diff --git a/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex
index f4c5db05c..80bef591e 100644
--- a/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex
@@ -4,7 +4,7 @@
 
 defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do
   @moduledoc "Filter local activities which have no content"
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   alias Pleroma.Web.Endpoint
 
diff --git a/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex
index 2ebc0674d..25031946c 100644
--- a/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex
@@ -4,7 +4,7 @@
 
 defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do
   @moduledoc "Does nothing (lets the messages go through unmodified)"
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   @impl true
   def filter(object) do
diff --git a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
index b658d7d41..90272766c 100644
--- a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
@@ -4,7 +4,7 @@
 
 defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do
   @moduledoc "Ensure no content placeholder is present (such as the dot from mastodon)"
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   @impl true
   def filter(
diff --git a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
index 2ad3fde0b..0d7146738 100644
--- a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
+++ b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
   @moduledoc "Scrub configured hypertext markup"
   alias Pleroma.HTML
 
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   @impl true
   def filter(%{"type" => "Create", "object" => child_object} = object) do
diff --git a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
index aac24c0ec..9a211fd44 100644
--- a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
   require Pleroma.Constants
 
   @moduledoc "Filter activities depending on their age"
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   defp check_date(%{"object" => %{"published" => published}} = message) do
     with %DateTime{} = now <- DateTime.utc_now(),
diff --git a/lib/pleroma/web/activity_pub/mrf/policy.ex b/lib/pleroma/web/activity_pub/mrf/policy.ex
new file mode 100644
index 000000000..a4a960c01
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/policy.ex
@@ -0,0 +1,16 @@
+# 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.MRF.Policy do
+  @callback filter(Map.t()) :: {:ok | :reject, Map.t()}
+  @callback describe() :: {:ok | :error, Map.t()}
+  @callback config_description() :: %{
+              optional(:children) => [map()],
+              key: atom(),
+              related_policy: String.t(),
+              label: String.t(),
+              description: String.t()
+            }
+  @optional_callbacks config_description: 0
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
index 47a43c6a2..b9d3e52c7 100644
--- a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
+++ b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
@@ -8,7 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
   alias Pleroma.Config
   alias Pleroma.User
 
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   require Pleroma.Constants
 
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index d40348cb1..30562ac08 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -4,7 +4,7 @@
 
 defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
   @moduledoc "Filter activities depending on their origin instance"
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   alias Pleroma.Config
   alias Pleroma.FollowingRelationship
diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
index 4c5e33619..c28f14a41 100644
--- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
@@ -8,7 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
   alias Pleroma.Config
 
   @moduledoc "Detect new emojis by their shortcode and steals them"
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   defp accept_host?(host), do: host in Config.get([:mrf_steal_emoji, :hosts], [])
 
diff --git a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
index 86965d47b..f84d7cc71 100644
--- a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
@@ -8,7 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
 
   require Logger
 
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   defp lookup_subchain(actor) do
     with matches <- Config.get([:mrf_subchain, :match_actor]),
diff --git a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
index 528093ac0..56ae654f2 100644
--- a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
@@ -4,7 +4,7 @@
 
 defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
   alias Pleroma.User
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
   @moduledoc """
      Apply policies based on user tags
 
diff --git a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
index 65b371bf3..1bcb3688b 100644
--- a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
   alias Pleroma.Config
 
   @moduledoc "Accept-list of users from specified instances"
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   defp filter_by_list(object, []), do: {:ok, object}
 
diff --git a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
index ce559a239..20f57f609 100644
--- a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
   @moduledoc "Filter messages which belong to certain activity vocabularies"
 
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   @impl true
   def filter(%{"type" => "Undo", "object" => child_message} = message) do
diff --git a/test/fixtures/modules/good_mrf.ex b/test/fixtures/modules/good_mrf.ex
index 39d0f14ec..5afa1c1d1 100644
--- a/test/fixtures/modules/good_mrf.ex
+++ b/test/fixtures/modules/good_mrf.ex
@@ -1,5 +1,5 @@
 defmodule Fixtures.Modules.GoodMRF do
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   @impl true
   def filter(a), do: {:ok, a}
diff --git a/test/support/mrf_module_mock.ex b/test/support/mrf_module_mock.ex
index 4dfdeb3b4..4d21d7fe0 100644
--- a/test/support/mrf_module_mock.ex
+++ b/test/support/mrf_module_mock.ex
@@ -3,7 +3,7 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule MRFModuleMock do
-  @behaviour Pleroma.Web.ActivityPub.MRF
+  @behaviour Pleroma.Web.ActivityPub.MRF.Policy
 
   @impl true
   def filter(message), do: {:ok, message}

From 6fcfa33e4ec39c66e07ca8187f618b9c6f5c25c3 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Mon, 7 Jun 2021 14:51:25 -0500
Subject: [PATCH 276/339] Fix MRF.config_descriptions/0

---
 lib/pleroma/web/activity_pub/mrf.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex
index 250dac695..ac00fa54b 100644
--- a/lib/pleroma/web/activity_pub/mrf.ex
+++ b/lib/pleroma/web/activity_pub/mrf.ex
@@ -131,7 +131,7 @@ def describe(policies) do
   def describe, do: get_policies() |> describe()
 
   def config_descriptions do
-    Pleroma.Web.ActivityPub.MRF
+    Pleroma.Web.ActivityPub.MRF.Policy
     |> Pleroma.Docs.Generator.list_behaviour_implementations()
     |> config_descriptions()
   end

From bc51dea4257d4faaff70f8511dcd3702489ebb74 Mon Sep 17 00:00:00 2001
From: feld <feld@feld.me>
Date: Mon, 7 Jun 2021 20:02:28 +0000
Subject: [PATCH 277/339] Update lib/mix/tasks/pleroma/database.ex

---
 lib/mix/tasks/pleroma/database.ex | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index bcde07774..57f73d12b 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -97,10 +97,10 @@ def run(["prune_objects" | args]) do
     |> Repo.delete_all(timeout: :infinity)
 
     prune_hashtags_query = """
-    delete from hashtags as ht
-    where not exists (
-      select 1 from hashtags_objects hto
-      where ht.id = hto.hashtag_id)
+    DELETE FROM hashtags AS ht
+    WHERE NOT EXISTS (
+      SELECT 1 FROM hashtags_objects hto
+      WHERE ht.id = hto.hashtag_id)
     """
 
     Repo.query(prune_hashtags_query)

From c31338abe6cc371c877d04a47f06ba5800653e50 Mon Sep 17 00:00:00 2001
From: feld <feld@feld.me>
Date: Mon, 7 Jun 2021 20:04:27 +0000
Subject: [PATCH 278/339] Update CHANGELOG.md

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5bb4b1e73..209432409 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ### Fixed
 - Don't crash so hard when email settings are invalid.
+- Mix task: pleroma.database prune_objects
 
 ## Unreleased (Patch)
 

From 4ca380490f1e42ef6b12c4b12ba9efabb89472fd Mon Sep 17 00:00:00 2001
From: feld <feld@feld.me>
Date: Mon, 7 Jun 2021 20:05:18 +0000
Subject: [PATCH 279/339] Update CHANGELOG.md

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 209432409..2c6f57691 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,7 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ### Fixed
 - Don't crash so hard when email settings are invalid.
-- Mix task: pleroma.database prune_objects
+- Mix task `pleroma.database prune_objects`
 
 ## Unreleased (Patch)
 

From 10abbf13ba9ba036e20e4018279c5a8f2faa19b9 Mon Sep 17 00:00:00 2001
From: feld <feld@feld.me>
Date: Mon, 7 Jun 2021 20:07:27 +0000
Subject: [PATCH 280/339] Update CHANGELOG.md

---
 CHANGELOG.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 18879a6df..61796271a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Checking activated Upload Filters for required commands.
 - Mix task `pleroma.database prune_objects`
 
+### Removed
+
+- **Breaking**: Remove deprecated `/api/qvitter/statuses/notifications/read` (replaced by `/api/v1/pleroma/notifications/read`)
+
 ## Unreleased (Patch)
 
 ### Fixed

From 9a357d63f0d8381492a0ffe0e507f233fc35fbf8 Mon Sep 17 00:00:00 2001
From: feld <feld@feld.me>
Date: Mon, 7 Jun 2021 20:07:59 +0000
Subject: [PATCH 281/339] Update CHANGELOG.md

---
 CHANGELOG.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 61796271a..daa8f2ff6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,7 +28,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mix task `pleroma.database prune_objects`
 
 ### Removed
-
 - **Breaking**: Remove deprecated `/api/qvitter/statuses/notifications/read` (replaced by `/api/v1/pleroma/notifications/read`)
 
 ## Unreleased (Patch)

From 264458531ad1024134fc2f53eded1d1075394536 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Mon, 7 Jun 2021 15:47:50 -0500
Subject: [PATCH 282/339] Formatting

---
 lib/pleroma/web/metadata/providers/twitter_card.ex        | 3 ++-
 test/pleroma/web/metadata/providers/twitter_card_test.exs | 6 +++++-
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex
index 589989a9d..12c372d77 100644
--- a/lib/pleroma/web/metadata/providers/twitter_card.ex
+++ b/lib/pleroma/web/metadata/providers/twitter_card.ex
@@ -86,7 +86,8 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
                 {:meta, [property: "twitter:player:width", content: "480"], []},
                 {:meta, [property: "twitter:player:height", content: "480"], []},
                 {:meta, [property: "twitter:player:stream", content: url["href"]], []},
-                {:meta, [property: "twitter:player:stream:content_type", content: url["mediaType"]], []}
+                {:meta,
+                 [property: "twitter:player:stream:content_type", content: url["mediaType"]], []}
                 | acc
               ]
 
diff --git a/test/pleroma/web/metadata/providers/twitter_card_test.exs b/test/pleroma/web/metadata/providers/twitter_card_test.exs
index 3a2f7ca31..196bca20a 100644
--- a/test/pleroma/web/metadata/providers/twitter_card_test.exs
+++ b/test/pleroma/web/metadata/providers/twitter_card_test.exs
@@ -145,7 +145,11 @@ test "it renders supported types of attachments and skips unknown types" do
               ], []},
              {:meta, [property: "twitter:player:width", content: "480"], []},
              {:meta, [property: "twitter:player:height", content: "480"], []},
-             {:meta, [property: "twitter:player:stream", content: "https://pleroma.gov/about/juche.webm"], []},
+             {:meta,
+              [
+                property: "twitter:player:stream",
+                content: "https://pleroma.gov/about/juche.webm"
+              ], []},
              {:meta, [property: "twitter:player:stream:content_type", content: "video/webm"], []}
            ] == result
   end

From d87dfcb5f0f91ad6fa9fccd47996c2bc54701553 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Mon, 7 Jun 2021 15:51:52 -0500
Subject: [PATCH 283/339] Put custom guards in Web.Utils.Guards Speeds up
 recompilation by removing a compile-time cycle on AdminAPI.Search

---
 lib/pleroma/user/query.ex           |  2 +-
 lib/pleroma/web/admin_api/search.ex |  6 ------
 lib/pleroma/web/utils/guards.ex     | 13 +++++++++++++
 3 files changed, 14 insertions(+), 7 deletions(-)
 create mode 100644 lib/pleroma/web/utils/guards.ex

diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex
index fa46545da..ac807fc79 100644
--- a/lib/pleroma/user/query.ex
+++ b/lib/pleroma/user/query.ex
@@ -27,7 +27,7 @@ defmodule Pleroma.User.Query do
       - e.g. Pleroma.User.Query.build(%{ap_id: ["http://ap_id1", "http://ap_id2"]})
   """
   import Ecto.Query
-  import Pleroma.Web.AdminAPI.Search, only: [not_empty_string: 1]
+  import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
 
   alias Pleroma.FollowingRelationship
   alias Pleroma.User
diff --git a/lib/pleroma/web/admin_api/search.ex b/lib/pleroma/web/admin_api/search.ex
index eeeebdf4e..01d974479 100644
--- a/lib/pleroma/web/admin_api/search.ex
+++ b/lib/pleroma/web/admin_api/search.ex
@@ -10,12 +10,6 @@ defmodule Pleroma.Web.AdminAPI.Search do
 
   @page_size 50
 
-  defmacro not_empty_string(string) do
-    quote do
-      is_binary(unquote(string)) and unquote(string) != ""
-    end
-  end
-
   @spec user(map()) :: {:ok, [User.t()], pos_integer()}
   def user(params \\ %{}) do
     query =
diff --git a/lib/pleroma/web/utils/guards.ex b/lib/pleroma/web/utils/guards.ex
new file mode 100644
index 000000000..aea7b6314
--- /dev/null
+++ b/lib/pleroma/web/utils/guards.ex
@@ -0,0 +1,13 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Utils.Guards do
+  @moduledoc """
+  Project-wide custom guards.
+  See: https://hexdocs.pm/elixir/master/patterns-and-guards.html#custom-patterns-and-guards-expressions
+  """
+
+  @doc "Checks for non-empty string"
+  defguard not_empty_string(string) when is_binary(string) and string != ""
+end

From f5ef7fe43bb42d5cd641666194f1d780499e1e09 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Mon, 7 Jun 2021 16:06:53 -0500
Subject: [PATCH 284/339] Fix test warnings

---
 .../controllers/config_controller_test.exs    | 43 +++++++++----------
 1 file changed, 20 insertions(+), 23 deletions(-)

diff --git a/test/pleroma/web/admin_api/controllers/config_controller_test.exs b/test/pleroma/web/admin_api/controllers/config_controller_test.exs
index d8ca07cd3..7c786c389 100644
--- a/test/pleroma/web/admin_api/controllers/config_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/config_controller_test.exs
@@ -1427,30 +1427,27 @@ test "custom instance thumbnail", %{conn: conn} do
         ]
       }
 
-      res =
-        assert conn
-               |> put_req_header("content-type", "application/json")
-               |> post("/api/pleroma/admin/config", %{"configs" => [params]})
-               |> json_response_and_validate_schema(200)
+      assert conn
+             |> put_req_header("content-type", "application/json")
+             |> post("/api/pleroma/admin/config", %{"configs" => [params]})
+             |> json_response_and_validate_schema(200) ==
+               %{
+                 "configs" => [
+                   %{
+                     "db" => [":instance_thumbnail"],
+                     "group" => ":pleroma",
+                     "key" => ":instance",
+                     "value" => params["value"]
+                   }
+                 ],
+                 "need_reboot" => false
+               }
 
-      assert res == %{
-               "configs" => [
-                 %{
-                   "db" => [":instance_thumbnail"],
-                   "group" => ":pleroma",
-                   "key" => ":instance",
-                   "value" => params["value"]
-                 }
-               ],
-               "need_reboot" => false
-             }
-
-      _res =
-        assert conn
-               |> get("/api/v1/instance")
-               |> json_response_and_validate_schema(200)
-
-      assert res = %{"thumbnail" => "https://example.com/media/new_thumbnail.jpg"}
+      assert conn
+             |> get("/api/v1/instance")
+             |> json_response_and_validate_schema(200)
+             |> Map.take(["thumbnail"]) ==
+               %{"thumbnail" => "https://example.com/media/new_thumbnail.jpg"}
     end
 
     test "Concurrent Limiter", %{conn: conn} do

From a5ae0432ed2353c714a4c212e418e6cc0ea91eb6 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Mon, 7 Jun 2021 16:09:47 -0500
Subject: [PATCH 285/339] Test was named incorrectly and did not execute

---
 .../pleroma/web/{shout_channel_test.ex => shout_channel_test.exs} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename test/pleroma/web/{shout_channel_test.ex => shout_channel_test.exs} (100%)

diff --git a/test/pleroma/web/shout_channel_test.ex b/test/pleroma/web/shout_channel_test.exs
similarity index 100%
rename from test/pleroma/web/shout_channel_test.ex
rename to test/pleroma/web/shout_channel_test.exs

From 017f947fc111eb98c964cd984fdb073623407b0e Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Mon, 7 Jun 2021 16:10:24 -0500
Subject: [PATCH 286/339] Channel name was incorrect. We left it as chat:public
 for backwards compatibility.

---
 test/pleroma/web/shout_channel_test.exs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/pleroma/web/shout_channel_test.exs b/test/pleroma/web/shout_channel_test.exs
index a266543d2..5c86efe9f 100644
--- a/test/pleroma/web/shout_channel_test.exs
+++ b/test/pleroma/web/shout_channel_test.exs
@@ -14,7 +14,7 @@ defmodule Pleroma.Web.ShoutChannelTest do
 
     {:ok, _, socket} =
       socket(UserSocket, "", %{user_name: user.nickname})
-      |> subscribe_and_join(ShoutChannel, "shout:public")
+      |> subscribe_and_join(ShoutChannel, "chat:public")
 
     {:ok, socket: socket}
   end

From bdaa1d45123ae8dd7f0138aa09b96d3104e1e58e Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Mon, 7 Jun 2021 17:07:40 -0500
Subject: [PATCH 287/339] Upload.Filter: use generic types in @spec Speeds up
 recompilation by reducing compile-time deps

---
 lib/pleroma/upload/filter.ex | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/upload/filter.ex b/lib/pleroma/upload/filter.ex
index c677d4b9f..e5db2fb20 100644
--- a/lib/pleroma/upload/filter.ex
+++ b/lib/pleroma/upload/filter.ex
@@ -15,13 +15,13 @@ defmodule Pleroma.Upload.Filter do
 
   require Logger
 
-  @callback filter(Pleroma.Upload.t()) ::
+  @callback filter(upload :: struct()) ::
               {:ok, :filtered}
               | {:ok, :noop}
-              | {:ok, :filtered, Pleroma.Upload.t()}
+              | {:ok, :filtered, upload :: struct()}
               | {:error, any()}
 
-  @spec filter([module()], Pleroma.Upload.t()) :: {:ok, Pleroma.Upload.t()} | {:error, any()}
+  @spec filter([module()], upload :: struct()) :: {:ok, upload :: struct()} | {:error, any()}
 
   def filter([], upload) do
     {:ok, upload}

From 1399b82f7be708562848031944f6caab8f193bda Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Mon, 7 Jun 2021 18:07:54 -0500
Subject: [PATCH 288/339] Create WrapperAuthenticator and simplify
 Authenticator behaviour Speeds up recompilation by reducing compile-time
 cycles

---
 lib/pleroma/web/auth/authenticator.ex         | 63 +------------------
 lib/pleroma/web/auth/helpers.ex               | 33 ++++++++++
 lib/pleroma/web/auth/ldap_authenticator.ex    |  3 +-
 lib/pleroma/web/auth/pleroma_authenticator.ex |  3 +-
 lib/pleroma/web/auth/wrapper_authenticator.ex | 42 +++++++++++++
 lib/pleroma/web/o_auth/o_auth_controller.ex   |  2 +-
 .../web/templates/o_auth/o_auth/show.html.eex |  2 +-
 .../controllers/remote_follow_controller.ex   |  4 +-
 test/pleroma/web/auth/authenticator_test.exs  | 14 ++---
 9 files changed, 91 insertions(+), 75 deletions(-)
 create mode 100644 lib/pleroma/web/auth/helpers.ex
 create mode 100644 lib/pleroma/web/auth/wrapper_authenticator.ex

diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex
index 84741ee11..3fe9718c4 100644
--- a/lib/pleroma/web/auth/authenticator.ex
+++ b/lib/pleroma/web/auth/authenticator.ex
@@ -3,68 +3,11 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.Auth.Authenticator do
-  alias Pleroma.Registration
-  alias Pleroma.User
-
-  def implementation do
-    Pleroma.Config.get(
-      Pleroma.Web.Auth.Authenticator,
-      Pleroma.Web.Auth.PleromaAuthenticator
-    )
-  end
-
-  @callback get_user(Plug.Conn.t()) :: {:ok, User.t()} | {:error, any()}
-  def get_user(plug), do: implementation().get_user(plug)
-
-  @callback create_from_registration(Plug.Conn.t(), Registration.t()) ::
+  @callback get_user(Plug.Conn.t()) :: {:ok, user :: struct()} | {:error, any()}
+  @callback create_from_registration(Plug.Conn.t(), registration :: struct()) ::
               {:ok, User.t()} | {:error, any()}
-  def create_from_registration(plug, registration),
-    do: implementation().create_from_registration(plug, registration)
-
-  @callback get_registration(Plug.Conn.t()) :: {:ok, Registration.t()} | {:error, any()}
-  def get_registration(plug), do: implementation().get_registration(plug)
-
+  @callback get_registration(Plug.Conn.t()) :: {:ok, registration :: struct()} | {:error, any()}
   @callback handle_error(Plug.Conn.t(), any()) :: any()
-  def handle_error(plug, error),
-    do: implementation().handle_error(plug, error)
-
   @callback auth_template() :: String.t() | nil
-  def auth_template do
-    # Note: `config :pleroma, :auth_template, "..."` support is deprecated
-    implementation().auth_template() ||
-      Pleroma.Config.get([:auth, :auth_template], Pleroma.Config.get(:auth_template)) ||
-      "show.html"
-  end
-
   @callback oauth_consumer_template() :: String.t() | nil
-  def oauth_consumer_template do
-    implementation().oauth_consumer_template() ||
-      Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html")
-  end
-
-  @doc "Gets user by nickname or email for auth."
-  @spec fetch_user(String.t()) :: User.t() | nil
-  def fetch_user(name) do
-    User.get_by_nickname_or_email(name)
-  end
-
-  # Gets name and password from conn
-  #
-  @spec fetch_credentials(Plug.Conn.t() | map()) ::
-          {:ok, {name :: any, password :: any}} | {:error, :invalid_credentials}
-  def fetch_credentials(%Plug.Conn{params: params} = _),
-    do: fetch_credentials(params)
-
-  def fetch_credentials(params) do
-    case params do
-      %{"authorization" => %{"name" => name, "password" => password}} ->
-        {:ok, {name, password}}
-
-      %{"grant_type" => "password", "username" => name, "password" => password} ->
-        {:ok, {name, password}}
-
-      _ ->
-        {:error, :invalid_credentials}
-    end
-  end
 end
diff --git a/lib/pleroma/web/auth/helpers.ex b/lib/pleroma/web/auth/helpers.ex
new file mode 100644
index 000000000..c566de8d4
--- /dev/null
+++ b/lib/pleroma/web/auth/helpers.ex
@@ -0,0 +1,33 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Auth.Helpers do
+  alias Pleroma.User
+
+  @doc "Gets user by nickname or email for auth."
+  @spec fetch_user(String.t()) :: User.t() | nil
+  def fetch_user(name) do
+    User.get_by_nickname_or_email(name)
+  end
+
+  # Gets name and password from conn
+  #
+  @spec fetch_credentials(Plug.Conn.t() | map()) ::
+          {:ok, {name :: any, password :: any}} | {:error, :invalid_credentials}
+  def fetch_credentials(%Plug.Conn{params: params} = _),
+    do: fetch_credentials(params)
+
+  def fetch_credentials(params) do
+    case params do
+      %{"authorization" => %{"name" => name, "password" => password}} ->
+        {:ok, {name, password}}
+
+      %{"grant_type" => "password", "username" => name, "password" => password} ->
+        {:ok, {name, password}}
+
+      _ ->
+        {:error, :invalid_credentials}
+    end
+  end
+end
diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex
index 17e08a2a6..f77e8d203 100644
--- a/lib/pleroma/web/auth/ldap_authenticator.ex
+++ b/lib/pleroma/web/auth/ldap_authenticator.ex
@@ -7,8 +7,7 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
 
   require Logger
 
-  import Pleroma.Web.Auth.Authenticator,
-    only: [fetch_credentials: 1, fetch_user: 1]
+  import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]
 
   @behaviour Pleroma.Web.Auth.Authenticator
   @base Pleroma.Web.Auth.PleromaAuthenticator
diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex
index 401f23c9f..68472e75f 100644
--- a/lib/pleroma/web/auth/pleroma_authenticator.ex
+++ b/lib/pleroma/web/auth/pleroma_authenticator.ex
@@ -8,8 +8,7 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
   alias Pleroma.User
   alias Pleroma.Web.Plugs.AuthenticationPlug
 
-  import Pleroma.Web.Auth.Authenticator,
-    only: [fetch_credentials: 1, fetch_user: 1]
+  import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]
 
   @behaviour Pleroma.Web.Auth.Authenticator
 
diff --git a/lib/pleroma/web/auth/wrapper_authenticator.ex b/lib/pleroma/web/auth/wrapper_authenticator.ex
new file mode 100644
index 000000000..c67082f7b
--- /dev/null
+++ b/lib/pleroma/web/auth/wrapper_authenticator.ex
@@ -0,0 +1,42 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Auth.WrapperAuthenticator do
+  @behaviour Pleroma.Web.Auth.Authenticator
+
+  defp implementation do
+    Pleroma.Config.get(
+      Pleroma.Web.Auth.Authenticator,
+      Pleroma.Web.Auth.PleromaAuthenticator
+    )
+  end
+
+  @impl true
+  def get_user(plug), do: implementation().get_user(plug)
+
+  @impl true
+  def create_from_registration(plug, registration),
+    do: implementation().create_from_registration(plug, registration)
+
+  @impl true
+  def get_registration(plug), do: implementation().get_registration(plug)
+
+  @impl true
+  def handle_error(plug, error),
+    do: implementation().handle_error(plug, error)
+
+  @impl true
+  def auth_template do
+    # Note: `config :pleroma, :auth_template, "..."` support is deprecated
+    implementation().auth_template() ||
+      Pleroma.Config.get([:auth, :auth_template], Pleroma.Config.get(:auth_template)) ||
+      "show.html"
+  end
+
+  @impl true
+  def oauth_consumer_template do
+    implementation().oauth_consumer_template() ||
+      Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html")
+  end
+end
diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex
index 42f4d768f..b9aadc6a4 100644
--- a/lib/pleroma/web/o_auth/o_auth_controller.ex
+++ b/lib/pleroma/web/o_auth/o_auth_controller.ex
@@ -12,7 +12,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
   alias Pleroma.Registration
   alias Pleroma.Repo
   alias Pleroma.User
-  alias Pleroma.Web.Auth.Authenticator
+  alias Pleroma.Web.Auth.WrapperAuthenticator, as: Authenticator
   alias Pleroma.Web.ControllerHelper
   alias Pleroma.Web.OAuth.App
   alias Pleroma.Web.OAuth.Authorization
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
index 2846ec7e7..181a9519a 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
@@ -61,5 +61,5 @@
 <% end %>
 
 <%= if Pleroma.Config.oauth_consumer_enabled?() do %>
-  <%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %>
+  <%= render @view_module, Pleroma.Web.Auth.WrapperAuthenticator.oauth_consumer_template(), assigns %>
 <% end %>
diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
index 9843cc362..42d7601ed 100644
--- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
@@ -11,8 +11,8 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
   alias Pleroma.MFA
   alias Pleroma.Object.Fetcher
   alias Pleroma.User
-  alias Pleroma.Web.Auth.Authenticator
   alias Pleroma.Web.Auth.TOTPAuthenticator
+  alias Pleroma.Web.Auth.WrapperAuthenticator
   alias Pleroma.Web.CommonAPI
 
   @status_types ["Article", "Event", "Note", "Video", "Page", "Question"]
@@ -88,7 +88,7 @@ def do_follow(%{assigns: %{user: %User{} = user}} = conn, %{"user" => %{"id" =>
   #
   def do_follow(conn, %{"authorization" => %{"name" => _, "password" => _, "id" => id}}) do
     with {_, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},
-         {_, {:ok, user}, _} <- {:auth, Authenticator.get_user(conn), followee},
+         {_, {:ok, user}, _} <- {:auth, WrapperAuthenticator.get_user(conn), followee},
          {_, _, _, false} <- {:mfa_required, followee, user, MFA.require?(user)},
          {:ok, _, _, _} <- CommonAPI.follow(user, followee) do
       redirect(conn, to: "/users/#{followee.id}")
diff --git a/test/pleroma/web/auth/authenticator_test.exs b/test/pleroma/web/auth/authenticator_test.exs
index e1f30e835..26779df03 100644
--- a/test/pleroma/web/auth/authenticator_test.exs
+++ b/test/pleroma/web/auth/authenticator_test.exs
@@ -5,38 +5,38 @@
 defmodule Pleroma.Web.Auth.AuthenticatorTest do
   use Pleroma.Web.ConnCase, async: true
 
-  alias Pleroma.Web.Auth.Authenticator
+  alias Pleroma.Web.Auth.Helpers
   import Pleroma.Factory
 
   describe "fetch_user/1" do
     test "returns user by name" do
       user = insert(:user)
-      assert Authenticator.fetch_user(user.nickname) == user
+      assert Helpers.fetch_user(user.nickname) == user
     end
 
     test "returns user by email" do
       user = insert(:user)
-      assert Authenticator.fetch_user(user.email) == user
+      assert Helpers.fetch_user(user.email) == user
     end
 
     test "returns nil" do
-      assert Authenticator.fetch_user("email") == nil
+      assert Helpers.fetch_user("email") == nil
     end
   end
 
   describe "fetch_credentials/1" do
     test "returns name and password from authorization params" do
       params = %{"authorization" => %{"name" => "test", "password" => "test-pass"}}
-      assert Authenticator.fetch_credentials(params) == {:ok, {"test", "test-pass"}}
+      assert Helpers.fetch_credentials(params) == {:ok, {"test", "test-pass"}}
     end
 
     test "returns name and password with grant_type 'password'" do
       params = %{"grant_type" => "password", "username" => "test", "password" => "test-pass"}
-      assert Authenticator.fetch_credentials(params) == {:ok, {"test", "test-pass"}}
+      assert Helpers.fetch_credentials(params) == {:ok, {"test", "test-pass"}}
     end
 
     test "returns error" do
-      assert Authenticator.fetch_credentials(%{}) == {:error, :invalid_credentials}
+      assert Helpers.fetch_credentials(%{}) == {:error, :invalid_credentials}
     end
   end
 end

From 0877b120c30a69788070de8990ac46ef0cdf23b3 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Sat, 22 May 2021 11:41:55 -0500
Subject: [PATCH 289/339] Pleroma.Web.ControllerHelper.truthy_param?/1 -->
 Pleroma.Web.Params.truthy_param?/1 Breaks cycle in
 lib/pleroma/web/api_spec/operations/status_operation.ex

---
 lib/pleroma/web/api_spec/schemas/boolean_like.ex |  2 +-
 lib/pleroma/web/common_api/activity_draft.ex     |  2 +-
 lib/pleroma/web/common_api/utils.ex              |  6 +++---
 lib/pleroma/web/controller_helper.ex             | 14 ++------------
 .../controllers/account_controller.ex            |  4 ++--
 lib/pleroma/web/o_auth/o_auth_controller.ex      |  4 ++--
 lib/pleroma/web/params.ex                        | 16 ++++++++++++++++
 7 files changed, 27 insertions(+), 21 deletions(-)
 create mode 100644 lib/pleroma/web/params.ex

diff --git a/lib/pleroma/web/api_spec/schemas/boolean_like.ex b/lib/pleroma/web/api_spec/schemas/boolean_like.ex
index 778158f66..1feda3baa 100644
--- a/lib/pleroma/web/api_spec/schemas/boolean_like.ex
+++ b/lib/pleroma/web/api_spec/schemas/boolean_like.ex
@@ -34,7 +34,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.BooleanLike do
 
   def cast(%Cast{value: value} = context) do
     context
-    |> Map.put(:value, Pleroma.Web.ControllerHelper.truthy_param?(value))
+    |> Map.put(:value, Pleroma.Web.Params.truthy_param?(value))
     |> Cast.ok()
   end
 end
diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
index 80a9fa7bb..d750c9de3 100644
--- a/lib/pleroma/web/common_api/activity_draft.ex
+++ b/lib/pleroma/web/common_api/activity_draft.ex
@@ -223,7 +223,7 @@ defp object(draft) do
   end
 
   defp preview?(draft) do
-    preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params[:preview])
+    preview? = Pleroma.Web.Params.truthy_param?(draft.params[:preview])
     %__MODULE__{draft | preview?: preview?}
   end
 
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 4cc34002d..4ba31a8b8 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -4,7 +4,6 @@
 
 defmodule Pleroma.Web.CommonAPI.Utils do
   import Pleroma.Web.Gettext
-  import Pleroma.Web.ControllerHelper, only: [truthy_param?: 1]
 
   alias Calendar.Strftime
   alias Pleroma.Activity
@@ -18,6 +17,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
   alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.CommonAPI.ActivityDraft
   alias Pleroma.Web.MediaProxy
+  alias Pleroma.Web.Params
   alias Pleroma.Web.Plugs.AuthenticationPlug
 
   require Logger
@@ -160,7 +160,7 @@ def make_poll_data(%{poll: %{options: options, expires_in: expires_in}} = data)
         |> DateTime.add(expires_in)
         |> DateTime.to_iso8601()
 
-      key = if truthy_param?(data.poll[:multiple]), do: "anyOf", else: "oneOf"
+      key = if Params.truthy_param?(data.poll[:multiple]), do: "anyOf", else: "oneOf"
       poll = %{"type" => "Question", key => option_notes, "closed" => end_time}
 
       {:ok, {poll, emoji}}
@@ -203,7 +203,7 @@ def make_content_html(%ActivityDraft{} = draft) do
     attachment_links =
       draft.params
       |> Map.get("attachment_links", Config.get([:instance, :attachment_links]))
-      |> truthy_param?()
+      |> Params.truthy_param?()
 
     content_type = get_content_type(draft.params[:content_type])
 
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex
index 61d65e7a3..afa152482 100644
--- a/lib/pleroma/web/controller_helper.ex
+++ b/lib/pleroma/web/controller_helper.ex
@@ -6,17 +6,7 @@ defmodule Pleroma.Web.ControllerHelper do
   use Pleroma.Web, :controller
 
   alias Pleroma.Pagination
-
-  # As in Mastodon API, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
-  @falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"]
-
-  def explicitly_falsy_param?(value), do: value in @falsy_param_values
-
-  # Note: `nil` and `""` are considered falsy values in Pleroma
-  def falsy_param?(value),
-    do: explicitly_falsy_param?(value) or value in [nil, ""]
-
-  def truthy_param?(value), do: not falsy_param?(value)
+  alias Pleroma.Web.Params
 
   def json_response(conn, status, _) when status in [204, :no_content] do
     conn
@@ -123,6 +113,6 @@ def embed_relationships?(params) do
     # To do once OpenAPI transition mess is over: just `truthy_param?(params[:with_relationships])`
     params
     |> Map.get(:with_relationships, params["with_relationships"])
-    |> truthy_param?()
+    |> Params.truthy_param?()
   end
 end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 7a1e99044..d9bb6f95e 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   import Pleroma.Web.ControllerHelper,
     only: [
       add_link_headers: 2,
-      truthy_param?: 1,
       assign_account_by_id: 2,
       embed_relationships?: 1,
       json_response: 3
@@ -25,6 +24,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   alias Pleroma.Web.MastodonAPI.MastodonAPIController
   alias Pleroma.Web.MastodonAPI.StatusView
   alias Pleroma.Web.OAuth.OAuthController
+  alias Pleroma.Web.Params
   alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
   alias Pleroma.Web.Plugs.OAuthScopesPlug
   alias Pleroma.Web.Plugs.RateLimiter
@@ -188,7 +188,7 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
         :accepts_chat_messages
       ]
       |> Enum.reduce(%{}, fn key, acc ->
-        Maps.put_if_present(acc, key, params[key], &{:ok, truthy_param?(&1)})
+        Maps.put_if_present(acc, key, params[key], &{:ok, Params.truthy_param?(&1)})
       end)
       |> Maps.put_if_present(:name, params[:display_name])
       |> Maps.put_if_present(:bio, params[:note])
diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex
index b9aadc6a4..6201d6e00 100644
--- a/lib/pleroma/web/o_auth/o_auth_controller.ex
+++ b/lib/pleroma/web/o_auth/o_auth_controller.ex
@@ -13,7 +13,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do
   alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web.Auth.WrapperAuthenticator, as: Authenticator
-  alias Pleroma.Web.ControllerHelper
   alias Pleroma.Web.OAuth.App
   alias Pleroma.Web.OAuth.Authorization
   alias Pleroma.Web.OAuth.MFAController
@@ -23,6 +22,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
   alias Pleroma.Web.OAuth.Token
   alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
   alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
+  alias Pleroma.Web.Params
   alias Pleroma.Web.Plugs.RateLimiter
 
   require Logger
@@ -50,7 +50,7 @@ def authorize(%Plug.Conn{} = conn, %{"authorization" => _} = params) do
   end
 
   def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, %{"force_login" => _} = params) do
-    if ControllerHelper.truthy_param?(params["force_login"]) do
+    if Params.truthy_param?(params["force_login"]) do
       do_authorize(conn, params)
     else
       handle_existing_authorization(conn, params)
diff --git a/lib/pleroma/web/params.ex b/lib/pleroma/web/params.ex
new file mode 100644
index 000000000..dd7059c89
--- /dev/null
+++ b/lib/pleroma/web/params.ex
@@ -0,0 +1,16 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Params do
+  # As in Mastodon API, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
+  @falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"]
+
+  defp explicitly_falsy_param?(value), do: value in @falsy_param_values
+
+  # Note: `nil` and `""` are considered falsy values in Pleroma
+  defp falsy_param?(value),
+    do: explicitly_falsy_param?(value) or value in [nil, ""]
+
+  def truthy_param?(value), do: not falsy_param?(value)
+end

From ec65b7ae294eaf7f908960950ee573bf8d038715 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Mon, 7 Jun 2021 16:01:26 -0500
Subject: [PATCH 290/339] Pleroma.Web.Params --> Pleroma.Web.Utils.Params

---
 lib/pleroma/web/api_spec/schemas/boolean_like.ex               | 2 +-
 lib/pleroma/web/common_api/activity_draft.ex                   | 2 +-
 lib/pleroma/web/common_api/utils.ex                            | 2 +-
 lib/pleroma/web/controller_helper.ex                           | 2 +-
 lib/pleroma/web/mastodon_api/controllers/account_controller.ex | 2 +-
 lib/pleroma/web/o_auth/o_auth_controller.ex                    | 2 +-
 lib/pleroma/web/{ => utils}/params.ex                          | 2 +-
 7 files changed, 7 insertions(+), 7 deletions(-)
 rename lib/pleroma/web/{ => utils}/params.ex (94%)

diff --git a/lib/pleroma/web/api_spec/schemas/boolean_like.ex b/lib/pleroma/web/api_spec/schemas/boolean_like.ex
index 1feda3baa..94c5020ca 100644
--- a/lib/pleroma/web/api_spec/schemas/boolean_like.ex
+++ b/lib/pleroma/web/api_spec/schemas/boolean_like.ex
@@ -34,7 +34,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.BooleanLike do
 
   def cast(%Cast{value: value} = context) do
     context
-    |> Map.put(:value, Pleroma.Web.Params.truthy_param?(value))
+    |> Map.put(:value, Pleroma.Web.Utils.Params.truthy_param?(value))
     |> Cast.ok()
   end
 end
diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
index d750c9de3..c691d71d2 100644
--- a/lib/pleroma/web/common_api/activity_draft.ex
+++ b/lib/pleroma/web/common_api/activity_draft.ex
@@ -223,7 +223,7 @@ defp object(draft) do
   end
 
   defp preview?(draft) do
-    preview? = Pleroma.Web.Params.truthy_param?(draft.params[:preview])
+    preview? = Pleroma.Web.Utils.Params.truthy_param?(draft.params[:preview])
     %__MODULE__{draft | preview?: preview?}
   end
 
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 4ba31a8b8..256d95b95 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -17,7 +17,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
   alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.CommonAPI.ActivityDraft
   alias Pleroma.Web.MediaProxy
-  alias Pleroma.Web.Params
+  alias Pleroma.Web.Utils.Params
   alias Pleroma.Web.Plugs.AuthenticationPlug
 
   require Logger
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex
index afa152482..7b84b43e4 100644
--- a/lib/pleroma/web/controller_helper.ex
+++ b/lib/pleroma/web/controller_helper.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.ControllerHelper do
   use Pleroma.Web, :controller
 
   alias Pleroma.Pagination
-  alias Pleroma.Web.Params
+  alias Pleroma.Web.Utils.Params
 
   def json_response(conn, status, _) when status in [204, :no_content] do
     conn
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index d9bb6f95e..b4ec66367 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -24,7 +24,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   alias Pleroma.Web.MastodonAPI.MastodonAPIController
   alias Pleroma.Web.MastodonAPI.StatusView
   alias Pleroma.Web.OAuth.OAuthController
-  alias Pleroma.Web.Params
+  alias Pleroma.Web.Utils.Params
   alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
   alias Pleroma.Web.Plugs.OAuthScopesPlug
   alias Pleroma.Web.Plugs.RateLimiter
diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex
index 6201d6e00..06c706f8e 100644
--- a/lib/pleroma/web/o_auth/o_auth_controller.ex
+++ b/lib/pleroma/web/o_auth/o_auth_controller.ex
@@ -22,7 +22,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
   alias Pleroma.Web.OAuth.Token
   alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
   alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
-  alias Pleroma.Web.Params
+  alias Pleroma.Web.Utils.Params
   alias Pleroma.Web.Plugs.RateLimiter
 
   require Logger
diff --git a/lib/pleroma/web/params.ex b/lib/pleroma/web/utils/params.ex
similarity index 94%
rename from lib/pleroma/web/params.ex
rename to lib/pleroma/web/utils/params.ex
index dd7059c89..6e0900341 100644
--- a/lib/pleroma/web/params.ex
+++ b/lib/pleroma/web/utils/params.ex
@@ -2,7 +2,7 @@
 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.Web.Params do
+defmodule Pleroma.Web.Utils.Params do
   # As in Mastodon API, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
   @falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"]
 

From b99f60615cd145d97f50207797ddc569e34cc3c8 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Mon, 7 Jun 2021 16:45:33 -0500
Subject: [PATCH 291/339] Fix order of Pleroma.Web.Utils.Params aliases

---
 lib/pleroma/web/common_api/utils.ex                            | 2 +-
 lib/pleroma/web/mastodon_api/controllers/account_controller.ex | 2 +-
 lib/pleroma/web/o_auth/o_auth_controller.ex                    | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 256d95b95..33639e695 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -17,8 +17,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do
   alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.CommonAPI.ActivityDraft
   alias Pleroma.Web.MediaProxy
-  alias Pleroma.Web.Utils.Params
   alias Pleroma.Web.Plugs.AuthenticationPlug
+  alias Pleroma.Web.Utils.Params
 
   require Logger
   require Pleroma.Constants
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index b4ec66367..4cc3645d4 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -24,11 +24,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   alias Pleroma.Web.MastodonAPI.MastodonAPIController
   alias Pleroma.Web.MastodonAPI.StatusView
   alias Pleroma.Web.OAuth.OAuthController
-  alias Pleroma.Web.Utils.Params
   alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
   alias Pleroma.Web.Plugs.OAuthScopesPlug
   alias Pleroma.Web.Plugs.RateLimiter
   alias Pleroma.Web.TwitterAPI.TwitterAPI
+  alias Pleroma.Web.Utils.Params
 
   plug(Pleroma.Web.ApiSpec.CastAndValidate)
 
diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex
index 06c706f8e..6951e0253 100644
--- a/lib/pleroma/web/o_auth/o_auth_controller.ex
+++ b/lib/pleroma/web/o_auth/o_auth_controller.ex
@@ -22,8 +22,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
   alias Pleroma.Web.OAuth.Token
   alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
   alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
-  alias Pleroma.Web.Utils.Params
   alias Pleroma.Web.Plugs.RateLimiter
+  alias Pleroma.Web.Utils.Params
 
   require Logger
 

From 5c27578bce7882d9ecbb1729971589d6593d9984 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Thu, 3 Jun 2021 16:58:18 -0500
Subject: [PATCH 292/339] Support metadata for video files too

---
 lib/pleroma/application_requirements.ex       |  3 +-
 lib/pleroma/upload/filter/analyze_metadata.ex | 38 +++++++++++++++++++
 2 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex
index ee6ee9516..a56311a65 100644
--- a/lib/pleroma/application_requirements.ex
+++ b/lib/pleroma/application_requirements.ex
@@ -168,7 +168,8 @@ defp check_system_commands!(:ok) do
       check_filter(Pleroma.Upload.Filter.Mogrify, "mogrify"),
       check_filter(Pleroma.Upload.Filter.Mogrifun, "mogrify"),
       check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "mogrify"),
-      check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "convert")
+      check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "convert"),
+      check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "ffprobe")
     ]
 
     preview_proxy_commands_status =
diff --git a/lib/pleroma/upload/filter/analyze_metadata.ex b/lib/pleroma/upload/filter/analyze_metadata.ex
index 8c23076d4..c89c30fc1 100644
--- a/lib/pleroma/upload/filter/analyze_metadata.ex
+++ b/lib/pleroma/upload/filter/analyze_metadata.ex
@@ -33,6 +33,23 @@ def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _} = upload)
     end
   end
 
+  def filter(%Pleroma.Upload{tempfile: file, content_type: "video" <> _} = upload) do
+    try do
+      result = media_dimensions(file)
+
+      upload =
+        upload
+        |> Map.put(:width, result.width)
+        |> Map.put(:height, result.height)
+
+      {:ok, :filtered, upload}
+    rescue
+      e in ErlangError ->
+        Logger.warn("#{__MODULE__}: #{inspect(e)}")
+        {:ok, :noop}
+    end
+  end
+
   def filter(_), do: {:ok, :noop}
 
   defp get_blurhash(file) do
@@ -42,4 +59,25 @@ defp get_blurhash(file) do
       _ -> nil
     end
   end
+
+  defp media_dimensions(file) do
+    with executable when is_binary(executable) <- System.find_executable("ffprobe"),
+         args = [
+           "-v",
+           "error",
+           "-show_entries",
+           "stream=width,height",
+           "-of",
+           "csv=p=0:s=x",
+           file
+         ],
+         {result, 0} <- System.cmd(executable, args),
+         [width, height] <-
+           String.split(String.trim(result), "x") |> Enum.map(&String.to_integer(&1)) do
+      %{width: width, height: height}
+    else
+      nil -> {:error, {:ffprobe, :command_not_found}}
+      {:error, _} = error -> error
+    end
+  end
 end

From 8443f82247d8e0a76009c9b4f337d2aec5b8aa5c Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 8 Jun 2021 12:56:03 -0500
Subject: [PATCH 293/339] Update scope of AnalyzeMetadata features

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index daa8f2ff6..dcb462f07 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,7 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 - MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded.
 - Return OAuth token `id` (primary key) in POST `/oauth/token`.
-- `AnalyzeMetadata` upload filter for extracting attachment dimensions and generating blurhashes.
+- `AnalyzeMetadata` upload filter for extracting image/video attachment dimensions and generating blurhashes for images. Blurhashes for videos are not generated at this time.
 - Attachment dimensions and blurhashes are federated when available.
 - Pinned posts federation
 

From 1c4c73c6a0c95ecb75d4048f52bedc511f0d4b66 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 8 Jun 2021 14:02:56 -0500
Subject: [PATCH 294/339] Add test for AnalyzeMetadata upload filter fetching
 dimensions from a video

---
 test/fixtures/video.mp4                        | Bin 0 -> 522216 bytes
 .../upload/filter/analyze_metadata_test.exs    |  11 +++++++++++
 2 files changed, 11 insertions(+)
 create mode 100644 test/fixtures/video.mp4

diff --git a/test/fixtures/video.mp4 b/test/fixtures/video.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..2021e3a5b29dd0520ca85df82f0f9830034d855e
GIT binary patch
literal 522216
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%0Do;<w&lO|+JEKE_=5dx`-jsdZ3
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%0D;D`{4nSUPzDCB+CU-_jllkj`~Di
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
GR|T+J!d_7T

literal 0
HcmV?d00001

diff --git a/test/pleroma/upload/filter/analyze_metadata_test.exs b/test/pleroma/upload/filter/analyze_metadata_test.exs
index 6f0e432ef..488743952 100644
--- a/test/pleroma/upload/filter/analyze_metadata_test.exs
+++ b/test/pleroma/upload/filter/analyze_metadata_test.exs
@@ -16,4 +16,15 @@ test "adds the image dimensions" do
 
     assert {:ok, :filtered, %{width: 1024, height: 768}} = AnalyzeMetadata.filter(upload)
   end
+
+  test "adds the video dimensions" do
+    upload = %Pleroma.Upload{
+      name: "coolvideo.mp4",
+      content_type: "video/mp4",
+      path: Path.absname("test/fixtures/video.mp4"),
+      tempfile: Path.absname("test/fixtures/video.mp4")
+    }
+
+    assert {:ok, :filtered, %{width: 480, height: 480}} = AnalyzeMetadata.filter(upload)
+  end
 end

From f1abe39f6f5220eae6aad84a27a917b1d9bd4439 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 8 Jun 2021 14:05:13 -0500
Subject: [PATCH 295/339] Update test names and verify blurhash is correctly
 generated for images

---
 test/pleroma/upload/filter/analyze_metadata_test.exs | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/test/pleroma/upload/filter/analyze_metadata_test.exs b/test/pleroma/upload/filter/analyze_metadata_test.exs
index 488743952..97f5fe9b2 100644
--- a/test/pleroma/upload/filter/analyze_metadata_test.exs
+++ b/test/pleroma/upload/filter/analyze_metadata_test.exs
@@ -6,7 +6,7 @@ defmodule Pleroma.Upload.Filter.AnalyzeMetadataTest do
   use Pleroma.DataCase, async: true
   alias Pleroma.Upload.Filter.AnalyzeMetadata
 
-  test "adds the image dimensions" do
+  test "adds the dimensions and blurhash for images" do
     upload = %Pleroma.Upload{
       name: "an… image.jpg",
       content_type: "image/jpeg",
@@ -14,10 +14,12 @@ test "adds the image dimensions" do
       tempfile: Path.absname("test/fixtures/image.jpg")
     }
 
-    assert {:ok, :filtered, %{width: 1024, height: 768}} = AnalyzeMetadata.filter(upload)
+    assert {:ok, :filtered,
+            %{width: 1024, height: 768, blurhash: "V5DI,j_NIS%eI.RDI[RS%1WDr=IVD-RoV{?Ge-tiSKkR"}} =
+             AnalyzeMetadata.filter(upload)
   end
 
-  test "adds the video dimensions" do
+  test "adds the dimensions for videos" do
     upload = %Pleroma.Upload{
       name: "coolvideo.mp4",
       content_type: "video/mp4",

From 3121ed1325cceb8ec3f8d153d3c6fa18b2951714 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 8 Jun 2021 14:49:57 -0500
Subject: [PATCH 296/339] Blurhash varies slightly by computer generating it,
 so just validate it wasn't nil

---
 test/pleroma/upload/filter/analyze_metadata_test.exs | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/test/pleroma/upload/filter/analyze_metadata_test.exs b/test/pleroma/upload/filter/analyze_metadata_test.exs
index 97f5fe9b2..4b636a684 100644
--- a/test/pleroma/upload/filter/analyze_metadata_test.exs
+++ b/test/pleroma/upload/filter/analyze_metadata_test.exs
@@ -14,9 +14,10 @@ test "adds the dimensions and blurhash for images" do
       tempfile: Path.absname("test/fixtures/image.jpg")
     }
 
-    assert {:ok, :filtered,
-            %{width: 1024, height: 768, blurhash: "V5DI,j_NIS%eI.RDI[RS%1WDr=IVD-RoV{?Ge-tiSKkR"}} =
-             AnalyzeMetadata.filter(upload)
+    {:ok, :filtered, meta} = AnalyzeMetadata.filter(upload)
+
+    assert %{width: 1024, height: 768} = meta
+    assert meta.blurhash
   end
 
   test "adds the dimensions for videos" do

From 5de65ce3e89ba2f229170ed18933c99e5caa8dff Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 8 Jun 2021 15:59:55 -0500
Subject: [PATCH 297/339] Set the correct height/width if the data is available
 when generating twittercard metadata

---
 lib/pleroma/web/metadata/providers/twitter_card.ex    |  7 +++++--
 .../web/metadata/providers/twitter_card_test.exs      | 11 ++++++++---
 2 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex
index 12c372d77..e28f832d4 100644
--- a/lib/pleroma/web/metadata/providers/twitter_card.ex
+++ b/lib/pleroma/web/metadata/providers/twitter_card.ex
@@ -80,11 +80,14 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
             # TODO: Need the true width and height values here or Twitter renders an iFrame with
             # a bad aspect ratio
             "video" ->
+              height = url["height"] || 480
+              width = url["width"] || 480
+
               [
                 {:meta, [property: "twitter:card", content: "player"], []},
                 {:meta, [property: "twitter:player", content: player_url(id)], []},
-                {:meta, [property: "twitter:player:width", content: "480"], []},
-                {:meta, [property: "twitter:player:height", content: "480"], []},
+                {:meta, [property: "twitter:player:width", content: "#{width}"], []},
+                {:meta, [property: "twitter:player:height", content: "#{height}"], []},
                 {:meta, [property: "twitter:player:stream", content: url["href"]], []},
                 {:meta,
                  [property: "twitter:player:stream:content_type", content: url["mediaType"]], []}
diff --git a/test/pleroma/web/metadata/providers/twitter_card_test.exs b/test/pleroma/web/metadata/providers/twitter_card_test.exs
index 196bca20a..6d761f4e4 100644
--- a/test/pleroma/web/metadata/providers/twitter_card_test.exs
+++ b/test/pleroma/web/metadata/providers/twitter_card_test.exs
@@ -123,7 +123,12 @@ test "it renders supported types of attachments and skips unknown types" do
             },
             %{
               "url" => [
-                %{"mediaType" => "video/webm", "href" => "https://pleroma.gov/about/juche.webm"}
+                %{
+                  "mediaType" => "video/webm",
+                  "href" => "https://pleroma.gov/about/juche.webm",
+                  "height" => 600,
+                  "width" => 800
+                }
               ]
             }
           ]
@@ -143,8 +148,8 @@ test "it renders supported types of attachments and skips unknown types" do
                 property: "twitter:player",
                 content: Router.Helpers.o_status_url(Endpoint, :notice_player, activity.id)
               ], []},
-             {:meta, [property: "twitter:player:width", content: "480"], []},
-             {:meta, [property: "twitter:player:height", content: "480"], []},
+             {:meta, [property: "twitter:player:width", content: "800"], []},
+             {:meta, [property: "twitter:player:height", content: "600"], []},
              {:meta,
               [
                 property: "twitter:player:stream",

From 1be14cc45f915824e5796396fcdb7cb402ffe138 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 8 Jun 2021 16:07:51 -0500
Subject: [PATCH 298/339] Ignore runtime deps in Pleroma.Config.Loader with
 Module.concat/1 Speeds up recompilation

---
 lib/pleroma/config/loader.ex | 30 +++++++++++++++---------------
 1 file changed, 15 insertions(+), 15 deletions(-)

diff --git a/lib/pleroma/config/loader.ex b/lib/pleroma/config/loader.ex
index 9489f58c4..2a945999e 100644
--- a/lib/pleroma/config/loader.ex
+++ b/lib/pleroma/config/loader.ex
@@ -3,21 +3,21 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Config.Loader do
-  defp reject_keys,
-    do: [
-      Pleroma.Repo,
-      Pleroma.Web.Endpoint,
-      :env,
-      :configurable_from_database,
-      :database,
-      :swarm
-    ]
+  # These modules are only being used as keys here (for equality check),
+  # so it's okay to use `Module.concat/1` to have the compiler ignore them.
+  @reject_keys [
+    Module.concat(["Pleroma.Repo"]),
+    Module.concat(["Pleroma.Web.Endpoint"]),
+    :env,
+    :configurable_from_database,
+    :database,
+    :swarm
+  ]
 
-  defp reject_groups,
-    do: [
-      :postgrex,
-      :tesla
-    ]
+  @reject_groups [
+    :postgrex,
+    :tesla
+  ]
 
   if Code.ensure_loaded?(Config.Reader) do
     @reader Config.Reader
@@ -54,7 +54,7 @@ defp filter(configs) do
   @spec filter_group(atom(), keyword()) :: keyword()
   def filter_group(group, configs) do
     Enum.reject(configs[group], fn {key, _v} ->
-      key in reject_keys() or group in reject_groups() or
+      key in @reject_keys or group in @reject_groups or
         (group == :phoenix and key == :serve_endpoints)
     end)
   end

From d4ac9445cd485a4055f93360714923c3f64d9673 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 8 Jun 2021 16:19:12 -0500
Subject: [PATCH 299/339] Twittercard metadata for images should also include
 dimensions if available

---
 lib/pleroma/web/metadata/providers/twitter_card.ex  | 13 ++++++-------
 .../web/metadata/providers/twitter_card_test.exs    | 11 ++++++++++-
 2 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex
index e28f832d4..bf6d4bcbe 100644
--- a/lib/pleroma/web/metadata/providers/twitter_card.ex
+++ b/lib/pleroma/web/metadata/providers/twitter_card.ex
@@ -55,7 +55,9 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
     Enum.reduce(attachments, [], fn attachment, acc ->
       rendered_tags =
         Enum.reduce(attachment["url"], [], fn url, acc ->
-          # TODO: Add additional properties to objects when we have the data available.
+          height = url["height"] || 480
+          width = url["width"] || 480
+
           case Utils.fetch_media_type(@media_types, url["mediaType"]) do
             "audio" ->
               [
@@ -73,16 +75,13 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
                  [
                    property: "twitter:player",
                    content: Utils.attachment_url(url["href"])
-                 ], []}
+                 ], []},
+                {:meta, [property: "twitter:player:width", content: "#{width}"], []},
+                {:meta, [property: "twitter:player:height", content: "#{height}"], []}
                 | acc
               ]
 
-            # TODO: Need the true width and height values here or Twitter renders an iFrame with
-            # a bad aspect ratio
             "video" ->
-              height = url["height"] || 480
-              width = url["width"] || 480
-
               [
                 {:meta, [property: "twitter:card", content: "player"], []},
                 {:meta, [property: "twitter:player", content: player_url(id)], []},
diff --git a/test/pleroma/web/metadata/providers/twitter_card_test.exs b/test/pleroma/web/metadata/providers/twitter_card_test.exs
index 6d761f4e4..dbb15b79f 100644
--- a/test/pleroma/web/metadata/providers/twitter_card_test.exs
+++ b/test/pleroma/web/metadata/providers/twitter_card_test.exs
@@ -111,7 +111,14 @@ test "it renders supported types of attachments and skips unknown types" do
           "content" => "pleroma in a nutshell",
           "attachment" => [
             %{
-              "url" => [%{"mediaType" => "image/png", "href" => "https://pleroma.gov/tenshi.png"}]
+              "url" => [
+                %{
+                  "mediaType" => "image/png",
+                  "href" => "https://pleroma.gov/tenshi.png",
+                  "height" => 1024,
+                  "width" => 1280
+                }
+              ]
             },
             %{
               "url" => [
@@ -142,6 +149,8 @@ test "it renders supported types of attachments and skips unknown types" do
              {:meta, [property: "twitter:description", content: "pleroma in a nutshell"], []},
              {:meta, [property: "twitter:card", content: "summary_large_image"], []},
              {:meta, [property: "twitter:player", content: "https://pleroma.gov/tenshi.png"], []},
+             {:meta, [property: "twitter:player:width", content: "1280"], []},
+             {:meta, [property: "twitter:player:height", content: "1024"], []},
              {:meta, [property: "twitter:card", content: "player"], []},
              {:meta,
               [

From aa8cc4e86e5c7a53fa8bc606dbce6c6b3a0a8c02 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 8 Jun 2021 16:31:12 -0500
Subject: [PATCH 300/339] Only use fallback for videos and only add this
 metadata for images if we really have it.

---
 .../web/metadata/providers/twitter_card.ex    | 28 +++++++++++++++----
 1 file changed, 22 insertions(+), 6 deletions(-)

diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex
index bf6d4bcbe..dfe477a8a 100644
--- a/lib/pleroma/web/metadata/providers/twitter_card.ex
+++ b/lib/pleroma/web/metadata/providers/twitter_card.ex
@@ -55,9 +55,6 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
     Enum.reduce(attachments, [], fn attachment, acc ->
       rendered_tags =
         Enum.reduce(attachment["url"], [], fn url, acc ->
-          height = url["height"] || 480
-          width = url["width"] || 480
-
           case Utils.fetch_media_type(@media_types, url["mediaType"]) do
             "audio" ->
               [
@@ -75,13 +72,16 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
                  [
                    property: "twitter:player",
                    content: Utils.attachment_url(url["href"])
-                 ], []},
-                {:meta, [property: "twitter:player:width", content: "#{width}"], []},
-                {:meta, [property: "twitter:player:height", content: "#{height}"], []}
+                 ], []}
                 | acc
               ]
+              |> maybe_add_dimensions(url)
 
             "video" ->
+              # fallback to old placeholder values
+              height = url["height"] || 480
+              width = url["width"] || 480
+
               [
                 {:meta, [property: "twitter:card", content: "player"], []},
                 {:meta, [property: "twitter:player", content: player_url(id)], []},
@@ -107,4 +107,20 @@ defp build_attachments(_id, _object), do: []
   defp player_url(id) do
     Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice_player, id)
   end
+
+  # Videos have problems without dimensions, but we used to not provide WxH for images.
+  # A default (read: incorrect) fallback for images is likely to cause rendering bugs.
+  defp maybe_add_dimensions(metadata, url) do
+    cond do
+      !is_nil(url["height"]) && !is_nil(url["width"]) ->
+        metadata ++
+          [
+            {:meta, [property: "twitter:player:width", content: "#{url["width"]}"], []},
+            {:meta, [property: "twitter:player:height", content: "#{url["height"]}"], []}
+          ]
+
+      true ->
+        metadata
+    end
+  end
 end

From 4faeec2c449c73563635424b6a7d597b9222bfe2 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 8 Jun 2021 15:58:19 -0500
Subject: [PATCH 301/339] Create AdminAPI.UserView to avoid compile-time dep
 Speeds up recompilation

---
 .../web/admin_api/controllers/user_controller.ex       |  2 --
 lib/pleroma/web/admin_api/views/user_view.ex           | 10 ++++++++++
 2 files changed, 10 insertions(+), 2 deletions(-)
 create mode 100644 lib/pleroma/web/admin_api/views/user_view.ex

diff --git a/lib/pleroma/web/admin_api/controllers/user_controller.ex b/lib/pleroma/web/admin_api/controllers/user_controller.ex
index d3e4c18a3..637a0e702 100644
--- a/lib/pleroma/web/admin_api/controllers/user_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/user_controller.ex
@@ -45,8 +45,6 @@ defmodule Pleroma.Web.AdminAPI.UserController do
     when action in [:follow, :unfollow]
   )
 
-  plug(:put_view, Pleroma.Web.AdminAPI.AccountView)
-
   action_fallback(AdminAPI.FallbackController)
 
   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.UserOperation
diff --git a/lib/pleroma/web/admin_api/views/user_view.ex b/lib/pleroma/web/admin_api/views/user_view.ex
new file mode 100644
index 000000000..e91265ffe
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/user_view.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.UserView do
+  use Pleroma.Web, :view
+  alias Pleroma.Web.AdminAPI
+
+  def render(view, opts), do: AdminAPI.AccountView.render(view, opts)
+end

From d70db63084449e48e90288bc7484733171246625 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 8 Jun 2021 16:58:33 -0500
Subject: [PATCH 302/339] Set the correct height/width if the data is available
 when generating opengraph metadata

---
 .../web/metadata/providers/open_graph.ex      | 22 +++++++++++++++++--
 .../metadata/providers/open_graph_test.exs    | 20 ++++++++++++++---
 2 files changed, 37 insertions(+), 5 deletions(-)

diff --git a/lib/pleroma/web/metadata/providers/open_graph.ex b/lib/pleroma/web/metadata/providers/open_graph.ex
index 18ddde84b..78cef1525 100644
--- a/lib/pleroma/web/metadata/providers/open_graph.ex
+++ b/lib/pleroma/web/metadata/providers/open_graph.ex
@@ -69,8 +69,7 @@ defp build_attachments(%{data: %{"attachment" => attachments}}) do
     Enum.reduce(attachments, [], fn attachment, acc ->
       rendered_tags =
         Enum.reduce(attachment["url"], [], fn url, acc ->
-          # TODO: Add additional properties to objects when we have the data available.
-          # Also, Whatsapp only wants JPEG or PNGs. It seems that if we add a second og:image
+          # TODO: Whatsapp only wants JPEG or PNGs. It seems that if we add a second og:image
           # object when a Video or GIF is attached it will display that in Whatsapp Rich Preview.
           case Utils.fetch_media_type(@media_types, url["mediaType"]) do
             "audio" ->
@@ -85,12 +84,14 @@ defp build_attachments(%{data: %{"attachment" => attachments}}) do
                 {:meta, [property: "og:image:alt", content: attachment["name"]], []}
                 | acc
               ]
+              |> maybe_add_dimensions(url)
 
             "video" ->
               [
                 {:meta, [property: "og:video", content: Utils.attachment_url(url["href"])], []}
                 | acc
               ]
+              |> maybe_add_dimensions(url)
 
             _ ->
               acc
@@ -102,4 +103,21 @@ defp build_attachments(%{data: %{"attachment" => attachments}}) do
   end
 
   defp build_attachments(_), do: []
+
+  # We can use url["mediaType"] to dynamically fill the metadata
+  defp maybe_add_dimensions(metadata, url) do
+    type = url["mediaType"] |> String.split("/") |> List.first()
+
+    cond do
+      !is_nil(url["height"]) && !is_nil(url["width"]) ->
+        metadata ++
+          [
+            {:meta, [property: "og:#{type}:width", content: "#{url["width"]}"], []},
+            {:meta, [property: "og:#{type}:height", content: "#{url["height"]}"], []}
+          ]
+
+      true ->
+        metadata
+    end
+  end
 end
diff --git a/test/pleroma/web/metadata/providers/open_graph_test.exs b/test/pleroma/web/metadata/providers/open_graph_test.exs
index fc44b3cbd..f5f71cee5 100644
--- a/test/pleroma/web/metadata/providers/open_graph_test.exs
+++ b/test/pleroma/web/metadata/providers/open_graph_test.exs
@@ -22,7 +22,12 @@ test "it renders all supported types of attachments and skips unknown types" do
           "attachment" => [
             %{
               "url" => [
-                %{"mediaType" => "image/png", "href" => "https://pleroma.gov/tenshi.png"}
+                %{
+                  "mediaType" => "image/png",
+                  "href" => "https://pleroma.gov/tenshi.png",
+                  "height" => 1024,
+                  "width" => 1280
+                }
               ]
             },
             %{
@@ -35,7 +40,12 @@ test "it renders all supported types of attachments and skips unknown types" do
             },
             %{
               "url" => [
-                %{"mediaType" => "video/webm", "href" => "https://pleroma.gov/about/juche.webm"}
+                %{
+                  "mediaType" => "video/webm",
+                  "href" => "https://pleroma.gov/about/juche.webm",
+                  "height" => 600,
+                  "width" => 800
+                }
               ]
             },
             %{
@@ -55,11 +65,15 @@ test "it renders all supported types of attachments and skips unknown types" do
     assert Enum.all?(
              [
                {:meta, [property: "og:image", content: "https://pleroma.gov/tenshi.png"], []},
+               {:meta, [property: "og:image:width", content: "1280"], []},
+               {:meta, [property: "og:image:height", content: "1024"], []},
                {:meta,
                 [property: "og:audio", content: "http://www.gnu.org/music/free-software-song.au"],
                 []},
                {:meta, [property: "og:video", content: "https://pleroma.gov/about/juche.webm"],
-                []}
+                []},
+               {:meta, [property: "og:video:width", content: "800"], []},
+               {:meta, [property: "og:video:height", content: "600"], []}
              ],
              fn element -> element in result end
            )

From 9cb8960284c7046d4a058c0526b55bce5ef9513b Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Tue, 8 Jun 2021 17:14:30 -0500
Subject: [PATCH 303/339] Switch OGP default type from "website" to "article"

This is what Mastodon uses and might fix some link preview bugs I've encountered
---
 lib/pleroma/web/metadata/providers/open_graph.ex | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/pleroma/web/metadata/providers/open_graph.ex b/lib/pleroma/web/metadata/providers/open_graph.ex
index 78cef1525..e5712ec63 100644
--- a/lib/pleroma/web/metadata/providers/open_graph.ex
+++ b/lib/pleroma/web/metadata/providers/open_graph.ex
@@ -32,7 +32,7 @@ def build_tags(%{
          property: "og:description",
          content: scrubbed_content
        ], []},
-      {:meta, [property: "og:type", content: "website"], []}
+      {:meta, [property: "og:type", content: "article"], []}
     ] ++
       if attachments == [] or Metadata.activity_nsfw?(object) do
         [
@@ -57,7 +57,7 @@ def build_tags(%{user: user}) do
          ], []},
         {:meta, [property: "og:url", content: user.uri || user.ap_id], []},
         {:meta, [property: "og:description", content: truncated_bio], []},
-        {:meta, [property: "og:type", content: "website"], []},
+        {:meta, [property: "og:type", content: "article"], []},
         {:meta, [property: "og:image", content: Utils.attachment_url(User.avatar_url(user))], []},
         {:meta, [property: "og:image:width", content: 150], []},
         {:meta, [property: "og:image:height", content: 150], []}

From 45ab24f2d9b289498c3b009d9509ee7aec818eba Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 8 Jun 2021 18:03:21 -0500
Subject: [PATCH 304/339] Switch to runtime deps in Pleroma.Instances Speeds up
 recompilation by limiting compile cycles

---
 lib/pleroma/instances.ex | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/lib/pleroma/instances.ex b/lib/pleroma/instances.ex
index 80addcc52..6b57e56da 100644
--- a/lib/pleroma/instances.ex
+++ b/lib/pleroma/instances.ex
@@ -5,13 +5,18 @@
 defmodule Pleroma.Instances do
   @moduledoc "Instances context."
 
-  @adapter Pleroma.Instances.Instance
+  alias Pleroma.Instances.Instance
 
-  defdelegate filter_reachable(urls_or_hosts), to: @adapter
-  defdelegate reachable?(url_or_host), to: @adapter
-  defdelegate set_reachable(url_or_host), to: @adapter
-  defdelegate set_unreachable(url_or_host, unreachable_since \\ nil), to: @adapter
-  defdelegate get_consistently_unreachable(), to: @adapter
+  def filter_reachable(urls_or_hosts), do: Instance.filter_reachable(urls_or_hosts)
+
+  def reachable?(url_or_host), do: Instance.reachable?(url_or_host)
+
+  def set_reachable(url_or_host), do: Instance.set_reachable(url_or_host)
+
+  def set_unreachable(url_or_host, unreachable_since \\ nil),
+    do: Instance.set_unreachable(url_or_host, unreachable_since)
+
+  def get_consistently_unreachable, do: Instance.get_consistently_unreachable()
 
   def set_consistently_unreachable(url_or_host),
     do: set_unreachable(url_or_host, reachability_datetime_threshold())

From 67ec0e6c18ec6c84fc3aefe9ab883e8f0b01792f Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 8 Jun 2021 18:18:25 -0500
Subject: [PATCH 305/339] Switch to runtime deps in ActivityPub.SideEffects
 Speeds up recompilation by reducing compile cycles

---
 lib/pleroma/web/activity_pub/side_effects.ex | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 674356d9a..1eca1cb31 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -28,11 +28,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
   require Logger
 
   @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
-  @ap_streamer Pleroma.Config.get([:side_effects, :ap_streamer], ActivityPub)
   @logger Pleroma.Config.get([:side_effects, :logger], Logger)
 
   @behaviour Pleroma.Web.ActivityPub.SideEffects.Handling
 
+  defp ap_streamer, do: Pleroma.Config.get([:side_effects, :ap_streamer], ActivityPub)
+
   @impl true
   def handle(object, meta \\ [])
 
@@ -302,8 +303,8 @@ def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object,
 
             MessageReference.delete_for_object(deleted_object)
 
-            @ap_streamer.stream_out(object)
-            @ap_streamer.stream_out_participations(deleted_object, user)
+            ap_streamer().stream_out(object)
+            ap_streamer().stream_out_participations(deleted_object, user)
             :ok
           else
             {:actor, _} ->

From 45b7325b9ef8110b424df3541b321c9a220f886c Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 8 Jun 2021 19:14:12 -0500
Subject: [PATCH 306/339] Refactor skipped plugs into Pleroma.Web functions
 Speeds up recompilation by reducing compile cycles

---
 lib/pleroma/web.ex                                        | 8 ++++++++
 lib/pleroma/web/masto_fe_controller.ex                    | 8 ++------
 .../web/mastodon_api/controllers/account_controller.ex    | 5 ++---
 .../web/mastodon_api/controllers/app_controller.ex        | 8 +-------
 .../mastodon_api/controllers/custom_emoji_controller.ex   | 6 +-----
 .../web/mastodon_api/controllers/instance_controller.ex   | 6 +-----
 .../mastodon_api/controllers/mastodon_api_controller.ex   | 6 +-----
 .../web/mastodon_api/controllers/status_controller.ex     | 5 +----
 .../web/mastodon_api/controllers/timeline_controller.ex   | 3 +--
 lib/pleroma/web/o_auth/o_auth_controller.ex               | 5 +----
 .../web/pleroma_api/controllers/account_controller.ex     | 6 +-----
 .../web/pleroma_api/controllers/emoji_pack_controller.ex  | 6 +-----
 lib/pleroma/web/twitter_api/controller.ex                 | 7 +------
 13 files changed, 22 insertions(+), 57 deletions(-)

diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex
index d26931af9..5761e3b38 100644
--- a/lib/pleroma/web.ex
+++ b/lib/pleroma/web.ex
@@ -62,6 +62,14 @@ defp skip_plug(conn, plug_modules) do
         )
       end
 
+      defp skip_auth(conn, _) do
+        skip_plug(conn, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug])
+      end
+
+      defp skip_public_check(conn, _) do
+        skip_plug(conn, EnsurePublicOrAuthenticatedPlug)
+      end
+
       # Executed just before actual controller action, invokes before-action hooks (callbacks)
       defp action(conn, params) do
         with %{halted: false} = conn <-
diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex
index e788ab37a..d2460f51d 100644
--- a/lib/pleroma/web/masto_fe_controller.ex
+++ b/lib/pleroma/web/masto_fe_controller.ex
@@ -8,13 +8,12 @@ defmodule Pleroma.Web.MastoFEController do
   alias Pleroma.User
   alias Pleroma.Web.MastodonAPI.AuthController
   alias Pleroma.Web.OAuth.Token
-  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
   alias Pleroma.Web.Plugs.OAuthScopesPlug
 
   plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :put_settings)
 
   # Note: :index action handles attempt of unauthenticated access to private instance with redirect
-  plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action == :index)
+  plug(:skip_public_check when action == :index)
 
   plug(
     OAuthScopesPlug,
@@ -22,10 +21,7 @@ defmodule Pleroma.Web.MastoFEController do
     when action == :index
   )
 
-  plug(
-    :skip_plug,
-    [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :manifest
-  )
+  plug(:skip_auth when action == :manifest)
 
   @doc "GET /web/*path"
   def index(conn, _params) do
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 4cc3645d4..5fcbffc34 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -24,7 +24,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   alias Pleroma.Web.MastodonAPI.MastodonAPIController
   alias Pleroma.Web.MastodonAPI.StatusView
   alias Pleroma.Web.OAuth.OAuthController
-  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
   alias Pleroma.Web.Plugs.OAuthScopesPlug
   alias Pleroma.Web.Plugs.RateLimiter
   alias Pleroma.Web.TwitterAPI.TwitterAPI
@@ -32,9 +31,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   plug(Pleroma.Web.ApiSpec.CastAndValidate)
 
-  plug(:skip_plug, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :create)
+  plug(:skip_auth when action == :create)
 
-  plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:show, :statuses])
+  plug(:skip_public_check when action in [:show, :statuses])
 
   plug(
     OAuthScopesPlug,
diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
index dd3b39c77..a95cc52fd 100644
--- a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
@@ -14,16 +14,10 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
   alias Pleroma.Web.OAuth.App
   alias Pleroma.Web.OAuth.Scopes
   alias Pleroma.Web.OAuth.Token
-  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
-  alias Pleroma.Web.Plugs.OAuthScopesPlug
 
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
 
-  plug(
-    :skip_plug,
-    [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug]
-    when action in [:create, :verify_credentials]
-  )
+  plug(:skip_auth when action in [:create, :verify_credentials])
 
   plug(Pleroma.Web.ApiSpec.CastAndValidate)
 
diff --git a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex
index d7e18dc92..31b647755 100644
--- a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex
@@ -7,11 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do
 
   plug(Pleroma.Web.ApiSpec.CastAndValidate)
 
-  plug(
-    :skip_plug,
-    [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug]
-    when action == :index
-  )
+  plug(:skip_auth when action == :index)
 
   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.CustomEmojiOperation
 
diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex
index c7a5267d4..5376e4594 100644
--- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex
@@ -7,11 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do
 
   plug(Pleroma.Web.ApiSpec.CastAndValidate)
 
-  plug(
-    :skip_plug,
-    [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug]
-    when action in [:show, :peers]
-  )
+  plug(:skip_auth when action in [:show, :peers])
 
   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.InstanceOperation
 
diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
index a1bcc91d9..a0f79f377 100644
--- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
@@ -15,11 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
 
   require Logger
 
-  plug(
-    :skip_plug,
-    [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug]
-    when action in [:empty_array, :empty_object]
-  )
+  plug(:skip_auth when action in [:empty_array, :empty_object])
 
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
 
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index 724dc5c5d..2eff4d9d0 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -27,10 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
 
   plug(Pleroma.Web.ApiSpec.CastAndValidate)
 
-  plug(
-    :skip_plug,
-    Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show]
-  )
+  plug(:skip_public_check when action in [:index, :show])
 
   @unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []}
 
diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
index 845f546d4..4b49b74ca 100644
--- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
@@ -12,12 +12,11 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
   alias Pleroma.Pagination
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
-  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
   alias Pleroma.Web.Plugs.OAuthScopesPlug
   alias Pleroma.Web.Plugs.RateLimiter
 
   plug(Pleroma.Web.ApiSpec.CastAndValidate)
-  plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:public, :hashtag])
+  plug(:skip_public_check when action in [:public, :hashtag])
 
   # TODO: Replace with a macro when there is a Phoenix release with the following commit in it:
   # https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e
diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex
index 6951e0253..247d8399c 100644
--- a/lib/pleroma/web/o_auth/o_auth_controller.ex
+++ b/lib/pleroma/web/o_auth/o_auth_controller.ex
@@ -32,10 +32,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
   plug(:fetch_session)
   plug(:fetch_flash)
 
-  plug(:skip_plug, [
-    Pleroma.Web.Plugs.OAuthScopesPlug,
-    Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
-  ])
+  plug(:skip_auth)
 
   plug(RateLimiter, [name: :authentication] when action == :create_authorization)
 
diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
index 6e01c5497..8e4d3e7f7 100644
--- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
@@ -11,7 +11,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.MastodonAPI.StatusView
-  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
   alias Pleroma.Web.Plugs.OAuthScopesPlug
   alias Pleroma.Web.Plugs.RateLimiter
 
@@ -29,10 +28,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
 
   plug(Pleroma.Web.ApiSpec.CastAndValidate)
 
-  plug(
-    :skip_plug,
-    [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirmation_resend
-  )
+  plug(:skip_auth when action == :confirmation_resend)
 
   plug(
     OAuthScopesPlug,
diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex
index d0f677d3c..1ea44f347 100644
--- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex
@@ -22,11 +22,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
          ]
   )
 
-  @skip_plugs [
-    Pleroma.Web.Plugs.OAuthScopesPlug,
-    Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
-  ]
-  plug(:skip_plug, @skip_plugs when action in [:index, :archive, :show])
+  plug(:skip_auth when action in [:index, :archive, :show])
 
   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation
 
diff --git a/lib/pleroma/web/twitter_api/controller.ex b/lib/pleroma/web/twitter_api/controller.ex
index e32713311..1e78ff2c1 100644
--- a/lib/pleroma/web/twitter_api/controller.ex
+++ b/lib/pleroma/web/twitter_api/controller.ex
@@ -7,17 +7,12 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
 
   alias Pleroma.User
   alias Pleroma.Web.OAuth.Token
-  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
   alias Pleroma.Web.Plugs.OAuthScopesPlug
   alias Pleroma.Web.TwitterAPI.TokenView
 
   require Logger
 
-  plug(
-    :skip_plug,
-    [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirm_email
-  )
-
+  plug(:skip_auth when action == :confirm_email)
   plug(:skip_plug, OAuthScopesPlug when action in [:oauth_tokens, :revoke_token])
 
   action_fallback(:errors)

From c839078a7517f6c3119cffa4eed953ea0c9334d2 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Wed, 9 Jun 2021 03:43:01 +0200
Subject: [PATCH 307/339] ObjectValidators.{Announce,EmojiReact,Like}: Fix
 context, actor & addressing

---
 .../object_validators/announce_validator.ex   | 27 +++++++---
 .../object_validators/common_fixes.ex         | 20 +++++++-
 .../emoji_react_validator.ex                  | 31 ++++++------
 .../object_validators/like_validator.ex       | 50 +++++++------------
 .../announce_validation_test.exs              | 22 ++++----
 .../like_validation_test.exs                  | 35 +++++++------
 test/pleroma/web/activity_pub/relay_test.exs  |  2 +-
 .../transmogrifier/announce_handling_test.exs | 23 ---------
 8 files changed, 105 insertions(+), 105 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
index a2f752ac3..4db76f387 100644
--- a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
   alias Pleroma.Object
   alias Pleroma.User
+  alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.ActivityPub.Visibility
 
@@ -23,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
     field(:type, :string)
     field(:object, ObjectValidators.ObjectID)
     field(:actor, ObjectValidators.ObjectID)
-    field(:context, :string, autogenerate: {Utils, :generate_context_id, []})
+    field(:context, :string)
     field(:to, ObjectValidators.Recipients, default: [])
     field(:cc, ObjectValidators.Recipients, default: [])
     field(:published, ObjectValidators.DateTime)
@@ -36,6 +37,10 @@ def cast_and_validate(data) do
   end
 
   def cast_data(data) do
+    data =
+      data
+      |> fix()
+
     %__MODULE__{}
     |> changeset(data)
   end
@@ -43,11 +48,21 @@ def cast_data(data) do
   def changeset(struct, data) do
     struct
     |> cast(data, __schema__(:fields))
-    |> fix_after_cast()
   end
 
-  def fix_after_cast(cng) do
-    cng
+  defp fix(data) do
+    data =
+      data
+      |> CommonFixes.fix_actor()
+      |> CommonFixes.fix_activity_addressing()
+
+    with %Object{} = object <- Object.normalize(data["object"]) do
+      data
+      |> CommonFixes.fix_activity_context(object)
+      |> CommonFixes.fix_object_action_recipients(object)
+    else
+      _ -> data
+    end
   end
 
   defp validate_data(data_cng) do
@@ -60,7 +75,7 @@ defp validate_data(data_cng) do
     |> validate_announcable()
   end
 
-  def validate_announcable(cng) do
+  defp validate_announcable(cng) do
     with actor when is_binary(actor) <- get_field(cng, :actor),
          object when is_binary(object) <- get_field(cng, :object),
          %User{} = actor <- User.get_cached_by_ap_id(actor),
@@ -91,7 +106,7 @@ def validate_announcable(cng) do
     end
   end
 
-  def validate_existing_announce(cng) do
+  defp validate_existing_announce(cng) do
     actor = get_field(cng, :actor)
     object = get_field(cng, :object)
 
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
index c958fcc5d..9631013a7 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
@@ -4,6 +4,7 @@
 
 defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
+  alias Pleroma.Object
   alias Pleroma.Object.Containment
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.Transmogrifier
@@ -36,7 +37,7 @@ def fix_object_defaults(data) do
     |> Transmogrifier.fix_implicit_addressing(follower_collection)
   end
 
-  def fix_activity_addressing(activity, _meta) do
+  def fix_activity_addressing(activity) do
     %User{follower_address: follower_collection} = User.get_cached_by_ap_id(activity["actor"])
 
     activity
@@ -57,4 +58,21 @@ def fix_actor(data) do
     |> Map.put("actor", actor)
     |> Map.put("attributedTo", actor)
   end
+
+  def fix_activity_context(data, %Object{data: %{"context" => object_context}}) do
+    data
+    |> Map.put("context", object_context)
+  end
+
+  def fix_object_action_recipients(%{"actor" => actor} = data, %Object{data: %{"actor" => actor}}) do
+    to = ((data["to"] || []) -- [actor]) |> Enum.uniq()
+
+    Map.put(data, "to", to)
+  end
+
+  def fix_object_action_recipients(data, %Object{data: %{"actor" => actor}}) do
+    to = ((data["to"] || []) ++ [actor]) |> Enum.uniq()
+
+    Map.put(data, "to", to)
+  end
 end
diff --git a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
index ec7566515..a18bd7540 100644
--- a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
 
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
   alias Pleroma.Object
+  alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
 
   import Ecto.Changeset
   import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -31,6 +32,10 @@ def cast_and_validate(data) do
   end
 
   def cast_data(data) do
+    data =
+      data
+      |> fix()
+
     %__MODULE__{}
     |> changeset(data)
   end
@@ -38,28 +43,24 @@ def cast_data(data) do
   def changeset(struct, data) do
     struct
     |> cast(data, __schema__(:fields))
-    |> fix_after_cast()
   end
 
-  def fix_after_cast(cng) do
-    cng
-    |> fix_context()
-  end
+  defp fix(data) do
+    data =
+      data
+      |> CommonFixes.fix_actor()
+      |> CommonFixes.fix_activity_addressing()
 
-  def fix_context(cng) do
-    object = get_field(cng, :object)
-
-    with nil <- get_field(cng, :context),
-         %Object{data: %{"context" => context}} <- Object.get_cached_by_ap_id(object) do
-      cng
-      |> put_change(:context, context)
+    with %Object{} = object <- Object.normalize(data["object"]) do
+      data
+      |> CommonFixes.fix_activity_context(object)
+      |> CommonFixes.fix_object_action_recipients(object)
     else
-      _ ->
-        cng
+      _ -> data
     end
   end
 
-  def validate_emoji(cng) do
+  defp validate_emoji(cng) do
     content = get_field(cng, :content)
 
     if Pleroma.Emoji.is_unicode_emoji?(content) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
index 509da507b..8b99c89b9 100644
--- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
 
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
   alias Pleroma.Object
+  alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
   alias Pleroma.Web.ActivityPub.Utils
 
   import Ecto.Changeset
@@ -31,6 +32,10 @@ def cast_and_validate(data) do
   end
 
   def cast_data(data) do
+    data =
+      data
+      |> fix()
+
     %__MODULE__{}
     |> changeset(data)
   end
@@ -38,41 +43,20 @@ def cast_data(data) do
   def changeset(struct, data) do
     struct
     |> cast(data, __schema__(:fields))
-    |> fix_after_cast()
   end
 
-  def fix_after_cast(cng) do
-    cng
-    |> fix_recipients()
-    |> fix_context()
-  end
+  defp fix(data) do
+    data =
+      data
+      |> CommonFixes.fix_actor()
+      |> CommonFixes.fix_activity_addressing()
 
-  def fix_context(cng) do
-    object = get_field(cng, :object)
-
-    with nil <- get_field(cng, :context),
-         %Object{data: %{"context" => context}} <- Object.get_cached_by_ap_id(object) do
-      cng
-      |> put_change(:context, context)
+    with %Object{} = object <- Object.normalize(data["object"]) do
+      data
+      |> CommonFixes.fix_activity_context(object)
+      |> CommonFixes.fix_object_action_recipients(object)
     else
-      _ ->
-        cng
-    end
-  end
-
-  def fix_recipients(cng) do
-    to = get_field(cng, :to)
-    cc = get_field(cng, :cc)
-    object = get_field(cng, :object)
-
-    with {[], []} <- {to, cc},
-         %Object{data: %{"actor" => actor}} <- Object.get_cached_by_ap_id(object),
-         {:ok, actor} <- ObjectValidators.ObjectID.cast(actor) do
-      cng
-      |> put_change(:to, [actor])
-    else
-      _ ->
-        cng
+      _ -> data
     end
   end
 
@@ -85,7 +69,7 @@ defp validate_data(data_cng) do
     |> validate_existing_like()
   end
 
-  def validate_existing_like(%{changes: %{actor: actor, object: object}} = cng) do
+  defp validate_existing_like(%{changes: %{actor: actor, object: object}} = cng) do
     if Utils.get_existing_like(actor, %{data: %{"id" => object}}) do
       cng
       |> add_error(:actor, "already liked this object")
@@ -95,5 +79,5 @@ def validate_existing_like(%{changes: %{actor: actor, object: object}} = cng) do
     end
   end
 
-  def validate_existing_like(cng), do: cng
+  defp validate_existing_like(cng), do: cng
 end
diff --git a/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs
index 939922127..20964e855 100644
--- a/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs
+++ b/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs
@@ -33,6 +33,18 @@ test "returns ok for a valid announce", %{valid_announce: valid_announce} do
       assert {:ok, _object, _meta} = ObjectValidator.validate(valid_announce, [])
     end
 
+    test "keeps announced object context", %{valid_announce: valid_announce} do
+      assert %Object{data: %{"context" => object_context}} =
+               Object.get_cached_by_ap_id(valid_announce["object"])
+
+      {:ok, %{"context" => context}, _} =
+        valid_announce
+        |> Map.put("context", "https://example.org/invalid_context_id")
+        |> ObjectValidator.validate([])
+
+      assert context == object_context
+    end
+
     test "returns an error if the object can't be found", %{valid_announce: valid_announce} do
       without_object =
         valid_announce
@@ -51,16 +63,6 @@ test "returns an error if the object can't be found", %{valid_announce: valid_an
       assert {:object, {"can't find object", []}} in cng.errors
     end
 
-    test "returns an error if we don't have the actor", %{valid_announce: valid_announce} do
-      nonexisting_actor =
-        valid_announce
-        |> Map.put("actor", "https://gensokyo.2hu/users/raymoo")
-
-      {:error, cng} = ObjectValidator.validate(nonexisting_actor, [])
-
-      assert {:actor, {"can't find user", []}} in cng.errors
-    end
-
     test "returns an error if the actor already announced the object", %{
       valid_announce: valid_announce,
       announcer: announcer,
diff --git a/test/pleroma/web/activity_pub/object_validators/like_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/like_validation_test.exs
index 55f67232e..e9ad817f1 100644
--- a/test/pleroma/web/activity_pub/object_validators/like_validation_test.exs
+++ b/test/pleroma/web/activity_pub/object_validators/like_validation_test.exs
@@ -40,17 +40,30 @@ test "is valid for a valid object", %{valid_like: valid_like} do
       assert LikeValidator.cast_and_validate(valid_like).valid?
     end
 
-    test "sets the 'to' field to the object actor if no recipients are given", %{
+    test "Add object actor from 'to' field if it doesn't owns the like", %{valid_like: valid_like} do
+      user = insert(:user)
+
+      object_actor = valid_like["actor"]
+
+      valid_like =
+        valid_like
+        |> Map.put("actor", user.ap_id)
+        |> Map.put("to", [])
+
+      {:ok, object, _meta} = ObjectValidator.validate(valid_like, [])
+      assert object_actor in object["to"]
+    end
+
+    test "Removes object actor from 'to' field if it owns the like", %{
       valid_like: valid_like,
       user: user
     } do
-      without_recipients =
+      valid_like =
         valid_like
-        |> Map.delete("to")
+        |> Map.put("to", [user.ap_id])
 
-      {:ok, object, _meta} = ObjectValidator.validate(without_recipients, [])
-
-      assert object["to"] == [user.ap_id]
+      {:ok, object, _meta} = ObjectValidator.validate(valid_like, [])
+      refute user.ap_id in object["to"]
     end
 
     test "sets the context field to the context of the object if no context is given", %{
@@ -66,16 +79,6 @@ test "sets the context field to the context of the object if no context is given
       assert object["context"] == post_activity.data["context"]
     end
 
-    test "it errors when the actor is missing or not known", %{valid_like: valid_like} do
-      without_actor = Map.delete(valid_like, "actor")
-
-      refute LikeValidator.cast_and_validate(without_actor).valid?
-
-      with_invalid_actor = Map.put(valid_like, "actor", "invalidactor")
-
-      refute LikeValidator.cast_and_validate(with_invalid_actor).valid?
-    end
-
     test "it errors when the object is missing or not known", %{valid_like: valid_like} do
       without_object = Map.delete(valid_like, "object")
 
diff --git a/test/pleroma/web/activity_pub/relay_test.exs b/test/pleroma/web/activity_pub/relay_test.exs
index 2aa07d1b5..d6de7d61e 100644
--- a/test/pleroma/web/activity_pub/relay_test.exs
+++ b/test/pleroma/web/activity_pub/relay_test.exs
@@ -148,7 +148,7 @@ test "returns error when object is unknown" do
       assert {:ok, %Activity{} = activity} = Relay.publish(note)
       assert activity.data["type"] == "Announce"
       assert activity.data["actor"] == service_actor.ap_id
-      assert activity.data["to"] == [service_actor.follower_address]
+      assert service_actor.follower_address in activity.data["to"]
       assert called(Pleroma.Web.Federator.publish(activity))
     end
 
diff --git a/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs
index 1886fea3f..524acddaf 100644
--- a/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs
@@ -150,27 +150,4 @@ test "it rejects incoming announces with an inlined activity from another origin
 
     assert {:error, _e} = Transmogrifier.handle_incoming(data)
   end
-
-  test "it does not clobber the addressing on announce activities" do
-    user = insert(:user)
-    {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
-
-    data =
-      File.read!("test/fixtures/mastodon-announce.json")
-      |> Jason.decode!()
-      |> Map.put("object", Object.normalize(activity, fetch: false).data["id"])
-      |> Map.put("to", ["http://mastodon.example.org/users/admin/followers"])
-      |> Map.put("cc", [])
-
-    _user =
-      insert(:user,
-        local: false,
-        ap_id: data["actor"],
-        follower_address: "http://mastodon.example.org/users/admin/followers"
-      )
-
-    {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
-
-    assert data["to"] == ["http://mastodon.example.org/users/admin/followers"]
-  end
 end

From d0147eba78ea6aeb054f53f18c36017a7583ff5c Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Wed, 9 Jun 2021 09:28:22 -0500
Subject: [PATCH 308/339] Use eblurhash 1.1.0 from Hex

---
 mix.exs  | 4 +---
 mix.lock | 2 +-
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/mix.exs b/mix.exs
index 5d945bf5f..afb4da1f6 100644
--- a/mix.exs
+++ b/mix.exs
@@ -196,9 +196,7 @@ defp deps do
       {:majic,
        git: "https://git.pleroma.social/pleroma/elixir-libraries/majic.git",
        ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"},
-      {:eblurhash,
-       git: "https://github.com/zotonic/eblurhash.git",
-       ref: "04a0b76eadf4de1be17726f39b6313b88708fd12"},
+      {:eblurhash, "~> 1.1.0"},
       {:open_api_spex, "~> 3.10"},
 
       ## dev & test
diff --git a/mix.lock b/mix.lock
index 1a0cae3ee..9665ca753 100644
--- a/mix.lock
+++ b/mix.lock
@@ -29,7 +29,7 @@
   "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
   "earmark": {:hex, :earmark, "1.4.15", "2c7f924bf495ec1f65bd144b355d0949a05a254d0ec561740308a54946a67888", [:mix], [{:earmark_parser, ">= 1.4.13", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "3b1209b85bc9f3586f370f7c363f6533788fb4e51db23aa79565875e7f9999ee"},
   "earmark_parser": {:hex, :earmark_parser, "1.4.13", "0c98163e7d04a15feb62000e1a891489feb29f3d10cb57d4f845c405852bbef8", [:mix], [], "hexpm", "d602c26af3a0af43d2f2645613f65841657ad6efc9f0e361c3b6c06b578214ba"},
-  "eblurhash": {:git, "https://github.com/zotonic/eblurhash.git", "04a0b76eadf4de1be17726f39b6313b88708fd12", [ref: "04a0b76eadf4de1be17726f39b6313b88708fd12"]},
+  "eblurhash": {:hex, :eblurhash, "1.1.0", "e10ccae762598507ebfacf0b645ed49520f2afa3e7e9943e73a91117dffce415", [:rebar3], [], "hexpm", "2e6b889d09fddd374e3c5ac57c486138768763264e99ac1074ae5fa7fc9ab51d"},
   "ecto": {:hex, :ecto, "3.4.6", "08f7afad3257d6eb8613309af31037e16c36808dfda5a3cd0cb4e9738db030e4", [: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", "6f13a9e2a62e75c2dcfc7207bfc65645ab387af8360db4c89fee8b5a4bf3f70b"},
   "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_explain": {:hex, :ecto_explain, "0.1.2", "a9d504cbd4adc809911f796d5ef7ebb17a576a6d32286c3d464c015bd39d5541", [:mix], [], "hexpm", "1d0e7798ae30ecf4ce34e912e5354a0c1c832b7ebceba39298270b9a9f316330"},

From 19a49dd757ebf60e8501c481f2d2be9d5e326808 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 9 Jun 2021 09:58:29 -0500
Subject: [PATCH 309/339] Remove Metadata.Utils.attachment_url/1

This was a wasteful shortcut to MediaProxy.preview_url/1 and we don't
always want the preview_url in the metadata anyway.
---
 lib/pleroma/web/metadata/providers/open_graph.ex | 16 ++++++++++------
 .../web/metadata/providers/twitter_card.ex       | 12 +++++++++---
 lib/pleroma/web/metadata/utils.ex                |  5 -----
 3 files changed, 19 insertions(+), 14 deletions(-)

diff --git a/lib/pleroma/web/metadata/providers/open_graph.ex b/lib/pleroma/web/metadata/providers/open_graph.ex
index e5712ec63..75d155236 100644
--- a/lib/pleroma/web/metadata/providers/open_graph.ex
+++ b/lib/pleroma/web/metadata/providers/open_graph.ex
@@ -4,6 +4,7 @@
 
 defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
   alias Pleroma.User
+  alias Pleroma.Web.MediaProxy
   alias Pleroma.Web.Metadata
   alias Pleroma.Web.Metadata.Providers.Provider
   alias Pleroma.Web.Metadata.Utils
@@ -36,8 +37,7 @@ def build_tags(%{
     ] ++
       if attachments == [] or Metadata.activity_nsfw?(object) do
         [
-          {:meta, [property: "og:image", content: Utils.attachment_url(User.avatar_url(user))],
-           []},
+          {:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))], []},
           {:meta, [property: "og:image:width", content: 150], []},
           {:meta, [property: "og:image:height", content: 150], []}
         ]
@@ -58,7 +58,7 @@ def build_tags(%{user: user}) do
         {:meta, [property: "og:url", content: user.uri || user.ap_id], []},
         {:meta, [property: "og:description", content: truncated_bio], []},
         {:meta, [property: "og:type", content: "article"], []},
-        {:meta, [property: "og:image", content: Utils.attachment_url(User.avatar_url(user))], []},
+        {:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))], []},
         {:meta, [property: "og:image:width", content: 150], []},
         {:meta, [property: "og:image:height", content: 150], []}
       ]
@@ -74,13 +74,17 @@ defp build_attachments(%{data: %{"attachment" => attachments}}) do
           case Utils.fetch_media_type(@media_types, url["mediaType"]) do
             "audio" ->
               [
-                {:meta, [property: "og:audio", content: Utils.attachment_url(url["href"])], []}
+                {:meta, [property: "og:audio", content: MediaProxy.url(url["href"])], []}
                 | acc
               ]
 
+            # Not using preview_url for this. It saves bandwidth, but the image dimensions will be wrong.
+            # We generate it on the fly and have no way to capture or analyze the image to get the dimensions.
+            # This can be an issue for apps/FEs rendering images in timelines too, but you can get clever with
+            # the aspect ratio metadata as a workaround.
             "image" ->
               [
-                {:meta, [property: "og:image", content: Utils.attachment_url(url["href"])], []},
+                {:meta, [property: "og:image", content: MediaProxy.url(url["href"])], []},
                 {:meta, [property: "og:image:alt", content: attachment["name"]], []}
                 | acc
               ]
@@ -88,7 +92,7 @@ defp build_attachments(%{data: %{"attachment" => attachments}}) do
 
             "video" ->
               [
-                {:meta, [property: "og:video", content: Utils.attachment_url(url["href"])], []}
+                {:meta, [property: "og:video", content: MediaProxy.url(url["href"])], []}
                 | acc
               ]
               |> maybe_add_dimensions(url)
diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex
index dfe477a8a..a952d0a05 100644
--- a/lib/pleroma/web/metadata/providers/twitter_card.ex
+++ b/lib/pleroma/web/metadata/providers/twitter_card.ex
@@ -5,6 +5,7 @@
 
 defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
   alias Pleroma.User
+  alias Pleroma.Web.MediaProxy
   alias Pleroma.Web.Metadata
   alias Pleroma.Web.Metadata.Providers.Provider
   alias Pleroma.Web.Metadata.Utils
@@ -48,7 +49,8 @@ defp title_tag(user) do
   end
 
   def image_tag(user) do
-    {:meta, [property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))], []}
+    {:meta, [property: "twitter:image", content: MediaProxy.preview_url(User.avatar_url(user))],
+     []}
   end
 
   defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
@@ -65,13 +67,17 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
                 | acc
               ]
 
+            # Not using preview_url for this. It saves bandwidth, but the image dimensions will be wrong.
+            # We generate it on the fly and have no way to capture or analyze the image to get the dimensions.
+            # This can be an issue for apps/FEs rendering images in timelines too, but you can get clever with
+            # the aspect ratio metadata as a workaround.
             "image" ->
               [
                 {:meta, [property: "twitter:card", content: "summary_large_image"], []},
                 {:meta,
                  [
                    property: "twitter:player",
-                   content: Utils.attachment_url(url["href"])
+                   content: MediaProxy.url(url["href"])
                  ], []}
                 | acc
               ]
@@ -87,7 +93,7 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
                 {:meta, [property: "twitter:player", content: player_url(id)], []},
                 {:meta, [property: "twitter:player:width", content: "#{width}"], []},
                 {:meta, [property: "twitter:player:height", content: "#{height}"], []},
-                {:meta, [property: "twitter:player:stream", content: url["href"]], []},
+                {:meta, [property: "twitter:player:stream", content: MediaProxy.url(url["href"])], []},
                 {:meta,
                  [property: "twitter:player:stream:content_type", content: url["mediaType"]], []}
                 | acc
diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex
index bc31d66b9..caca42934 100644
--- a/lib/pleroma/web/metadata/utils.ex
+++ b/lib/pleroma/web/metadata/utils.ex
@@ -7,7 +7,6 @@ defmodule Pleroma.Web.Metadata.Utils do
   alias Pleroma.Emoji
   alias Pleroma.Formatter
   alias Pleroma.HTML
-  alias Pleroma.Web.MediaProxy
 
   def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do
     content
@@ -38,10 +37,6 @@ def scrub_html(content) when is_binary(content) do
 
   def scrub_html(content), do: content
 
-  def attachment_url(url) do
-    MediaProxy.preview_url(url)
-  end
-
   def user_name_string(user) do
     "#{user.name} " <>
       if user.local do

From 2cf648d41989dc9cf243fb0972b075726c86adad Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 9 Jun 2021 10:02:41 -0500
Subject: [PATCH 310/339] Add a video thumbnail to the OpenGraph metadata if
 Media Preview Proxy is enabled.

---
 lib/pleroma/web/metadata/providers/open_graph.ex | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/lib/pleroma/web/metadata/providers/open_graph.ex b/lib/pleroma/web/metadata/providers/open_graph.ex
index 75d155236..332684782 100644
--- a/lib/pleroma/web/metadata/providers/open_graph.ex
+++ b/lib/pleroma/web/metadata/providers/open_graph.ex
@@ -96,6 +96,7 @@ defp build_attachments(%{data: %{"attachment" => attachments}}) do
                 | acc
               ]
               |> maybe_add_dimensions(url)
+              |> maybe_add_video_thumbnail(url)
 
             _ ->
               acc
@@ -124,4 +125,18 @@ defp maybe_add_dimensions(metadata, url) do
         metadata
     end
   end
+
+  defp maybe_add_video_thumbnail(url, metadata) do
+    cond do
+      Pleroma.Config.get([:media_preview_proxy, :enabled], false) ->
+        [
+          {:meta, [property: "og:image:width", content: "#{url["width"]}"], []},
+          {:meta, [property: "og:image:height", content: "#{url["height"]}"], []},
+          {:meta, [property: "og:image", content: MediaProxy.preview_url(url["href"])], []}
+        ]
+
+      true ->
+        metadata
+    end
+  end
 end

From dc8fe91decd9fd94b5e1ea4fcf2f798430b4c42e Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 9 Jun 2021 10:06:44 -0500
Subject: [PATCH 311/339] Metadata.Utils.attachment_url/1 was used in this test
 too

---
 test/pleroma/web/metadata/providers/twitter_card_test.exs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/test/pleroma/web/metadata/providers/twitter_card_test.exs b/test/pleroma/web/metadata/providers/twitter_card_test.exs
index dbb15b79f..1b8d27cda 100644
--- a/test/pleroma/web/metadata/providers/twitter_card_test.exs
+++ b/test/pleroma/web/metadata/providers/twitter_card_test.exs
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do
   alias Pleroma.User
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.Endpoint
+  alias Pleroma.Web.MediaProxy
   alias Pleroma.Web.Metadata.Providers.TwitterCard
   alias Pleroma.Web.Metadata.Utils
   alias Pleroma.Web.Router
@@ -17,7 +18,7 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do
 
   test "it renders twitter card for user info" do
     user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
-    avatar_url = Utils.attachment_url(User.avatar_url(user))
+    avatar_url = MediaProxy.preview_url(User.avatar_url(user))
     res = TwitterCard.build_tags(%{user: user})
 
     assert res == [

From 86bcb87e6c58797387934cbda5ec14b81f3f5f1d Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 9 Jun 2021 11:05:24 -0500
Subject: [PATCH 312/339] Fix incorrectly ordered arguments to the function and
 not properly merging lists.

---
 lib/pleroma/web/metadata/providers/open_graph.ex | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/metadata/providers/open_graph.ex b/lib/pleroma/web/metadata/providers/open_graph.ex
index 332684782..0a90904af 100644
--- a/lib/pleroma/web/metadata/providers/open_graph.ex
+++ b/lib/pleroma/web/metadata/providers/open_graph.ex
@@ -126,9 +126,10 @@ defp maybe_add_dimensions(metadata, url) do
     end
   end
 
-  defp maybe_add_video_thumbnail(url, metadata) do
+  defp maybe_add_video_thumbnail(metadata, url) do
     cond do
       Pleroma.Config.get([:media_preview_proxy, :enabled], false) ->
+        metadata ++
         [
           {:meta, [property: "og:image:width", content: "#{url["width"]}"], []},
           {:meta, [property: "og:image:height", content: "#{url["height"]}"], []},

From 2a47156b87c668d11f3f2eeee5782472c12c5279 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 9 Jun 2021 11:06:53 -0500
Subject: [PATCH 313/339] Lint

---
 lib/pleroma/web/metadata/providers/open_graph.ex | 16 +++++++++-------
 .../web/metadata/providers/twitter_card.ex       |  3 ++-
 2 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/lib/pleroma/web/metadata/providers/open_graph.ex b/lib/pleroma/web/metadata/providers/open_graph.ex
index 0a90904af..f6c5c36d7 100644
--- a/lib/pleroma/web/metadata/providers/open_graph.ex
+++ b/lib/pleroma/web/metadata/providers/open_graph.ex
@@ -37,7 +37,8 @@ def build_tags(%{
     ] ++
       if attachments == [] or Metadata.activity_nsfw?(object) do
         [
-          {:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))], []},
+          {:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))],
+           []},
           {:meta, [property: "og:image:width", content: 150], []},
           {:meta, [property: "og:image:height", content: 150], []}
         ]
@@ -58,7 +59,8 @@ def build_tags(%{user: user}) do
         {:meta, [property: "og:url", content: user.uri || user.ap_id], []},
         {:meta, [property: "og:description", content: truncated_bio], []},
         {:meta, [property: "og:type", content: "article"], []},
-        {:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))], []},
+        {:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))],
+         []},
         {:meta, [property: "og:image:width", content: 150], []},
         {:meta, [property: "og:image:height", content: 150], []}
       ]
@@ -130,11 +132,11 @@ defp maybe_add_video_thumbnail(metadata, url) do
     cond do
       Pleroma.Config.get([:media_preview_proxy, :enabled], false) ->
         metadata ++
-        [
-          {:meta, [property: "og:image:width", content: "#{url["width"]}"], []},
-          {:meta, [property: "og:image:height", content: "#{url["height"]}"], []},
-          {:meta, [property: "og:image", content: MediaProxy.preview_url(url["href"])], []}
-        ]
+          [
+            {:meta, [property: "og:image:width", content: "#{url["width"]}"], []},
+            {:meta, [property: "og:image:height", content: "#{url["height"]}"], []},
+            {:meta, [property: "og:image", content: MediaProxy.preview_url(url["href"])], []}
+          ]
 
       true ->
         metadata
diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex
index a952d0a05..bfcacf988 100644
--- a/lib/pleroma/web/metadata/providers/twitter_card.ex
+++ b/lib/pleroma/web/metadata/providers/twitter_card.ex
@@ -93,7 +93,8 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
                 {:meta, [property: "twitter:player", content: player_url(id)], []},
                 {:meta, [property: "twitter:player:width", content: "#{width}"], []},
                 {:meta, [property: "twitter:player:height", content: "#{height}"], []},
-                {:meta, [property: "twitter:player:stream", content: MediaProxy.url(url["href"])], []},
+                {:meta, [property: "twitter:player:stream", content: MediaProxy.url(url["href"])],
+                 []},
                 {:meta,
                  [property: "twitter:player:stream:content_type", content: url["mediaType"]], []}
                 | acc

From 5f7901cc48031dc7cb552a63b77721a6457425f6 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 9 Jun 2021 11:09:14 -0500
Subject: [PATCH 314/339] Credo

---
 lib/pleroma/web/metadata/providers/open_graph.ex   | 9 +++++----
 lib/pleroma/web/metadata/providers/twitter_card.ex | 9 +++++----
 2 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/lib/pleroma/web/metadata/providers/open_graph.ex b/lib/pleroma/web/metadata/providers/open_graph.ex
index f6c5c36d7..d9f2597ae 100644
--- a/lib/pleroma/web/metadata/providers/open_graph.ex
+++ b/lib/pleroma/web/metadata/providers/open_graph.ex
@@ -80,10 +80,11 @@ defp build_attachments(%{data: %{"attachment" => attachments}}) do
                 | acc
               ]
 
-            # Not using preview_url for this. It saves bandwidth, but the image dimensions will be wrong.
-            # We generate it on the fly and have no way to capture or analyze the image to get the dimensions.
-            # This can be an issue for apps/FEs rendering images in timelines too, but you can get clever with
-            # the aspect ratio metadata as a workaround.
+            # Not using preview_url for this. It saves bandwidth, but the image dimensions will
+            # be wrong. We generate it on the fly and have no way to capture or analyze the
+            # analyze the image to get the dimensions. This can be an issue for apps/FEs
+            # rendering images in timelines too, but you can get clever with the aspect ratio
+            # metadata as a workaround.
             "image" ->
               [
                 {:meta, [property: "og:image", content: MediaProxy.url(url["href"])], []},
diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex
index bfcacf988..8adab818d 100644
--- a/lib/pleroma/web/metadata/providers/twitter_card.ex
+++ b/lib/pleroma/web/metadata/providers/twitter_card.ex
@@ -67,10 +67,11 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
                 | acc
               ]
 
-            # Not using preview_url for this. It saves bandwidth, but the image dimensions will be wrong.
-            # We generate it on the fly and have no way to capture or analyze the image to get the dimensions.
-            # This can be an issue for apps/FEs rendering images in timelines too, but you can get clever with
-            # the aspect ratio metadata as a workaround.
+            # Not using preview_url for this. It saves bandwidth, but the image dimensions will
+            # be wrong. We generate it on the fly and have no way to capture or analyze the
+            # analyze the image to get the dimensions. This can be an issue for apps/FEs
+            # rendering images in timelines too, but you can get clever with the aspect ratio
+            # metadata as a workaround.
             "image" ->
               [
                 {:meta, [property: "twitter:card", content: "summary_large_image"], []},

From f37db238480f841763555418d11859e3f0a06e5e Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 9 Jun 2021 11:46:31 -0500
Subject: [PATCH 315/339] Test that videos only get image thumbnails in OGP
 metadata when we can produce them with Preview Proxy

---
 .../metadata/providers/open_graph_test.exs    | 80 +++++++++++++++++++
 1 file changed, 80 insertions(+)

diff --git a/test/pleroma/web/metadata/providers/open_graph_test.exs b/test/pleroma/web/metadata/providers/open_graph_test.exs
index f5f71cee5..28ca8839c 100644
--- a/test/pleroma/web/metadata/providers/open_graph_test.exs
+++ b/test/pleroma/web/metadata/providers/open_graph_test.exs
@@ -107,4 +107,84 @@ test "it does not render attachments if post is nsfw" do
 
     refute {:meta, [property: "og:image", content: "https://misskey.microsoft/corndog.png"], []} in result
   end
+
+  test "video attachments have image thumbnail with WxH metadata with Preview Proxy enabled" do
+    clear_config([:media_proxy, :enabled], true)
+    clear_config([:media_preview_proxy, :enabled], true)
+    user = insert(:user)
+
+    note =
+      insert(:note, %{
+        data: %{
+          "actor" => user.ap_id,
+          "id" => "https://pleroma.gov/objects/whatever",
+          "content" => "test video post",
+          "sensitive" => false,
+          "attachment" => [
+            %{
+              "url" => [
+                %{
+                  "mediaType" => "video/webm",
+                  "href" => "https://pleroma.gov/about/juche.webm",
+                  "height" => 600,
+                  "width" => 800
+                }
+              ]
+            }
+          ]
+        }
+      })
+
+    result = OpenGraph.build_tags(%{object: note, url: note.data["id"], user: user})
+
+    assert {:meta, [property: "og:image:width", content: "800"], []} in result
+    assert {:meta, [property: "og:image:height", content: "600"], []} in result
+
+    assert {:meta,
+            [
+              property: "og:image",
+              content:
+                "http://localhost:4001/proxy/preview/LzAnlke-l5oZbNzWsrHfprX1rGw/aHR0cHM6Ly9wbGVyb21hLmdvdi9hYm91dC9qdWNoZS53ZWJt/juche.webm"
+            ], []} in result
+  end
+
+  test "video attachments have no image thumbnail with Preview Proxy disabled" do
+    clear_config([:media_proxy, :enabled], true)
+    clear_config([:media_preview_proxy, :enabled], false)
+    user = insert(:user)
+
+    note =
+      insert(:note, %{
+        data: %{
+          "actor" => user.ap_id,
+          "id" => "https://pleroma.gov/objects/whatever",
+          "content" => "test video post",
+          "sensitive" => false,
+          "attachment" => [
+            %{
+              "url" => [
+                %{
+                  "mediaType" => "video/webm",
+                  "href" => "https://pleroma.gov/about/juche.webm",
+                  "height" => 600,
+                  "width" => 800
+                }
+              ]
+            }
+          ]
+        }
+      })
+
+    result = OpenGraph.build_tags(%{object: note, url: note.data["id"], user: user})
+
+    refute {:meta, [property: "og:image:width", content: "800"], []} in result
+    refute {:meta, [property: "og:image:height", content: "600"], []} in result
+
+    refute {:meta,
+            [
+              property: "og:image",
+              content:
+                "http://localhost:4001/proxy/preview/LzAnlke-l5oZbNzWsrHfprX1rGw/aHR0cHM6Ly9wbGVyb21hLmdvdi9hYm91dC9qdWNoZS53ZWJt/juche.webm"
+            ], []} in result
+  end
 end

From d12e62c0b6f83a439c49a4bd94b4e77e53da66a1 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 9 Jun 2021 11:56:54 -0500
Subject: [PATCH 316/339] Add new Twittercard/OGP changes

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index dcb462f07..52d92c6d2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
 - HTTPSecurityPlug now sends a response header to opt out of Google's FLoC (Federated Learning of Cohorts) targeted advertising.
 - Email address is now returned if requesting user is the owner of the user account so it can be exposed in client and FE user settings UIs.
+- Improved Twittercard and OpenGraph meta tag generation including thumbnails and image dimension metadata when available.
 
 ### Added
 

From 6aa7fc15df372478fbff02730bc521fab2ccd1e3 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Wed, 9 Jun 2021 11:58:51 -0500
Subject: [PATCH 317/339] Formatting of the comment

---
 lib/pleroma/web/metadata/providers/open_graph.ex   | 6 +++---
 lib/pleroma/web/metadata/providers/twitter_card.ex | 6 +++---
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/lib/pleroma/web/metadata/providers/open_graph.ex b/lib/pleroma/web/metadata/providers/open_graph.ex
index d9f2597ae..ef4ad6885 100644
--- a/lib/pleroma/web/metadata/providers/open_graph.ex
+++ b/lib/pleroma/web/metadata/providers/open_graph.ex
@@ -82,9 +82,9 @@ defp build_attachments(%{data: %{"attachment" => attachments}}) do
 
             # Not using preview_url for this. It saves bandwidth, but the image dimensions will
             # be wrong. We generate it on the fly and have no way to capture or analyze the
-            # analyze the image to get the dimensions. This can be an issue for apps/FEs
-            # rendering images in timelines too, but you can get clever with the aspect ratio
-            # metadata as a workaround.
+            # image to get the dimensions. This can be an issue for apps/FEs rendering images
+            # in timelines too, but you can get clever with the aspect ratio metadata as a
+            # workaround.
             "image" ->
               [
                 {:meta, [property: "og:image", content: MediaProxy.url(url["href"])], []},
diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex
index 8adab818d..79183df86 100644
--- a/lib/pleroma/web/metadata/providers/twitter_card.ex
+++ b/lib/pleroma/web/metadata/providers/twitter_card.ex
@@ -69,9 +69,9 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
 
             # Not using preview_url for this. It saves bandwidth, but the image dimensions will
             # be wrong. We generate it on the fly and have no way to capture or analyze the
-            # analyze the image to get the dimensions. This can be an issue for apps/FEs
-            # rendering images in timelines too, but you can get clever with the aspect ratio
-            # metadata as a workaround.
+            # image to get the dimensions. This can be an issue for apps/FEs rendering images
+            # in timelines too, but you can get clever with the aspect ratio metadata as a
+            # workaround.
             "image" ->
               [
                 {:meta, [property: "twitter:card", content: "summary_large_image"], []},

From 4bb578a1d76c8094db36021db0aed2dfcebd1dbc Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Sun, 23 May 2021 18:31:07 -0500
Subject: [PATCH 318/339] Add cycles test to .gitlab-ci.yml Thank you
 @jb55@bitcoinhackers.org for the awk syntax

---
 .gitlab-ci.yml | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b155c81bd..88504b3e3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -154,6 +154,14 @@ analysis:
   script:
     - mix credo --strict --only=warnings,todo,fixme,consistency,readability
 
+cycles:
+  stage: test
+  image: elixir:1.11
+  script:
+    - mix deps.get
+    - mix compile
+    - mix xref graph --format cycles --label compile | awk '{print $0} END{exit ($0 != "No cycles found")}'
+
 docs-deploy:
   stage: deploy
   cache: *testing_cache_policy

From cefb952dffb3f6fb3e515167e58f910e7e6fc8ea Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Wed, 9 Jun 2021 13:08:24 -0500
Subject: [PATCH 319/339] CI: echo $MIX_ENV

---
 .gitlab-ci.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 88504b3e3..a790d60a4 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -24,6 +24,7 @@ stages:
   - docker
 
 before_script:
+  - echo $MIX_ENV
   - rm -rf _build/*/lib/pleroma
   - apt-get update && apt-get install -y cmake
   - mix local.hex --force

From 87cd04fe0c10f5952aa456237906e4c966e445ea Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Wed, 9 Jun 2021 13:12:33 -0500
Subject: [PATCH 320/339] Cycles CI: disable cache

---
 .gitlab-ci.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a790d60a4..056af56cd 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -158,6 +158,7 @@ analysis:
 cycles:
   stage: test
   image: elixir:1.11
+  cache: {}
   script:
     - mix deps.get
     - mix compile

From 15e2aaa9f6e2201c46d18d8ddead922a2ef3288f Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Wed, 9 Jun 2021 13:30:19 -0500
Subject: [PATCH 321/339] Fix compile cycle in Pleroma.Tests.AuthTestController

---
 lib/pleroma/tests/auth_test_controller.ex | 12 ++----------
 1 file changed, 2 insertions(+), 10 deletions(-)

diff --git a/lib/pleroma/tests/auth_test_controller.ex b/lib/pleroma/tests/auth_test_controller.ex
index ddf3fea4f..76514948b 100644
--- a/lib/pleroma/tests/auth_test_controller.ex
+++ b/lib/pleroma/tests/auth_test_controller.ex
@@ -9,7 +9,6 @@ defmodule Pleroma.Tests.AuthTestController do
   use Pleroma.Web, :controller
 
   alias Pleroma.User
-  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
   alias Pleroma.Web.Plugs.OAuthScopesPlug
 
   # Serves only with proper OAuth token (:api and :authenticated_api)
@@ -47,10 +46,7 @@ defmodule Pleroma.Tests.AuthTestController do
   # Via :authenticated_api, serves if token is present and has requested scopes
   #
   # Suggested use: as :fallback_oauth_check but open with nil :user for :api on private instances
-  plug(
-    :skip_plug,
-    EnsurePublicOrAuthenticatedPlug when action == :fallback_oauth_skip_publicity_check
-  )
+  plug(:skip_public_check when action == :fallback_oauth_skip_publicity_check)
 
   plug(
     OAuthScopesPlug,
@@ -62,11 +58,7 @@ defmodule Pleroma.Tests.AuthTestController do
   # Via :authenticated_api, serves if :user is set (regardless of token presence and its scopes)
   #
   # Suggested use: making an :api endpoint always accessible (e.g. email confirmation endpoint)
-  plug(
-    :skip_plug,
-    [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug]
-    when action == :skip_oauth_skip_publicity_check
-  )
+  plug(:skip_auth when action == :skip_oauth_skip_publicity_check)
 
   # Via :authenticated_api, always fails with 403 (endpoint is insecure)
   # Via :api, drops :user if present and serves if public (private instance rejects on no user)

From 202ee5fd77e721c8822dd779c8b558ec8cfacfcc Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Thu, 10 Jun 2021 09:56:43 -0500
Subject: [PATCH 322/339] Add note about video thumbnails for code spelunkers
 unfamiliar with Media Preview Proxy

---
 lib/pleroma/web/metadata/providers/open_graph.ex | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lib/pleroma/web/metadata/providers/open_graph.ex b/lib/pleroma/web/metadata/providers/open_graph.ex
index ef4ad6885..df0cca74a 100644
--- a/lib/pleroma/web/metadata/providers/open_graph.ex
+++ b/lib/pleroma/web/metadata/providers/open_graph.ex
@@ -129,6 +129,8 @@ defp maybe_add_dimensions(metadata, url) do
     end
   end
 
+  # Media Preview Proxy makes thumbnails of videos without resizing, so we can trust the
+  # width and height of the source video.
   defp maybe_add_video_thumbnail(metadata, url) do
     cond do
       Pleroma.Config.get([:media_preview_proxy, :enabled], false) ->

From 6b1f7f2f528824a1f5e935a14645e7731a9c2a9c Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Fri, 11 Jun 2021 08:43:36 +0200
Subject: [PATCH 323/339] docs: Use one file to describe dependencies

---
 docs/installation/alpine_linux_en.md          | 20 +------------------
 docs/installation/debian_based_en.md          | 20 +------------------
 docs/installation/freebsd_en.md               |  4 +++-
 .../installation/generic_dependencies.include | 16 +++++++++++++++
 docs/installation/gentoo_en.md                |  4 +---
 docs/installation/netbsd_en.md                |  4 +++-
 docs/installation/openbsd_en.md               | 14 +++----------
 7 files changed, 28 insertions(+), 54 deletions(-)
 create mode 100644 docs/installation/generic_dependencies.include

diff --git a/docs/installation/alpine_linux_en.md b/docs/installation/alpine_linux_en.md
index 54859bf03..13395ff25 100644
--- a/docs/installation/alpine_linux_en.md
+++ b/docs/installation/alpine_linux_en.md
@@ -5,25 +5,7 @@ This guide is a step-by-step installation guide for Alpine Linux. The instructio
 
 It assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.linode.com/docs/tools-reference/custom-kernels-distros/install-alpine-linux-on-your-linode/#configuration). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su -l <username> -s $SHELL -c 'command'` instead.
 
-### Required packages
-
-* `postgresql`
-* `elixir`
-* `erlang`
-* `erlang-parsetools`
-* `erlang-xmerl`
-* `git`
-* `file-dev`
-* Development Tools
-* `cmake`
-
-#### Optional packages used in this guide
-
-* `nginx` (preferred, example configs for other reverse proxies can be found in the repo)
-* `certbot` (or any other ACME client for Let’s Encrypt certificates)
-* `ImageMagick`
-* `ffmpeg`
-* `exiftool`
+{! backend/installation/generic_dependencies.include !}
 
 ### Prepare the system
 
diff --git a/docs/installation/debian_based_en.md b/docs/installation/debian_based_en.md
index b8c2b8e86..b6d24a5e9 100644
--- a/docs/installation/debian_based_en.md
+++ b/docs/installation/debian_based_en.md
@@ -3,25 +3,7 @@
 
 This guide will assume you are on Debian Stretch. This guide should also work with Ubuntu 16.04 and 18.04. It also assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.digitalocean.com/community/tutorials/how-to-add-delete-and-grant-sudo-privileges-to-users-on-a-debian-vps). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead.
 
-### Required packages
-
-* `postgresql` (9.6+, Ubuntu 16.04 comes with 9.5, you can get a newer version from [here](https://www.postgresql.org/download/linux/ubuntu/))
-* `postgresql-contrib` (9.6+, same situtation as above)
-* `elixir` (1.8+, Follow the guide to install from the Erlang Solutions repo or use [asdf](https://github.com/asdf-vm/asdf) as the pleroma user)
-* `erlang-dev`
-* `erlang-nox`
-* `libmagic-dev`
-* `git`
-* `build-essential`
-* `cmake`
-
-#### Optional packages used in this guide
-
-* `nginx` (preferred, example configs for other reverse proxies can be found in the repo)
-* `certbot` (or any other ACME client for Let’s Encrypt certificates)
-* `ImageMagick`
-* `ffmpeg`
-* `exiftool`
+{! backend/installation/generic_dependencies.include !}
 
 ### Prepare the system
 
diff --git a/docs/installation/freebsd_en.md b/docs/installation/freebsd_en.md
index 39b8e8d66..9cbe0f203 100644
--- a/docs/installation/freebsd_en.md
+++ b/docs/installation/freebsd_en.md
@@ -2,7 +2,9 @@
 
 This document was written for FreeBSD 12.1, but should be work on future releases.
 
-## Required software
+{! backend/installation/generic_dependencies.include !}
+
+## Installing software used in this guide
 
 This assumes the target system has `pkg(8)`.
 
diff --git a/docs/installation/generic_dependencies.include b/docs/installation/generic_dependencies.include
new file mode 100644
index 000000000..baed19de0
--- /dev/null
+++ b/docs/installation/generic_dependencies.include
@@ -0,0 +1,16 @@
+## Required dependencies
+
+* PostgreSQL 9.6+
+* Elixir 1.9+
+* Erlang OTP 22.2+
+* git
+* file / libmagic
+* gcc (clang might also work)
+* GNU make
+* CMake
+
+## Optionnal dependencies
+
+* ImageMagick
+* FFmpeg
+* exiftool
diff --git a/docs/installation/gentoo_en.md b/docs/installation/gentoo_en.md
index d649393fc..982ab52d2 100644
--- a/docs/installation/gentoo_en.md
+++ b/docs/installation/gentoo_en.md
@@ -3,9 +3,7 @@
 
 This guide will assume that you have administrative rights, either as root or a user with [sudo permissions](https://wiki.gentoo.org/wiki/Sudo). Lines that begin with `#` indicate that they should be run as the superuser. Lines using `$` should be run as the indicated user, e.g. `pleroma$` should be run as the `pleroma` user.
 
-### Configuring your hostname (optional)
-
-If you would like your prompt to permanently include your host/domain, change `/etc/conf.d/hostname` to your hostname. You can reboot or use the `hostname` command to make immediate changes.
+{! backend/installation/generic_dependencies.include !}
 
 ### Your make.conf, package.use, and USE flags
 
diff --git a/docs/installation/netbsd_en.md b/docs/installation/netbsd_en.md
index fc56e79ce..41b3b0072 100644
--- a/docs/installation/netbsd_en.md
+++ b/docs/installation/netbsd_en.md
@@ -1,6 +1,8 @@
 # Installing on NetBSD
 
-## Required software
+{! backend/installation/generic_dependencies.include !}
+
+## Installing software used in this guide
 
 pkgin should have been installed by the NetBSD installer if you selected
 the right options. If it isn't installed, install it using pkg_add.
diff --git a/docs/installation/openbsd_en.md b/docs/installation/openbsd_en.md
index 95f029180..c80c8f678 100644
--- a/docs/installation/openbsd_en.md
+++ b/docs/installation/openbsd_en.md
@@ -4,19 +4,11 @@ This guide describes the installation and configuration of pleroma (and the requ
 
 For any additional information regarding commands and configuration files mentioned here, check the man pages [online](https://man.openbsd.org/) or directly on your server with the man command.
 
+{! backend/installation/generic_dependencies.include !}
+
+### Preparing the system
 #### Required software
 
-The following packages need to be installed:
-
-  * elixir
-  * gmake
-  * git
-  * postgresql-server
-  * postgresql-contrib
-  * cmake
-  * ffmpeg
-  * ImageMagick
-
 To install them, run the following command (with doas or as root):
 
 ```

From 17f980e9abf25b005570d3b638a111b953e87ee0 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Fri, 11 Jun 2021 08:44:27 +0200
Subject: [PATCH 324/339] docs: Remove Erlang Solutions repository

---
 docs/installation/debian_based_en.md | 12 +++---------
 1 file changed, 3 insertions(+), 9 deletions(-)

diff --git a/docs/installation/debian_based_en.md b/docs/installation/debian_based_en.md
index b6d24a5e9..02682e5b0 100644
--- a/docs/installation/debian_based_en.md
+++ b/docs/installation/debian_based_en.md
@@ -1,7 +1,7 @@
 # Installing on Debian Based Distributions
 ## Installation
 
-This guide will assume you are on Debian Stretch. This guide should also work with Ubuntu 16.04 and 18.04. It also assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.digitalocean.com/community/tutorials/how-to-add-delete-and-grant-sudo-privileges-to-users-on-a-debian-vps). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead.
+This guide will assume you are on Debian 11 (“bullseye”) or later. This guide should also work with Ubuntu 18.04 (“Bionic Beaver”) and later. It also assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.digitalocean.com/community/tutorials/how-to-add-delete-and-grant-sudo-privileges-to-users-on-a-debian-vps). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead.
 
 {! backend/installation/generic_dependencies.include !}
 
@@ -22,20 +22,14 @@ sudo apt install git build-essential postgresql postgresql-contrib cmake libmagi
 
 ### Install Elixir and Erlang
 
-* Download and add the Erlang repository:
-
-```shell
-wget -P /tmp/ https://packages.erlang-solutions.com/erlang-solutions_2.0_all.deb
-sudo dpkg -i /tmp/erlang-solutions_2.0_all.deb
-```
-
-* Install Elixir and Erlang:
+* Install Elixir and Erlang (you might need to use backports or [asdf](https://github.com/asdf-vm/asdf) on old systems):
 
 ```shell
 sudo apt update
 sudo apt install elixir erlang-dev erlang-nox
 ```
 
+
 ### Optional packages: [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md)
 
 ```shell

From 822196f393e8b214b03ece875af0f53e41f40528 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Fri, 11 Jun 2021 08:46:38 +0200
Subject: [PATCH 325/339] =?UTF-8?q?docs/=E2=80=A6/opt=5Fen.md:=20Reuse=20/?=
 =?UTF-8?q?main/=20repository=20url=20for=20the=20/community/=20repo?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 docs/installation/otp_en.md | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md
index 8e43e3239..3f67534ac 100644
--- a/docs/installation/otp_en.md
+++ b/docs/installation/otp_en.md
@@ -31,7 +31,7 @@ Other than things bundled in the OTP release Pleroma depends on:
 
 === "Alpine"
     ```
-    echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories
+    awk 'NR==2' /etc/apk/repositories | sed 's/main/community/' | tee -a /etc/apk/repositories
     apk update
     apk add curl unzip ncurses postgresql postgresql-contrib nginx certbot file-dev
     ```
@@ -50,7 +50,6 @@ Per [`docs/installation/optional/media_graphics_packages.md`](optional/media_gra
 
 === "Alpine"
     ```
-    echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories
     apk update
     apk add imagemagick ffmpeg exiftool
     ```

From 640e1cf09d501b5b0088cb0c3bdb123c171e730a Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Fri, 11 Jun 2021 08:45:19 -0500
Subject: [PATCH 326/339] Cycles CI: skip unless Elixir code is modified

---
 .gitlab-ci.yml | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 056af56cd..3ac30b13d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -158,6 +158,11 @@ analysis:
 cycles:
   stage: test
   image: elixir:1.11
+  only:
+    changes:
+      - "**/*.ex"
+      - "**/*.exs"
+      - "mix.lock"
   cache: {}
   script:
     - mix deps.get

From a851a24036e07db99f9d893ec9043f0d826ca877 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Tue, 22 Jun 2021 11:12:53 +0200
Subject: [PATCH 327/339] Downgrade Plug to 1.10.x, revert upload_limit tuple
 to function change

This should fix setting the upload limit in the database as found in:
https://queer.hacktivis.me/notice/A8XUZp74Cg7eYNEMxU

This reverts commit 7d350b73f58664eb822efaa5f522fcf2bd38f669.
---
 lib/pleroma/web/endpoint.ex | 2 +-
 mix.exs                     | 3 +++
 mix.lock                    | 2 +-
 3 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index 7591d0ae5..8e274de88 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -102,7 +102,7 @@ defmodule Pleroma.Web.Endpoint do
   plug(Plug.Parsers,
     parsers: [
       :urlencoded,
-      {:multipart, length: Config.get([:instance, :upload_limit])},
+      {:multipart, length: {Config, :get, [[:instance, :upload_limit]]}},
       :json
     ],
     pass: ["*/*"],
diff --git a/mix.exs b/mix.exs
index afb4da1f6..a0a6106a9 100644
--- a/mix.exs
+++ b/mix.exs
@@ -199,6 +199,9 @@ defp deps do
       {:eblurhash, "~> 1.1.0"},
       {:open_api_spex, "~> 3.10"},
 
+      # 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},
diff --git a/mix.lock b/mix.lock
index 9665ca753..7a1dbb22c 100644
--- a/mix.lock
+++ b/mix.lock
@@ -95,7 +95,7 @@
   "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.11.1", "f2992bac66fdae679453c9e86134a4201f6f43a687d8ff1cd1b2862d53c80259", [: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", "23524e4fefbb587c11f0833b3910bfb414bf2e2534d61928e920f54e3a1b881f"},
+  "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"},

From fc6ab78a84b1ef384fa48349e792921364de5df9 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Tue, 22 Jun 2021 12:25:25 +0200
Subject: [PATCH 328/339] Add test on changing [:instance, :upload_limit]

---
 .../api_spec/operations/media_operation.ex    |  2 +
 .../controllers/media_controller_test.exs     | 55 +++++++++++++++++++
 2 files changed, 57 insertions(+)

diff --git a/lib/pleroma/web/api_spec/operations/media_operation.ex b/lib/pleroma/web/api_spec/operations/media_operation.ex
index 1e245b291..451b6510f 100644
--- a/lib/pleroma/web/api_spec/operations/media_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/media_operation.ex
@@ -24,6 +24,7 @@ def create_operation do
       requestBody: Helpers.request_body("Parameters", create_request()),
       responses: %{
         200 => Operation.response("Media", "application/json", Attachment),
+        400 => Operation.response("Media", "application/json", ApiError),
         401 => Operation.response("Media", "application/json", ApiError),
         422 => Operation.response("Media", "application/json", ApiError)
       }
@@ -121,6 +122,7 @@ def create2_operation do
       requestBody: Helpers.request_body("Parameters", create_request()),
       responses: %{
         202 => Operation.response("Media", "application/json", Attachment),
+        400 => Operation.response("Media", "application/json", ApiError),
         422 => Operation.response("Media", "application/json", ApiError),
         500 => Operation.response("Media", "application/json", ApiError)
       }
diff --git a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs
index 39d7f99f6..ff988a7fd 100644
--- a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs
@@ -5,6 +5,8 @@
 defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do
   use Pleroma.Web.ConnCase
 
+  import ExUnit.CaptureLog
+
   alias Pleroma.Object
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
@@ -67,6 +69,59 @@ test "/api/v2/media", %{conn: conn, user: user, image: image} do
       object = Object.get_by_id(media["id"])
       assert object.data["actor"] == user.ap_id
     end
+
+    test "/api/v2/media, upload_limit", %{conn: conn, user: user} do
+      desc = "Description of the binary"
+
+      upload_limit = Config.get([:instance, :upload_limit]) * 8 + 8
+
+      assert :ok ==
+               File.write(Path.absname("test/tmp/large_binary.data"), <<0::size(upload_limit)>>)
+
+      large_binary = %Plug.Upload{
+        content_type: nil,
+        path: Path.absname("test/tmp/large_binary.data"),
+        filename: "large_binary.data"
+      }
+
+      assert capture_log(fn ->
+               assert %{"error" => "file_too_large"} =
+                        conn
+                        |> put_req_header("content-type", "multipart/form-data")
+                        |> post("/api/v2/media", %{
+                          "file" => large_binary,
+                          "description" => desc
+                        })
+                        |> json_response_and_validate_schema(400)
+             end) =~
+               "[error] Elixir.Pleroma.Upload store (using Pleroma.Uploaders.Local) failed: :file_too_large"
+
+      clear_config([:instance, :upload_limit], upload_limit)
+
+      assert response =
+               conn
+               |> put_req_header("content-type", "multipart/form-data")
+               |> post("/api/v2/media", %{
+                 "file" => large_binary,
+                 "description" => desc
+               })
+               |> json_response_and_validate_schema(202)
+
+      assert media_id = response["id"]
+
+      %{conn: conn} = oauth_access(["read:media"], user: user)
+
+      media =
+        conn
+        |> get("/api/v1/media/#{media_id}")
+        |> json_response_and_validate_schema(200)
+
+      assert media["type"] == "unknown"
+      assert media["description"] == desc
+      assert media["id"]
+
+      assert :ok == File.rm(Path.absname("test/tmp/large_binary.data"))
+    end
   end
 
   describe "Update media description" do

From 54af527759a222fff4adc7ab52425f4e1085eb2c Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Wed, 23 Jun 2021 13:02:41 -0500
Subject: [PATCH 329/339] Upgrade Ecto to v3.6.2, remove deprecated
 ecto_explain

---
 lib/pleroma/repo.ex                          | 2 --
 mix.exs                                      | 3 +--
 mix.lock                                     | 5 ++---
 test/mix/tasks/pleroma/ecto/migrate_test.exs | 2 +-
 4 files changed, 4 insertions(+), 8 deletions(-)

diff --git a/lib/pleroma/repo.ex b/lib/pleroma/repo.ex
index b8ea06e33..61b64ed3e 100644
--- a/lib/pleroma/repo.ex
+++ b/lib/pleroma/repo.ex
@@ -8,8 +8,6 @@ defmodule Pleroma.Repo do
     adapter: Ecto.Adapters.Postgres,
     migration_timestamps: [type: :naive_datetime_usec]
 
-  use Ecto.Explain
-
   import Ecto.Query
   require Logger
 
diff --git a/mix.exs b/mix.exs
index afb4da1f6..92b76d70c 100644
--- a/mix.exs
+++ b/mix.exs
@@ -121,8 +121,7 @@ defp deps do
       {:phoenix_pubsub, "~> 2.0"},
       {:phoenix_ecto, "~> 4.0"},
       {:ecto_enum, "~> 1.4"},
-      {:ecto_explain, "~> 0.1.2"},
-      {:ecto_sql, "~> 3.4.4"},
+      {:ecto_sql, "~> 3.6.2"},
       {:postgrex, ">= 0.15.5"},
       {:oban, "~> 2.3.4"},
       {:gettext, "~> 0.18"},
diff --git a/mix.lock b/mix.lock
index 9665ca753..a5b9cb80f 100644
--- a/mix.lock
+++ b/mix.lock
@@ -30,10 +30,9 @@
   "earmark": {:hex, :earmark, "1.4.15", "2c7f924bf495ec1f65bd144b355d0949a05a254d0ec561740308a54946a67888", [:mix], [{:earmark_parser, ">= 1.4.13", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "3b1209b85bc9f3586f370f7c363f6533788fb4e51db23aa79565875e7f9999ee"},
   "earmark_parser": {:hex, :earmark_parser, "1.4.13", "0c98163e7d04a15feb62000e1a891489feb29f3d10cb57d4f845c405852bbef8", [:mix], [], "hexpm", "d602c26af3a0af43d2f2645613f65841657ad6efc9f0e361c3b6c06b578214ba"},
   "eblurhash": {:hex, :eblurhash, "1.1.0", "e10ccae762598507ebfacf0b645ed49520f2afa3e7e9943e73a91117dffce415", [:rebar3], [], "hexpm", "2e6b889d09fddd374e3c5ac57c486138768763264e99ac1074ae5fa7fc9ab51d"},
-  "ecto": {:hex, :ecto, "3.4.6", "08f7afad3257d6eb8613309af31037e16c36808dfda5a3cd0cb4e9738db030e4", [: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", "6f13a9e2a62e75c2dcfc7207bfc65645ab387af8360db4c89fee8b5a4bf3f70b"},
+  "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_explain": {:hex, :ecto_explain, "0.1.2", "a9d504cbd4adc809911f796d5ef7ebb17a576a6d32286c3d464c015bd39d5541", [:mix], [], "hexpm", "1d0e7798ae30ecf4ce34e912e5354a0c1c832b7ebceba39298270b9a9f316330"},
-  "ecto_sql": {:hex, :ecto_sql, "3.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"},
+  "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"},
   "elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"},
   "esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"},
diff --git a/test/mix/tasks/pleroma/ecto/migrate_test.exs b/test/mix/tasks/pleroma/ecto/migrate_test.exs
index 5bdfd8f30..3bfdde1c0 100644
--- a/test/mix/tasks/pleroma/ecto/migrate_test.exs
+++ b/test/mix/tasks/pleroma/ecto/migrate_test.exs
@@ -13,7 +13,7 @@ test "ecto.migrate info message" do
 
     assert capture_log(fn ->
              Mix.Tasks.Pleroma.Ecto.Migrate.run()
-           end) =~ "[info] Already up"
+           end) =~ "[info] Migrations already up"
 
     Logger.configure(level: level)
   end

From 281806de75ae5b0cad0c9b0f5dbc7c01c0b64f93 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Thu, 24 Jun 2021 21:00:23 -0500
Subject: [PATCH 330/339] Activity deletion: fix FunctionClauseError #2686

---
 lib/pleroma/activity.ex | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index 53beca5e6..7e36c1b53 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -313,13 +313,15 @@ def delete_all_by_object_ap_id(id) when is_binary(id) do
 
   def delete_all_by_object_ap_id(_), do: nil
 
-  defp purge_web_resp_cache(%Activity{} = activity) do
-    %{path: path} = URI.parse(activity.data["id"])
-    @cachex.del(:web_resp_cache, path)
+  defp purge_web_resp_cache(%Activity{data: %{"id" => id}} = activity) when is_binary(id) do
+    with %{path: path} <- URI.parse(id) do
+      @cachex.del(:web_resp_cache, path)
+    end
+
     activity
   end
 
-  defp purge_web_resp_cache(nil), do: nil
+  defp purge_web_resp_cache(activity), do: activity
 
   def follow_accepted?(
         %Activity{data: %{"type" => "Follow", "object" => followed_ap_id}} = activity

From be2da95c36c14ac42eee4009c6e3e803bafd3d2c Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 29 Jun 2021 21:45:38 -0500
Subject: [PATCH 331/339] Correctly purge a remote user

---
 lib/pleroma/user.ex        | 16 ++++++++++------
 test/pleroma/user_test.exs | 18 ++++++++++++++++++
 2 files changed, 28 insertions(+), 6 deletions(-)

diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 9942617d8..aebb5da95 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1713,6 +1713,12 @@ def purge_user_changeset(user) do
     })
   end
 
+  def purge(%User{} = user) do
+    user
+    |> purge_user_changeset()
+    |> update_and_set_cache()
+  end
+
   def delete(users) when is_list(users) do
     for user <- users, do: delete(user)
   end
@@ -1726,9 +1732,9 @@ defp delete_and_invalidate_cache(%User{} = user) do
     Repo.delete(user)
   end
 
-  defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user)
+  defp delete_or_purge(%User{local: false} = user), do: purge(user)
 
-  defp delete_or_deactivate(%User{local: true} = user) do
+  defp delete_or_purge(%User{local: true} = user) do
     status = account_status(user)
 
     case status do
@@ -1739,9 +1745,7 @@ defp delete_or_deactivate(%User{local: true} = user) do
         delete_and_invalidate_cache(user)
 
       _ ->
-        user
-        |> purge_user_changeset()
-        |> update_and_set_cache()
+        purge(user)
     end
   end
 
@@ -1769,7 +1773,7 @@ def perform(:delete, %User{} = user) do
 
     delete_outgoing_pending_follow_requests(user)
 
-    delete_or_deactivate(user)
+    delete_or_purge(user)
   end
 
   def perform(:set_activation_async, user, status), do: set_activation(user, status)
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index 6f5bcab57..529f837e8 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -1684,6 +1684,24 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do
            } = user
   end
 
+  test "delete/1 purges a remote user" do
+    user =
+      insert(:user, %{
+        name: "qqqqqqq",
+        avatar: %{"a" => "b"},
+        banner: %{"a" => "b"},
+        local: false
+      })
+
+    {:ok, job} = User.delete(user)
+    {:ok, _} = ObanHelpers.perform(job)
+    user = User.get_by_id(user.id)
+
+    assert user.name == nil
+    assert user.avatar == %{}
+    assert user.banner == %{}
+  end
+
   test "get_public_key_for_ap_id fetches a user that's not in the db" do
     assert {:ok, _key} = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin")
   end

From c6d4133727ba623d4c96358e3c4de5f2194d07f8 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 29 Jun 2021 22:30:48 -0500
Subject: [PATCH 332/339] Deletions: purge the user immediately

---
 lib/pleroma/user.ex | 25 ++++++++++---------------
 1 file changed, 10 insertions(+), 15 deletions(-)

diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index aebb5da95..406a7f5f9 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1724,31 +1724,27 @@ def delete(users) when is_list(users) do
   end
 
   def delete(%User{} = user) do
+    purge(user)
     BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
   end
 
-  defp delete_and_invalidate_cache(%User{} = user) do
+  defp delete_from_db(%User{} = user) do
     invalidate_cache(user)
     Repo.delete(user)
   end
 
-  defp delete_or_purge(%User{local: false} = user), do: purge(user)
-
-  defp delete_or_purge(%User{local: true} = user) do
+  defp maybe_delete_from_db(%User{local: true} = user) do
     status = account_status(user)
 
-    case status do
-      :confirmation_pending ->
-        delete_and_invalidate_cache(user)
-
-      :approval_pending ->
-        delete_and_invalidate_cache(user)
-
-      _ ->
-        purge(user)
+    if status in [:confirmation_pending, :approval_pending] do
+      delete_from_db(user)
+    else
+      {:ok, user}
     end
   end
 
+  defp maybe_delete_from_db(user), do: {:ok, user}
+
   def perform(:force_password_reset, user), do: force_password_reset(user)
 
   @spec perform(atom(), User.t()) :: {:ok, User.t()}
@@ -1770,10 +1766,9 @@ def perform(:delete, %User{} = user) do
 
     delete_user_activities(user)
     delete_notifications_from_user_activities(user)
-
     delete_outgoing_pending_follow_requests(user)
 
-    delete_or_purge(user)
+    maybe_delete_from_db(user)
   end
 
   def perform(:set_activation_async, user, status), do: set_activation(user, status)

From 01c2d2a29670d8b3a4acee06c5f91b52e371fd00 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 29 Jun 2021 22:53:33 -0500
Subject: [PATCH 333/339] Also purge the user in User.perform/2

---
 lib/pleroma/user.ex | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 406a7f5f9..f3cf3c69b 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1724,6 +1724,7 @@ def delete(users) when is_list(users) do
   end
 
   def delete(%User{} = user) do
+    # Purge the user immediately
     purge(user)
     BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
   end
@@ -1749,6 +1750,9 @@ def perform(:force_password_reset, user), do: force_password_reset(user)
 
   @spec perform(atom(), User.t()) :: {:ok, User.t()}
   def perform(:delete, %User{} = user) do
+    # Purge the user again, in case perform/2 is called directly
+    purge(user)
+
     # Remove all relationships
     user
     |> get_followers()

From a7929c4d89a07a7f577e7cde5638bde8b1cb586a Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Tue, 29 Jun 2021 23:56:19 -0500
Subject: [PATCH 334/339] Deletions: preserve account status fields during
 purge, fix checks

---
 lib/pleroma/user.ex        | 22 ++++++++++++----------
 test/pleroma/user_test.exs |  4 ++--
 2 files changed, 14 insertions(+), 12 deletions(-)

diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index f3cf3c69b..5d8b936aa 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1692,9 +1692,7 @@ def purge_user_changeset(user) do
       follower_count: 0,
       following_count: 0,
       is_locked: false,
-      is_confirmed: true,
       password_reset_pending: false,
-      is_approved: true,
       registration_reason: nil,
       confirmation_token: nil,
       domain_blocks: [],
@@ -1710,9 +1708,15 @@ def purge_user_changeset(user) do
       raw_fields: [],
       is_discoverable: false,
       also_known_as: []
+      # id: preserved
+      # ap_id: preserved
+      # nickname: preserved
     })
   end
 
+  # Purge doesn't delete the user from the database.
+  # It just nulls all its fields and deactivates it.
+  # See `User.purge_user_changeset/1` above.
   def purge(%User{} = user) do
     user
     |> purge_user_changeset()
@@ -1729,20 +1733,18 @@ def delete(%User{} = user) do
     BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
   end
 
+  # *Actually* delete the user from the DB
   defp delete_from_db(%User{} = user) do
     invalidate_cache(user)
     Repo.delete(user)
   end
 
-  defp maybe_delete_from_db(%User{local: true} = user) do
-    status = account_status(user)
+  # If the user never finalized their account, it's safe to delete them.
+  defp maybe_delete_from_db(%User{local: true, is_confirmed: false} = user),
+    do: delete_from_db(user)
 
-    if status in [:confirmation_pending, :approval_pending] do
-      delete_from_db(user)
-    else
-      {:ok, user}
-    end
-  end
+  defp maybe_delete_from_db(%User{local: true, is_approved: false} = user),
+    do: delete_from_db(user)
 
   defp maybe_delete_from_db(user), do: {:ok, user}
 
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index 529f837e8..60bc58a48 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -1663,9 +1663,9 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do
              follower_count: 0,
              following_count: 0,
              is_locked: false,
-             is_confirmed: true,
+             is_confirmed: false,
              password_reset_pending: false,
-             is_approved: true,
+             is_approved: false,
              registration_reason: nil,
              confirmation_token: nil,
              domain_blocks: [],

From 43800d83f4fc3b251cdd93c28dab2df7297021b3 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Wed, 30 Jun 2021 01:14:34 -0500
Subject: [PATCH 335/339] Deletions: allow deactivated users to be deleted

---
 lib/pleroma/web/activity_pub/activity_pub.ex         |  9 ++++++---
 .../object_validators/delete_validator.ex            | 12 +++++++++++-
 .../activity_pub/object_validators/undo_validator.ex | 12 +++++++++++-
 test/pleroma/user_test.exs                           |  8 ++++----
 4 files changed, 32 insertions(+), 9 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 5b45e2ca1..787b5884f 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -52,15 +52,18 @@ defp get_recipients(data) do
     {recipients, to, cc}
   end
 
-  defp check_actor_is_active(nil), do: true
+  defp check_actor_can_insert(%{"type" => "Delete"}), do: true
+  defp check_actor_can_insert(%{"type" => "Undo"}), do: true
 
-  defp check_actor_is_active(actor) when is_binary(actor) do
+  defp check_actor_can_insert(%{"actor" => actor}) when is_binary(actor) do
     case User.get_cached_by_ap_id(actor) do
       %User{is_active: true} -> true
       _ -> false
     end
   end
 
+  defp check_actor_can_insert(_), do: true
+
   defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(content) do
     limit = Config.get([:instance, :remote_limit])
     String.length(content) <= limit
@@ -116,7 +119,7 @@ def persist(object, meta) do
   def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do
     with nil <- Activity.normalize(map),
          map <- lazy_put_activity_defaults(map, fake),
-         {_, true} <- {:actor_check, bypass_actor_check || check_actor_is_active(map["actor"])},
+         {_, true} <- {:actor_check, bypass_actor_check || check_actor_can_insert(map)},
          {_, true} <- {:remote_limit_pass, check_remote_limit(map)},
          {:ok, map} <- MRF.filter(map),
          {recipients, _, _} = get_recipients(map),
diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
index fc1a79a72..750ea0f7f 100644
--- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
 
   alias Pleroma.Activity
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
+  alias Pleroma.User
 
   import Ecto.Changeset
   import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -57,7 +58,7 @@ def validate_data(cng) do
     cng
     |> validate_required([:id, :type, :actor, :to, :cc, :object])
     |> validate_inclusion(:type, ["Delete"])
-    |> validate_actor_presence()
+    |> validate_delete_actor(:actor)
     |> validate_modification_rights()
     |> validate_object_or_user_presence(allowed_types: @deletable_types)
     |> add_deleted_activity_id()
@@ -72,4 +73,13 @@ def cast_and_validate(data) do
     |> cast_data
     |> validate_data
   end
+
+  defp validate_delete_actor(cng, field_name) do
+    validate_change(cng, field_name, fn field_name, actor ->
+      case User.get_cached_by_ap_id(actor) do
+        %User{} -> []
+        _ -> [{field_name, "can't find user"}]
+      end
+    end)
+  end
 end
diff --git a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
index 783a79ddb..ab29f9820 100644
--- a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
 
   alias Pleroma.Activity
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
+  alias Pleroma.User
 
   import Ecto.Changeset
   import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -42,7 +43,7 @@ def validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Undo"])
     |> validate_required([:id, :type, :object, :actor, :to, :cc])
-    |> validate_actor_presence()
+    |> validate_undo_actor(:actor)
     |> validate_object_presence()
     |> validate_undo_rights()
   end
@@ -59,4 +60,13 @@ def validate_undo_rights(cng) do
       _ -> cng
     end
   end
+
+  defp validate_undo_actor(cng, field_name) do
+    validate_change(cng, field_name, fn field_name, actor ->
+      case User.get_cached_by_ap_id(actor) do
+        %User{} -> []
+        _ -> [{field_name, "can't find user"}]
+      end
+    end)
+  end
 end
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index 60bc58a48..181990e4b 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -1621,9 +1621,9 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do
         follower_count: 9,
         following_count: 9001,
         is_locked: true,
-        is_confirmed: false,
+        is_confirmed: true,
         password_reset_pending: true,
-        is_approved: false,
+        is_approved: true,
         registration_reason: "ahhhhh",
         confirmation_token: "qqqq",
         domain_blocks: ["lain.com"],
@@ -1663,9 +1663,9 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do
              follower_count: 0,
              following_count: 0,
              is_locked: false,
-             is_confirmed: false,
+             is_confirmed: true,
              password_reset_pending: false,
-             is_approved: false,
+             is_approved: true,
              registration_reason: nil,
              confirmation_token: nil,
              domain_blocks: [],

From beb1c98ab5e0848127a4490180364552f6fcdbf5 Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Wed, 30 Jun 2021 01:48:17 -0500
Subject: [PATCH 336/339] Deletions: don't purge keys so Delete/Undo activities
 can be signed

---
 lib/pleroma/user.ex        | 2 --
 test/pleroma/user_test.exs | 4 ++--
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 5d8b936aa..de3b8ca3b 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1680,8 +1680,6 @@ def purge_user_changeset(user) do
       email: nil,
       name: nil,
       password_hash: nil,
-      keys: nil,
-      public_key: nil,
       avatar: %{},
       tags: [],
       last_refreshed_at: nil,
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index 181990e4b..ec0aaa9eb 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -1651,8 +1651,8 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do
              email: nil,
              name: nil,
              password_hash: nil,
-             keys: nil,
-             public_key: nil,
+             keys: "RSA begin buplic key",
+             public_key: "--PRIVATE KEYE--",
              avatar: %{},
              tags: [],
              last_refreshed_at: nil,

From 310ef6b70d9ca18d857f43677d857d09d91ffe0e Mon Sep 17 00:00:00 2001
From: Alex Gleason <alex@alexgleason.me>
Date: Wed, 30 Jun 2021 12:25:20 -0500
Subject: [PATCH 337/339] Deletions: change User.purge/1 to defp, add CHANGELOG
 entry

---
 CHANGELOG.md        | 2 ++
 lib/pleroma/user.ex | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 52d92c6d2..330802b29 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Fixed
 - Don't crash so hard when email settings are invalid.
 - Checking activated Upload Filters for required commands.
+- Remote users can no longer reappear after being deleted.
+- Deactivated users may now be deleted.
 - Mix task `pleroma.database prune_objects`
 
 ### Removed
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index f5b12abad..62506f37a 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1730,7 +1730,7 @@ def purge_user_changeset(user) do
   # Purge doesn't delete the user from the database.
   # It just nulls all its fields and deactivates it.
   # See `User.purge_user_changeset/1` above.
-  def purge(%User{} = user) do
+  defp purge(%User{} = user) do
     user
     |> purge_user_changeset()
     |> update_and_set_cache()

From 64d009693e35039025b0ff1cc536206054c2b918 Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@feld.me>
Date: Thu, 8 Jul 2021 12:33:17 -0500
Subject: [PATCH 338/339] Update Linkify to fix crash on posts with a URL we
 failed to parse correctly

---
 CHANGELOG.md | 1 +
 mix.exs      | 2 +-
 mix.lock     | 2 +-
 3 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 330802b29..9854eb531 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,6 +29,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Remote users can no longer reappear after being deleted.
 - Deactivated users may now be deleted.
 - Mix task `pleroma.database prune_objects`
+- Linkify: Parsing crash with URLs ending in unbalanced closed paren, no path separator, and no query parameters
 
 ### Removed
 - **Breaking**: Remove deprecated `/api/qvitter/statuses/notifications/read` (replaced by `/api/v1/pleroma/notifications/read`)
diff --git a/mix.exs b/mix.exs
index e4b160971..1a7aac6a4 100644
--- a/mix.exs
+++ b/mix.exs
@@ -157,7 +157,7 @@ defp deps do
       {:floki, "~> 0.27"},
       {:timex, "~> 3.6"},
       {:ueberauth, "~> 0.4"},
-      {:linkify, "~> 0.5.0"},
+      {:linkify, "~> 0.5.1"},
       {:http_signatures, "~> 0.1.0"},
       {:telemetry, "~> 0.3"},
       {:poolboy, "~> 1.5"},
diff --git a/mix.lock b/mix.lock
index 65a225504..b78ae0bc9 100644
--- a/mix.lock
+++ b/mix.lock
@@ -67,7 +67,7 @@
   "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.0", "e0ea8de73ff44742d6a889721221f4c4eccaad5284957ee9832ffeb347602d54", [:mix], [], "hexpm", "4ccd958350aee7c51c89e21f05b15d30596ebbba707e051d21766be1809df2d7"},
+  "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"},

From 6dc78f5f6f8c607c90246ff30520aeb2f84634df Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Fri, 18 Sep 2020 14:22:27 +0200
Subject: [PATCH 339/339] AP C2S: Remove restrictions and make it go through
 pipeline

---
 CHANGELOG.md                                  |   1 +
 lib/pleroma/activity.ex                       |   3 +-
 .../activity_pub/activity_pub_controller.ex   | 104 +++++++++---------
 .../web/activity_pub/object_validator.ex      |   2 +
 .../activity_pub_controller_test.exs          |  31 +++---
 5 files changed, 77 insertions(+), 64 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9854eb531..036b9e775 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - HTTPSecurityPlug now sends a response header to opt out of Google's FLoC (Federated Learning of Cohorts) targeted advertising.
 - Email address is now returned if requesting user is the owner of the user account so it can be exposed in client and FE user settings UIs.
 - Improved Twittercard and OpenGraph meta tag generation including thumbnails and image dimension metadata when available.
+- ActivityPub Client-to-Server(C2S): Limitation on the type of Activity/Object are lifted as they are now passed through ObjectValidators
 
 ### Added
 
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index 7e36c1b53..6a991c48e 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -292,7 +292,8 @@ def get_in_reply_to_activity(%Activity{} = activity) do
     get_in_reply_to_activity_from_object(Object.normalize(activity, fetch: false))
   end
 
-  def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"])
+  def normalize(%Activity{data: %{"id" => ap_id}}), do: get_by_ap_id_with_object(ap_id)
+  def normalize(%{"id" => ap_id}), do: get_by_ap_id_with_object(ap_id)
   def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id)
   def normalize(_), do: nil
 
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 5aa3b281a..57ac40b42 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -11,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
   alias Pleroma.Object.Fetcher
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
-  alias Pleroma.Web.ActivityPub.Builder
   alias Pleroma.Web.ActivityPub.InternalFetchActor
   alias Pleroma.Web.ActivityPub.ObjectView
   alias Pleroma.Web.ActivityPub.Pipeline
@@ -403,83 +402,90 @@ def read_inbox(%{assigns: %{user: %User{nickname: as_nickname}}} = conn, %{
     |> json(err)
   end
 
-  defp handle_user_activity(
-         %User{} = user,
-         %{"type" => "Create", "object" => %{"type" => "Note"} = object} = params
-       ) do
-    content = if is_binary(object["content"]), do: object["content"], else: ""
-    name = if is_binary(object["name"]), do: object["name"], else: ""
-    summary = if is_binary(object["summary"]), do: object["summary"], else: ""
-    length = String.length(content <> name <> summary)
+  defp fix_user_message(%User{ap_id: actor}, %{"type" => "Create", "object" => object} = activity)
+       when is_map(object) do
+    length =
+      [object["content"], object["summary"], object["name"]]
+      |> Enum.filter(&is_binary(&1))
+      |> Enum.join("")
+      |> String.length()
 
-    if length > Pleroma.Config.get([:instance, :limit]) do
-      {:error, dgettext("errors", "Note is over the character limit")}
-    else
+    limit = Pleroma.Config.get([:instance, :limit])
+
+    if length < limit do
       object =
         object
-        |> Map.merge(Map.take(params, ["to", "cc"]))
-        |> Map.put("attributedTo", user.ap_id)
-        |> Transmogrifier.fix_object()
+        |> Transmogrifier.strip_internal_fields()
+        |> Map.put("attributedTo", actor)
+        |> Map.put("actor", actor)
+        |> Map.put("id", Utils.generate_object_id())
 
-      ActivityPub.create(%{
-        to: params["to"],
-        actor: user,
-        context: object["context"],
-        object: object,
-        additional: Map.take(params, ["cc"])
-      })
-    end
-  end
-
-  defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do
-    with %Object{} = object <- Object.normalize(params["object"], fetch: false),
-         true <- user.is_moderator || 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}
+      {:ok, Map.put(activity, "object", object)}
     else
-      _ -> {:error, dgettext("errors", "Can't delete object")}
+      {:error,
+       dgettext(
+         "errors",
+         "Character limit (%{limit} characters) exceeded, contains %{length} characters",
+         limit: limit,
+         length: length
+       )}
     end
   end
 
-  defp handle_user_activity(%User{} = user, %{"type" => "Like"} = params) do
-    with %Object{} = object <- Object.normalize(params["object"], fetch: false),
-         {_, {: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
+  defp fix_user_message(
+         %User{ap_id: actor} = user,
+         %{"type" => "Delete", "object" => object} = activity
+       ) do
+    with {_, %Object{data: object_data}} <- {:normalize, Object.normalize(object, fetch: false)},
+         {_, true} <- {:permission, user.is_moderator || actor == object_data["actor"]} do
       {:ok, activity}
     else
-      _ -> {:error, dgettext("errors", "Can't like object")}
+      {:normalize, _} ->
+        {:error, "No such object found"}
+
+      {:permission, _} ->
+        {:forbidden, "You can't delete this object"}
     end
   end
 
-  defp handle_user_activity(_, _) do
-    {:error, dgettext("errors", "Unhandled activity type")}
+  defp fix_user_message(%User{}, activity) do
+    {:ok, activity}
   end
 
   def update_outbox(
-        %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
+        %{assigns: %{user: %User{nickname: nickname, ap_id: actor} = user}} = conn,
         %{"nickname" => nickname} = params
       ) do
-    actor = user.ap_id
-
     params =
       params
-      |> Map.drop(["id"])
+      |> Map.drop(["nickname"])
+      |> Map.put("id", Utils.generate_activity_id())
       |> Map.put("actor", actor)
-      |> Transmogrifier.fix_addressing()
 
-    with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
+    with {:ok, params} <- fix_user_message(user, params),
+         {:ok, activity, _} <- Pipeline.common_pipeline(params, local: true),
+         %Activity{data: activity_data} <- Activity.normalize(activity) do
       conn
       |> put_status(:created)
-      |> put_resp_header("location", activity.data["id"])
-      |> json(activity.data)
+      |> put_resp_header("location", activity_data["id"])
+      |> json(activity_data)
     else
+      {:forbidden, message} ->
+        conn
+        |> put_status(:forbidden)
+        |> json(message)
+
       {:error, message} ->
         conn
         |> put_status(:bad_request)
         |> json(message)
+
+      e ->
+        Logger.warn(fn -> "AP C2S: #{inspect(e)}" end)
+
+        conn
+        |> put_status(:bad_request)
+        |> json("Bad Request")
     end
   end
 
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 248a12a36..50999539c 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -175,6 +175,8 @@ def validate(%{"type" => type} = object, meta) when type in ~w(Add Remove) do
     end
   end
 
+  def validate(o, m), do: {:error, {:validator_not_set, {o, m}}}
+
   def cast_and_apply(%{"type" => "ChatMessage"} = object) do
     ChatMessageValidator.cast_and_apply(object)
   end
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index c7039d1f8..50315e21f 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -1334,9 +1334,12 @@ test "It returns poll Answers when authenticated", %{conn: conn} do
         activity: %{
           "@context" => "https://www.w3.org/ns/activitystreams",
           "type" => "Create",
-          "object" => %{"type" => "Note", "content" => "AP C2S test"},
-          "to" => "https://www.w3.org/ns/activitystreams#Public",
-          "cc" => []
+          "object" => %{
+            "type" => "Note",
+            "content" => "AP C2S test",
+            "to" => "https://www.w3.org/ns/activitystreams#Public",
+            "cc" => []
+          }
         }
       ]
     end
@@ -1442,19 +1445,19 @@ test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
       user = User.get_cached_by_ap_id(note_activity.data["actor"])
 
       data = %{
-        type: "Delete",
-        object: %{
-          id: note_object.data["id"]
+        "type" => "Delete",
+        "object" => %{
+          "id" => note_object.data["id"]
         }
       }
 
-      conn =
+      result =
         conn
         |> assign(:user, user)
         |> put_req_header("content-type", "application/activity+json")
         |> post("/users/#{user.nickname}/outbox", data)
+        |> json_response(201)
 
-      result = json_response(conn, 201)
       assert Activity.get_by_ap_id(result["id"])
 
       assert object = Object.get_by_ap_id(note_object.data["id"])
@@ -1479,7 +1482,7 @@ test "it rejects delete activity of object from other actor", %{conn: conn} do
         |> put_req_header("content-type", "application/activity+json")
         |> post("/users/#{user.nickname}/outbox", data)
 
-      assert json_response(conn, 400)
+      assert json_response(conn, 403)
     end
 
     test "it increases like count when receiving a like action", %{conn: conn} do
@@ -1557,7 +1560,7 @@ test "Character limitation", %{conn: conn, activity: activity} do
         |> post("/users/#{user.nickname}/outbox", activity)
         |> json_response(400)
 
-      assert result == "Note is over the character limit"
+      assert result == "Character limit (5 characters) exceeded, contains 11 characters"
     end
   end
 
@@ -1934,10 +1937,10 @@ test "POST /api/ap/upload_media", %{conn: conn} do
         "object" => %{
           "type" => "Note",
           "content" => "AP C2S test, attachment",
-          "attachment" => [object]
-        },
-        "to" => "https://www.w3.org/ns/activitystreams#Public",
-        "cc" => []
+          "attachment" => [object],
+          "to" => "https://www.w3.org/ns/activitystreams#Public",
+          "cc" => []
+        }
       }
 
       activity_response =