From 9c7da2ef4834729bd6bff21063bf81c7be5f1635 Mon Sep 17 00:00:00 2001
From: Hakaba Hitoyo
Date: Sat, 26 May 2018 14:02:57 +0900
Subject: [PATCH 001/100] output repeats at ActivityPub outbox
---
lib/pleroma/web/activity_pub/views/user_view.ex | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index ffd76b529..011ae18a3 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -98,9 +98,6 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do
info = User.user_info(user)
params = %{
- "type" => ["Create", "Announce"],
- "actor_id" => user.ap_id,
- "whole_db" => true,
"limit" => "10"
}
@@ -111,10 +108,8 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do
params
end
- activities = ActivityPub.fetch_public_activities(params)
- min_id = Enum.at(activities, 0).id
-
- activities = Enum.reverse(activities)
+ activities = ActivityPub.fetch_public_activities(user, nil, params)
+ min_id = Enum.at(Enum.reverse(activities), 0).id
max_id = Enum.at(activities, 0).id
collection =
From e90b734f1c41c13e08b7461f5bfb42745ea7ba08 Mon Sep 17 00:00:00 2001
From: Hakaba Hitoyo
Date: Sat, 26 May 2018 14:10:12 +0900
Subject: [PATCH 002/100] debug
---
lib/pleroma/web/activity_pub/views/user_view.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 011ae18a3..719bd128b 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -108,7 +108,7 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do
params
end
- activities = ActivityPub.fetch_public_activities(user, nil, params)
+ activities = ActivityPub.fetch_user_activities(user, nil, params)
min_id = Enum.at(Enum.reverse(activities), 0).id
max_id = Enum.at(activities, 0).id
From f42ffbe9a855494c182c97f5eb641e800e562aa4 Mon Sep 17 00:00:00 2001
From: Henry Jameson
Date: Tue, 12 Jun 2018 14:52:54 +0300
Subject: [PATCH 003/100] Initial invites support + tests.
---
lib/pleroma/UserInviteToken.ex | 40 ++++++++++++
lib/pleroma/web/router.ex | 4 +-
lib/pleroma/web/twitter_api/twitter_api.ex | 36 +++++++---
...180612110515_create_user_invite_tokens.exs | 12 ++++
test/web/twitter_api/twitter_api_test.exs | 65 ++++++++++++++++++-
5 files changed, 143 insertions(+), 14 deletions(-)
create mode 100644 lib/pleroma/UserInviteToken.ex
create mode 100644 priv/repo/migrations/20180612110515_create_user_invite_tokens.exs
diff --git a/lib/pleroma/UserInviteToken.ex b/lib/pleroma/UserInviteToken.ex
new file mode 100644
index 000000000..48ee1019a
--- /dev/null
+++ b/lib/pleroma/UserInviteToken.ex
@@ -0,0 +1,40 @@
+defmodule Pleroma.UserInviteToken do
+ use Ecto.Schema
+
+ import Ecto.Changeset
+
+ alias Pleroma.{User, UserInviteToken, Repo}
+
+ schema "user_invite_tokens" do
+ field(:token, :string)
+ field(:used, :boolean, default: false)
+
+ timestamps()
+ end
+
+ def create_token do
+ token = :crypto.strong_rand_bytes(32) |> Base.url_encode64()
+
+ token = %UserInviteToken{
+ used: false,
+ token: token
+ }
+
+ Repo.insert(token)
+ end
+
+ def used_changeset(struct) do
+ struct
+ |> cast(%{}, [])
+ |> put_change(:used, true)
+ end
+
+ def mark_as_used(token) do
+ with %{used: false} = token <- Repo.get_by(UserInviteToken, %{token: token}),
+ {:ok, token} <- Repo.update(used_changeset(token)) do
+ {:ok, token}
+ else
+ _e -> {:error, token}
+ end
+ end
+end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index ee6a373d3..127bf4d9e 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -194,9 +194,7 @@ def user_fetcher(username) do
get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status)
get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation)
- if @registrations_open do
- post("/account/register", TwitterAPI.Controller, :register)
- end
+ post("/account/register", TwitterAPI.Controller, :register)
get("/search", TwitterAPI.Controller, :search)
get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline)
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index ccc6fe8e7..8608ee9ac 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -1,11 +1,13 @@
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
- alias Pleroma.{User, Activity, Repo, Object}
+ alias Pleroma.{UserInviteToken, User, Activity, Repo, Object}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.TwitterAPI.UserView
alias Pleroma.Web.{OStatus, CommonAPI}
import Ecto.Query
+ @instance Application.get_env(:pleroma, :instance)
@httpoison Application.get_env(:pleroma, :httpoison)
+ @registrations_open Keyword.get(@instance, :registrations_open)
def create_status(%User{} = user, %{"status" => _} = data) do
CommonAPI.post(user, data)
@@ -124,6 +126,8 @@ def upload(%Plug.Upload{} = file, format \\ "xml") do
end
def register_user(params) do
+ tokenString = params["token"]
+
params = %{
nickname: params["nickname"],
name: params["fullname"],
@@ -133,17 +137,29 @@ def register_user(params) do
password_confirmation: params["confirm"]
}
- changeset = User.register_changeset(%User{}, params)
+ # no need to query DB if registration is open
+ unless @registrations_open || is_nil(tokenString) do
+ token = Repo.get_by(UserInviteToken, %{token: tokenString})
+ end
- with {:ok, user} <- Repo.insert(changeset) do
- {:ok, user}
- else
- {:error, changeset} ->
- errors =
- Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
- |> Jason.encode!()
+ cond do
+ @registrations_open || !is_nil(token) && !token.used ->
+ changeset = User.register_changeset(%User{}, params)
- {:error, %{error: errors}}
+ with {:ok, user} <- Repo.insert(changeset) do
+ !@registrations_open && UserInviteToken.mark_as_used(token.token)
+ {:ok, user}
+ else
+ {:error, changeset} ->
+ errors =
+ Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
+ |> Jason.encode!()
+
+ {:error, %{error: errors}}
+ end
+
+ !@registrations_open && is_nil(token) -> {:error, "Invalid token"}
+ !@registrations_open && token.used -> {:error, "Expired token"}
end
end
diff --git a/priv/repo/migrations/20180612110515_create_user_invite_tokens.exs b/priv/repo/migrations/20180612110515_create_user_invite_tokens.exs
new file mode 100644
index 000000000..d0a1cf784
--- /dev/null
+++ b/priv/repo/migrations/20180612110515_create_user_invite_tokens.exs
@@ -0,0 +1,12 @@
+defmodule Pleroma.Repo.Migrations.CreateUserInviteTokens do
+ use Ecto.Migration
+
+ def change do
+ create table(:user_invite_tokens) do
+ add :token, :string
+ add :used, :boolean, default: false
+
+ timestamps()
+ end
+ end
+end
diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs
index edacb312d..ed9158bf5 100644
--- a/test/web/twitter_api/twitter_api_test.exs
+++ b/test/web/twitter_api/twitter_api_test.exs
@@ -2,7 +2,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
use Pleroma.DataCase
alias Pleroma.Builders.UserBuilder
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView}
- alias Pleroma.{Activity, User, Object, Repo}
+ alias Pleroma.{Activity, User, Object, Repo, UserInviteToken}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.TwitterAPI.ActivityView
@@ -246,6 +246,69 @@ test "it registers a new user and returns the user." do
UserView.render("show.json", %{user: fetched_user})
end
+ @moduletag skip: "needs 'registrations_open: false' in config"
+ test "it registers a new user via invite token and returns the user." do
+ {:ok, token} = UserInviteToken.create_token()
+
+ data = %{
+ "nickname" => "vinny",
+ "email" => "pasta@pizza.vs",
+ "fullname" => "Vinny Vinesauce",
+ "bio" => "streamer",
+ "password" => "hiptofbees",
+ "confirm" => "hiptofbees",
+ "token" => token.token
+ }
+
+ {:ok, user} = TwitterAPI.register_user(data)
+
+ fetched_user = Repo.get_by(User, nickname: "vinny")
+ token = Repo.get_by(UserInviteToken, token: token.token)
+
+ assert token.used == true
+ assert UserView.render("show.json", %{user: user}) ==
+ UserView.render("show.json", %{user: fetched_user})
+ end
+
+ @moduletag skip: "needs 'registrations_open: false' in config"
+ test "it returns an error if invalid token submitted" do
+ data = %{
+ "nickname" => "GrimReaper",
+ "email" => "death@reapers.afterlife",
+ "fullname" => "Reaper Grim",
+ "bio" => "Your time has come",
+ "password" => "scythe",
+ "confirm" => "scythe",
+ "token" => "DudeLetMeInImAFairy"
+ }
+
+ {:error, msg} = TwitterAPI.register_user(data)
+
+ assert msg == "Invalid token"
+ refute Repo.get_by(User, nickname: "GrimReaper")
+ end
+
+ @moduletag skip: "needs 'registrations_open: false' in config"
+ test "it returns an error if expired token submitted" do
+ {:ok, token} = UserInviteToken.create_token()
+ UserInviteToken.mark_as_used(token.token)
+
+ data = %{
+ "nickname" => "GrimReaper",
+ "email" => "death@reapers.afterlife",
+ "fullname" => "Reaper Grim",
+ "bio" => "Your time has come",
+ "password" => "scythe",
+ "confirm" => "scythe",
+ "token" => token.token
+ }
+
+ {:error, msg} = TwitterAPI.register_user(data)
+
+ assert msg == "Expired token"
+ refute Repo.get_by(User, nickname: "GrimReaper")
+ end
+
test "it returns the error on registration problems" do
data = %{
"nickname" => "lain",
From 9c1cf1befb9905282f6b8afcfee3cf3578f41431 Mon Sep 17 00:00:00 2001
From: Henry Jameson
Date: Tue, 12 Jun 2018 15:01:40 +0300
Subject: [PATCH 004/100] formatting
---
lib/pleroma/web/twitter_api/twitter_api.ex | 11 +++++++----
test/web/twitter_api/twitter_api_test.exs | 3 ++-
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index 8608ee9ac..ce0da3340 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -143,7 +143,7 @@ def register_user(params) do
end
cond do
- @registrations_open || !is_nil(token) && !token.used ->
+ @registrations_open || (!is_nil(token) && !token.used) ->
changeset = User.register_changeset(%User{}, params)
with {:ok, user} <- Repo.insert(changeset) do
@@ -155,11 +155,14 @@ def register_user(params) do
Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
|> Jason.encode!()
- {:error, %{error: errors}}
+ {:error, %{error: errors}}
end
- !@registrations_open && is_nil(token) -> {:error, "Invalid token"}
- !@registrations_open && token.used -> {:error, "Expired token"}
+ !@registrations_open && is_nil(token) ->
+ {:error, "Invalid token"}
+
+ !@registrations_open && token.used ->
+ {:error, "Expired token"}
end
end
diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs
index ed9158bf5..bdf2f2885 100644
--- a/test/web/twitter_api/twitter_api_test.exs
+++ b/test/web/twitter_api/twitter_api_test.exs
@@ -266,8 +266,9 @@ test "it registers a new user via invite token and returns the user." do
token = Repo.get_by(UserInviteToken, token: token.token)
assert token.used == true
+
assert UserView.render("show.json", %{user: user}) ==
- UserView.render("show.json", %{user: fetched_user})
+ UserView.render("show.json", %{user: fetched_user})
end
@moduletag skip: "needs 'registrations_open: false' in config"
From 0b1ca6a584219083e2d312abe2c1bdd8fab98e38 Mon Sep 17 00:00:00 2001
From: Henry Jameson
Date: Sun, 17 Jun 2018 14:30:07 +0300
Subject: [PATCH 005/100] Token-generating task
---
lib/mix/tasks/generate_invite_token.ex | 25 +++++++++++++++++++
...serInviteToken.ex => user_invite_token.ex} | 0
lib/pleroma/web/router.ex | 5 ++++
3 files changed, 30 insertions(+)
create mode 100644 lib/mix/tasks/generate_invite_token.ex
rename lib/pleroma/{UserInviteToken.ex => user_invite_token.ex} (100%)
diff --git a/lib/mix/tasks/generate_invite_token.ex b/lib/mix/tasks/generate_invite_token.ex
new file mode 100644
index 000000000..a5f41ef0e
--- /dev/null
+++ b/lib/mix/tasks/generate_invite_token.ex
@@ -0,0 +1,25 @@
+defmodule Mix.Tasks.GenerateInviteToken do
+ use Mix.Task
+
+ @shortdoc "Generate password reset link for user"
+ def run([]) do
+ Mix.Task.run("app.start")
+
+ with {:ok, token} <- Pleroma.UserInviteToken.create_token() do
+ IO.puts("Generated user invite token")
+
+ IO.puts(
+ "Url: #{
+ Pleroma.Web.Router.Helpers.redirect_url(
+ Pleroma.Web.Endpoint,
+ :registration_page,
+ token.token
+ )
+ }"
+ )
+ else
+ _ ->
+ IO.puts("Error creating token")
+ end
+ end
+end
diff --git a/lib/pleroma/UserInviteToken.ex b/lib/pleroma/user_invite_token.ex
similarity index 100%
rename from lib/pleroma/UserInviteToken.ex
rename to lib/pleroma/user_invite_token.ex
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 127bf4d9e..dcbf7f008 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -344,6 +344,7 @@ def user_fetcher(username) do
end
scope "/", Fallback do
+ get("/registration/:token", RedirectController, :registration_page)
get("/*path", RedirectController, :redirector)
end
end
@@ -358,4 +359,8 @@ def redirector(conn, _params) do
|> send_file(200, "priv/static/index.html")
end
end
+
+ def registration_page(conn, params) do
+ redirector(conn, params)
+ end
end
From 082920044abeadb9daf593d7e58d210634f8b4a5 Mon Sep 17 00:00:00 2001
From: Francis Dinh
Date: Thu, 21 Jun 2018 14:04:12 -0400
Subject: [PATCH 006/100] Normalize file extension for uploaded files
---
lib/pleroma/upload.ex | 26 +++++++++++++-------------
test/fixtures/test.txt | 1 +
test/upload_test.exs | 26 ++++++++++++++++++++++++++
3 files changed, 40 insertions(+), 13 deletions(-)
create mode 100644 test/fixtures/test.txt
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index 43df0d418..6793c4671 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -110,20 +110,20 @@ defp get_name(file, uuid, type, should_dedupe) do
if should_dedupe do
create_name(uuid, List.last(String.split(file.filename, ".")), type)
else
- unless String.contains?(file.filename, ".") do
- case type do
- "image/png" -> file.filename <> ".png"
- "image/jpeg" -> file.filename <> ".jpg"
- "image/gif" -> file.filename <> ".gif"
- "video/webm" -> file.filename <> ".webm"
- "video/mp4" -> file.filename <> ".mp4"
- "audio/mpeg" -> file.filename <> ".mp3"
- "audio/ogg" -> file.filename <> ".ogg"
- "audio/wav" -> file.filename <> ".wav"
- _ -> file.filename
+ parts = String.split(file.filename, ".")
+
+ new_filename =
+ if length(parts) > 1 do
+ Enum.drop(parts, -1) |> Enum.join(".")
+ else
+ Enum.join(parts)
end
- else
- file.filename
+
+ case type do
+ "application/octet-stream" -> file.filename
+ "audio/mpeg" -> new_filename <> ".mp3"
+ "image/jpeg" -> new_filename <> ".jpg"
+ _ -> Enum.join([new_filename, String.split(type, "/") |> List.last()], ".")
end
end
end
diff --git a/test/fixtures/test.txt b/test/fixtures/test.txt
new file mode 100644
index 000000000..e9ea42a12
--- /dev/null
+++ b/test/fixtures/test.txt
@@ -0,0 +1 @@
+this is a text file
diff --git a/test/upload_test.exs b/test/upload_test.exs
index 09aa5e068..d273ea5f6 100644
--- a/test/upload_test.exs
+++ b/test/upload_test.exs
@@ -56,5 +56,31 @@ test "adds missing extension" do
data = Upload.store(file, false)
assert data["name"] == "an [image.jpg"
end
+
+ test "fixes incorrect file extension" do
+ File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image_tmp.jpg"),
+ filename: "an [image.blah"
+ }
+
+ data = Upload.store(file, false)
+ assert data["name"] == "an [image.jpg"
+ end
+
+ test "don't modify filename of an unknown type" do
+ File.cp("test/fixtures/test.txt", "test/fixtures/test_tmp.txt")
+
+ file = %Plug.Upload{
+ content_type: "text/plain",
+ path: Path.absname("test/fixtures/test_tmp.txt"),
+ filename: "test.txt"
+ }
+
+ data = Upload.store(file, false)
+ assert data["name"] == "test.txt"
+ end
end
end
From cb21bf5fc225899ad6cf87f5ab0253506cf0531e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tristan=20Mah=C3=A9?=
Date: Tue, 26 Jun 2018 13:45:47 -0700
Subject: [PATCH 007/100] filter exif data #187
---
config/config.exs | 4 +++-
lib/pleroma/upload.ex | 8 ++++++++
mix.exs | 3 ++-
3 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index cf6cbaa9d..fd9662aea 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -10,7 +10,9 @@
config :pleroma, Pleroma.Repo, types: Pleroma.PostgresTypes
-config :pleroma, Pleroma.Upload, uploads: "uploads"
+config :pleroma, Pleroma.Upload,
+ uploads: "uploads",
+ strip_exif: false
# Configures the endpoint
config :pleroma, Pleroma.Web.Endpoint,
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index 43df0d418..dee281f5b 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -80,6 +80,14 @@ def store(%{"img" => "data:image/" <> image_data}, should_dedupe) do
}
end
+ def strip_exif_data(file) do
+ settings = Application.get_env(:pleroma, Pleroma.Upload)
+ @do_strip = Keyword.fetch!(settings, :strip_exif)
+ if @do_strip == true do
+ Mogrify.open(file) |> Mogrify.custom("strip") |> Mogrify.save(in_place: true)
+ end
+ end
+
def upload_path do
settings = Application.get_env(:pleroma, Pleroma.Upload)
Keyword.fetch!(settings, :uploads)
diff --git a/mix.exs b/mix.exs
index 281687294..cc279a7f9 100644
--- a/mix.exs
+++ b/mix.exs
@@ -47,7 +47,8 @@ defp deps do
{:jason, "~> 1.0"},
{:ex_machina, "~> 2.0", only: :test},
{:credo, "~> 0.7", only: [:dev, :test]},
- {:mock, "~> 0.3.0", only: :test}
+ {:mock, "~> 0.3.0", only: :test},
+ {:mogrify, "~> 0.6.1"}
]
end
From c67cf8e9af3ab9b52f34387a686a68ee4e1554b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tristan=20Mah=C3=A9?=
Date: Tue, 26 Jun 2018 13:49:57 -0700
Subject: [PATCH 008/100] format...
---
config/config.exs | 2 +-
lib/pleroma/upload.ex | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index fd9662aea..33b7f098e 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -10,7 +10,7 @@
config :pleroma, Pleroma.Repo, types: Pleroma.PostgresTypes
-config :pleroma, Pleroma.Upload,
+config :pleroma, Pleroma.Upload,
uploads: "uploads",
strip_exif: false
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index dee281f5b..e412e43fa 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -83,8 +83,9 @@ def store(%{"img" => "data:image/" <> image_data}, should_dedupe) do
def strip_exif_data(file) do
settings = Application.get_env(:pleroma, Pleroma.Upload)
@do_strip = Keyword.fetch!(settings, :strip_exif)
+
if @do_strip == true do
- Mogrify.open(file) |> Mogrify.custom("strip") |> Mogrify.save(in_place: true)
+ Mogrify.open(file) |> Mogrify.custom("strip") |> Mogrify.save(in_place: true)
end
end
From d8d43f1173aaea677a74aee6315d1195d59197e3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tristan=20Mah=C3=A9?=
Date: Tue, 26 Jun 2018 14:03:23 -0700
Subject: [PATCH 009/100] do the filtering
---
lib/pleroma/upload.ex | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index e412e43fa..1640c1f9c 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -18,6 +18,8 @@ def store(%Plug.Upload{} = file, should_dedupe) do
File.cp!(file.path, result_file)
end
+ strip_exif_data(content_type, file.path)
+
%{
"type" => "Image",
"url" => [
@@ -67,6 +69,8 @@ def store(%{"img" => "data:image/" <> image_data}, should_dedupe) do
File.rename(uuidpath, result_file)
end
+ strip_exif_data(content_type, uuidpath)
+
%{
"type" => "Image",
"url" => [
@@ -80,11 +84,12 @@ def store(%{"img" => "data:image/" <> image_data}, should_dedupe) do
}
end
- def strip_exif_data(file) do
+ def strip_exif_data(content_type, file) do
settings = Application.get_env(:pleroma, Pleroma.Upload)
@do_strip = Keyword.fetch!(settings, :strip_exif)
+ [filetype, ext] = String.split(content_type, "/")
- if @do_strip == true do
+ if filetype == "image" and @do_strip == true do
Mogrify.open(file) |> Mogrify.custom("strip") |> Mogrify.save(in_place: true)
end
end
From 2df0fd1462c7fed0fe80bafecdfc72309025c943 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tristan=20Mah=C3=A9?=
Date: Tue, 26 Jun 2018 14:16:28 -0700
Subject: [PATCH 010/100] mogrify mix.lock
---
mix.lock | 1 +
1 file changed, 1 insertion(+)
diff --git a/mix.lock b/mix.lock
index 2a826111c..9ae8fe0ac 100644
--- a/mix.lock
+++ b/mix.lock
@@ -25,6 +25,7 @@
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
"mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
"mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
+ "mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"},
"phoenix": {:hex, :phoenix, "1.3.2", "2a00d751f51670ea6bc3f2ba4e6eb27ecb8a2c71e7978d9cd3e5de5ccf7378bd", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
From ca63585a329f0afa02f7d539328f5fb3485d2a29 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tristan=20Mah=C3=A9?=
Date: Tue, 26 Jun 2018 14:35:35 -0700
Subject: [PATCH 011/100] maybe I should learn proper elixir ;D
---
lib/pleroma/upload.ex | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index 1640c1f9c..45fac9060 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -86,10 +86,10 @@ def store(%{"img" => "data:image/" <> image_data}, should_dedupe) do
def strip_exif_data(content_type, file) do
settings = Application.get_env(:pleroma, Pleroma.Upload)
- @do_strip = Keyword.fetch!(settings, :strip_exif)
+ do_strip = Keyword.fetch!(settings, :strip_exif)
[filetype, ext] = String.split(content_type, "/")
- if filetype == "image" and @do_strip == true do
+ if filetype == "image" and do_strip == true do
Mogrify.open(file) |> Mogrify.custom("strip") |> Mogrify.save(in_place: true)
end
end
From dc8ace29d12e8022ef7381d273724d7e5e7e3a19 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tristan=20Mah=C3=A9?=
Date: Tue, 26 Jun 2018 15:09:45 -0700
Subject: [PATCH 012/100] use the correct end file
---
lib/pleroma/upload.ex | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index 45fac9060..dd2bbab9f 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -18,7 +18,7 @@ def store(%Plug.Upload{} = file, should_dedupe) do
File.cp!(file.path, result_file)
end
- strip_exif_data(content_type, file.path)
+ strip_exif_data(content_type, result_file)
%{
"type" => "Image",
@@ -69,7 +69,7 @@ def store(%{"img" => "data:image/" <> image_data}, should_dedupe) do
File.rename(uuidpath, result_file)
end
- strip_exif_data(content_type, uuidpath)
+ strip_exif_data(content_type, result_file)
%{
"type" => "Image",
From a2009432408dd0e93c3eb9506ff37da788b50895 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Mon, 18 Jun 2018 05:23:54 +0000
Subject: [PATCH 013/100] object: add helper functions to handle various forms
of a given object and return a normalized one
---
lib/pleroma/object.ex | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index ff2af4a6f..1bcff5a7b 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -27,6 +27,10 @@ def get_by_ap_id(ap_id) do
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
end
+ def normalize(obj) when is_map(obj), do: Object.get_by_ap_id(obj["id"])
+ def normalize(ap_id) when is_binary(ap_id), do: Object.get_by_ap_id(ap_id)
+ def normalize(_), do: nil
+
def get_cached_by_ap_id(ap_id) do
if Mix.env() == :test do
get_by_ap_id(ap_id)
From b036a19c213c2b3cc08c64a0d887d36b9386609d Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Mon, 18 Jun 2018 20:54:59 +0000
Subject: [PATCH 014/100] activity: add normalize() to find a complete activity
given either URI or partial structure
---
lib/pleroma/activity.ex | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index dd6805125..bed96861f 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -78,4 +78,8 @@ def get_create_activity_by_object_ap_id(ap_id) when is_binary(ap_id) do
end
def get_create_activity_by_object_ap_id(_), do: nil
+
+ def normalize(obj) when is_map(obj), do: Activity.get_by_ap_id(obj["id"])
+ def normalize(ap_id) when is_binary(ap_id), do: Activity.get_by_ap_id(ap_id)
+ def normalize(_), do: nil
end
From 47189531c55ac0c3f671298e6efa8c020ce5853a Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Mon, 18 Jun 2018 20:57:38 +0000
Subject: [PATCH 015/100] user: use Object.normalize() instead of
Object.get_by_ap_id() directly.
---
lib/pleroma/user.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 94f16c3c0..df22d29a8 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -607,7 +607,7 @@ def delete(%User{} = user) do
|> Enum.each(fn activity ->
case activity.data["type"] do
"Create" ->
- ActivityPub.delete(Object.get_by_ap_id(activity.data["object"]["id"]))
+ ActivityPub.delete(Object.normalize(activity.data["object"]))
# TODO: Do something with likes, follows, repeats.
_ ->
From fb04fecfb4791639369414a685be79bdd9d03309 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Mon, 18 Jun 2018 20:58:09 +0000
Subject: [PATCH 016/100] streamer: use Object.normalize() instead of
Object.get_by_ap_id() directly.
---
lib/pleroma/web/streamer.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex
index ce38f3cc3..c61bad830 100644
--- a/lib/pleroma/web/streamer.ex
+++ b/lib/pleroma/web/streamer.ex
@@ -158,7 +158,7 @@ def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = ite
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
blocks = user.info["blocks"] || []
- parent = Object.get_by_ap_id(item.data["object"])
+ parent = Object.normalize(item.data["object"])
unless is_nil(parent) or item.actor in blocks or parent.data["actor"] in blocks do
send(socket.transport_pid, {:text, represent_update(item, user)})
From 15d624e07797719e9ec1cf112b8beaa08dad2562 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Mon, 18 Jun 2018 21:01:33 +0000
Subject: [PATCH 017/100] activitypub: use Object.normalize() instead of
Object.get_by_ap_id() directly.
---
lib/pleroma/web/activity_pub/activity_pub.ex | 6 +++---
lib/pleroma/web/activity_pub/transmogrifier.ex | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 195679fad..93219d76a 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -670,7 +670,7 @@ def fetch_object_from_id(id) do
recv_timeout: 20000
),
{:ok, data} <- Jason.decode(body),
- nil <- Object.get_by_ap_id(data["id"]),
+ nil <- Object.normalize(data),
params <- %{
"type" => "Create",
"to" => data["to"],
@@ -679,7 +679,7 @@ def fetch_object_from_id(id) do
"object" => data
},
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
- {:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
+ {:ok, Object.normalize(activity.data["object"])}
else
object = %Object{} ->
{:ok, object}
@@ -688,7 +688,7 @@ def fetch_object_from_id(id) do
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
case OStatus.fetch_activity_from_url(id) do
- {:ok, [activity | _]} -> {:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
+ {:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"])}
e -> e
end
end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 30cd70fb6..8b5c85f42 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -412,7 +412,7 @@ def handle_incoming(
def handle_incoming(_), do: :error
def get_obj_helper(id) do
- if object = Object.get_by_ap_id(id), do: {:ok, object}, else: nil
+ if object = Object.normalize(id), do: {:ok, object}, else: nil
end
def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) do
From 49da04c5094e698dca4dbf5788127177702f288b Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Mon, 18 Jun 2018 21:08:12 +0000
Subject: [PATCH 018/100] common api: use Object.normalize() instead of
Object.get_by_ap_id() directly.
---
lib/pleroma/web/common_api/common_api.ex | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 8845419c2..3f18a68e8 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -7,7 +7,7 @@ defmodule Pleroma.Web.CommonAPI do
def delete(activity_id, user) do
with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, activity_id),
- %Object{} = object <- Object.get_by_ap_id(object_id),
+ %Object{} = object <- Object.normalize(object_id),
true <- user.info["is_moderator"] || user.ap_id == object.data["actor"],
{:ok, delete} <- ActivityPub.delete(object) do
{:ok, delete}
@@ -16,7 +16,7 @@ def delete(activity_id, user) do
def repeat(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
- object <- Object.get_by_ap_id(activity.data["object"]["id"]) do
+ object <- Object.normalize(activity.data["object"]["id"]) do
ActivityPub.announce(user, object)
else
_ ->
@@ -26,7 +26,7 @@ def repeat(id_or_ap_id, user) do
def unrepeat(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
- object <- Object.get_by_ap_id(activity.data["object"]["id"]) do
+ object <- Object.normalize(activity.data["object"]["id"]) do
ActivityPub.unannounce(user, object)
else
_ ->
@@ -37,7 +37,7 @@ def unrepeat(id_or_ap_id, user) do
def favorite(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
false <- activity.data["actor"] == user.ap_id,
- object <- Object.get_by_ap_id(activity.data["object"]["id"]) do
+ object <- Object.normalize(activity.data["object"]["id"]) do
ActivityPub.like(user, object)
else
_ ->
@@ -48,7 +48,7 @@ def favorite(id_or_ap_id, user) do
def unfavorite(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
false <- activity.data["actor"] == user.ap_id,
- object <- Object.get_by_ap_id(activity.data["object"]["id"]) do
+ object <- Object.normalize(activity.data["object"]["id"]) do
ActivityPub.unlike(user, object)
else
_ ->
From bc05548370862a2a5daba20d4577b56fabc169ea Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Mon, 18 Jun 2018 21:08:37 +0000
Subject: [PATCH 019/100] ostatus: use Object.normalize() instead of
Object.get_by_ap_id() directly.
---
lib/pleroma/web/ostatus/handlers/delete_handler.ex | 2 +-
lib/pleroma/web/ostatus/ostatus.ex | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/lib/pleroma/web/ostatus/handlers/delete_handler.ex b/lib/pleroma/web/ostatus/handlers/delete_handler.ex
index 4f3016b65..6330d7f64 100644
--- a/lib/pleroma/web/ostatus/handlers/delete_handler.ex
+++ b/lib/pleroma/web/ostatus/handlers/delete_handler.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.OStatus.DeleteHandler do
def handle_delete(entry, _doc \\ nil) do
with id <- XML.string_from_xpath("//id", entry),
- object when not is_nil(object) <- Object.get_by_ap_id(id),
+ %Object{} = object <- Object.normalize(id),
{:ok, delete} <- ActivityPub.delete(object, false) do
delete
end
diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex
index f0ff0624f..916c894eb 100644
--- a/lib/pleroma/web/ostatus/ostatus.ex
+++ b/lib/pleroma/web/ostatus/ostatus.ex
@@ -89,7 +89,7 @@ def handle_incoming(xml_string) do
def make_share(entry, doc, retweeted_activity) do
with {:ok, actor} <- find_make_or_update_user(doc),
- %Object{} = object <- Object.get_by_ap_id(retweeted_activity.data["object"]["id"]),
+ %Object{} = object <- Object.normalize(retweeted_activity.data["object"]),
id when not is_nil(id) <- string_from_xpath("/entry/id", entry),
{:ok, activity, _object} = ActivityPub.announce(actor, object, id, false) do
{:ok, activity}
@@ -107,7 +107,7 @@ def handle_share(entry, doc) do
def make_favorite(entry, doc, favorited_activity) do
with {:ok, actor} <- find_make_or_update_user(doc),
- %Object{} = object <- Object.get_by_ap_id(favorited_activity.data["object"]["id"]),
+ %Object{} = object <- Object.normalize(favorited_activity.data["object"]),
id when not is_nil(id) <- string_from_xpath("/entry/id", entry),
{:ok, activity, _object} = ActivityPub.like(actor, object, id, false) do
{:ok, activity}
From 5b240c3b187260498cbebbd540872975fb30bafe Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Mon, 18 Jun 2018 21:20:18 +0000
Subject: [PATCH 020/100] federator: use Activity.normalize() instead of
directly using Activity.get_by_ap_id().
---
lib/pleroma/web/federator/federator.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex
index 8ca530031..ccefb0bdf 100644
--- a/lib/pleroma/web/federator/federator.ex
+++ b/lib/pleroma/web/federator/federator.ex
@@ -95,7 +95,7 @@ def handle(:incoming_ap_doc, params) do
params = Utils.normalize_params(params)
with {:ok, _user} <- ap_enabled_actor(params["actor"]),
- nil <- Activity.get_by_ap_id(params["id"]),
+ nil <- Activity.normalize(params["id"]),
{:ok, _activity} <- Transmogrifier.handle_incoming(params) do
else
%Activity{} ->
From 6f4ca7ddf7f70b219ef1223f6343707ff6910f90 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Mon, 18 Jun 2018 21:20:44 +0000
Subject: [PATCH 021/100] ostatus: use Activity.normalize() instead of directly
using Activity.get_by_ap_id().
---
lib/pleroma/web/ostatus/activity_representer.ex | 2 +-
lib/pleroma/web/ostatus/ostatus_controller.ex | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/pleroma/web/ostatus/activity_representer.ex b/lib/pleroma/web/ostatus/activity_representer.ex
index 4179d86c9..5d831459b 100644
--- a/lib/pleroma/web/ostatus/activity_representer.ex
+++ b/lib/pleroma/web/ostatus/activity_representer.ex
@@ -246,7 +246,7 @@ def to_simple_form(
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
mentions = (activity.recipients || []) |> get_mentions
- follow_activity = Activity.get_by_ap_id(follow_activity["id"])
+ follow_activity = Activity.normalize(follow_activity)
[
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex
index 2f72fdb16..00bffbd5d 100644
--- a/lib/pleroma/web/ostatus/ostatus_controller.ex
+++ b/lib/pleroma/web/ostatus/ostatus_controller.ex
@@ -107,7 +107,7 @@ def object(conn, %{"uuid" => uuid}) do
def activity(conn, %{"uuid" => uuid}) do
with id <- o_status_url(conn, :activity, uuid),
- {_, %Activity{} = activity} <- {:activity, Activity.get_by_ap_id(id)},
+ {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
{_, true} <- {:public?, ActivityPub.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case get_format(conn) do
From 7c63e70de1b92ab8119732fe92040dea7f545b3d Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Mon, 18 Jun 2018 21:21:03 +0000
Subject: [PATCH 022/100] activitypub: use Activity.normalize() in several
places instead of using Activity.get_by_ap_id() directly.
---
lib/pleroma/web/activity_pub/activity_pub.ex | 2 +-
lib/pleroma/web/activity_pub/transmogrifier.ex | 18 ++----------------
2 files changed, 3 insertions(+), 17 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 93219d76a..464832a1e 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -30,7 +30,7 @@ defp check_actor_is_active(actor) do
end
def insert(map, local \\ true) when is_map(map) do
- with nil <- Activity.get_by_ap_id(map["id"]),
+ with nil <- Activity.normalize(map),
map <- lazy_put_activity_defaults(map),
:ok <- check_actor_is_active(map["actor"]),
{:ok, map} <- MRF.filter(map),
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 8b5c85f42..9d7c64743 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -460,14 +460,7 @@ def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = obj
# Mastodon Accept/Reject requires a non-normalized object containing the actor URIs,
# because of course it does.
def prepare_outgoing(%{"type" => "Accept"} = data) do
- follow_activity_id =
- if is_binary(data["object"]) do
- data["object"]
- else
- data["object"]["id"]
- end
-
- with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do
+ with follow_activity <- Activity.normalize(data["object"]) do
object = %{
"actor" => follow_activity.actor,
"object" => follow_activity.data["object"],
@@ -485,14 +478,7 @@ def prepare_outgoing(%{"type" => "Accept"} = data) do
end
def prepare_outgoing(%{"type" => "Reject"} = data) do
- follow_activity_id =
- if is_binary(data["object"]) do
- data["object"]
- else
- data["object"]["id"]
- end
-
- with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do
+ with follow_activity <- Activity.normalize(data["object"]) do
object = %{
"actor" => follow_activity.actor,
"object" => follow_activity.data["object"],
From 4f6de34f4f40d408fb9200a5c4c908aec3ed9e91 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Sat, 23 Jun 2018 06:53:29 +0000
Subject: [PATCH 023/100] mastodon api: use info["default_scope"] if available
for post scope
---
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 9d3f526c9..09e6b0b59 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -873,7 +873,7 @@ def index(%{assigns: %{user: user}} = conn, _params) do
},
compose: %{
me: "#{user.id}",
- default_privacy: "public",
+ default_privacy: user.info["default_scope"] || "public",
default_sensitive: false
},
media_attachments: %{
From dcdf7b6686fe3732ac8f9a93570dfa8a1412783a Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Sat, 23 Jun 2018 06:55:32 +0000
Subject: [PATCH 024/100] twitter api: user view: show default message scope if
known
---
lib/pleroma/web/twitter_api/views/user_view.ex | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex
index 711008973..9c8460378 100644
--- a/lib/pleroma/web/twitter_api/views/user_view.ex
+++ b/lib/pleroma/web/twitter_api/views/user_view.ex
@@ -52,7 +52,8 @@ def render("user.json", %{user: user = %User{}} = assigns) do
"cover_photo" => User.banner_url(user) |> MediaProxy.url(),
"background_image" => image_url(user.info["background"]) |> MediaProxy.url(),
"is_local" => user.local,
- "locked" => !!user.info["locked"]
+ "locked" => !!user.info["locked"],
+ "default_scope" => user.info["default_scope"] || "public"
}
if assigns[:token] do
From 2f14996d9afedbc4dbd75a518cf191636f926f74 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Sat, 23 Jun 2018 07:02:49 +0000
Subject: [PATCH 025/100] twitter api: allow setting default_scope
---
.../web/twitter_api/twitter_api_controller.ex | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
index 8f5b3c786..65e67396b 100644
--- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
@@ -431,6 +431,19 @@ def update_profile(%{assigns: %{user: user}} = conn, params) do
user
end
+ user =
+ if default_scope = params["default_scope"] do
+ with new_info <- Map.put(user.info, "default_scope", default_scope),
+ change <- User.info_changeset(user, %{info: new_info}),
+ {:ok, user} <- User.update_and_set_cache(change) do
+ user
+ else
+ _e -> user
+ end
+ else
+ user
+ end
+
with changeset <- User.update_changeset(user, params),
{:ok, user} <- User.update_and_set_cache(changeset) do
CommonAPI.update(user)
From 32211c4ada3ea113fb4041ae28b884130b2f4342 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Sun, 24 Jun 2018 06:31:09 +0000
Subject: [PATCH 026/100] tests: add default_scope where appropriate
---
test/web/twitter_api/views/user_view_test.exs | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs
index eea743b32..49f73c2fe 100644
--- a/test/web/twitter_api/views/user_view_test.exs
+++ b/test/web/twitter_api/views/user_view_test.exs
@@ -60,7 +60,8 @@ test "A user" do
"cover_photo" => banner,
"background_image" => nil,
"is_local" => true,
- "locked" => false
+ "locked" => false,
+ "default_scope" => "public"
}
assert represented == UserView.render("show.json", %{user: user})
@@ -96,7 +97,8 @@ test "A user for a given other follower", %{user: user} do
"cover_photo" => banner,
"background_image" => nil,
"is_local" => true,
- "locked" => false
+ "locked" => false,
+ "default_scope" => "public"
}
assert represented == UserView.render("show.json", %{user: user, for: follower})
@@ -133,7 +135,8 @@ test "A user that follows you", %{user: user} do
"cover_photo" => banner,
"background_image" => nil,
"is_local" => true,
- "locked" => false
+ "locked" => false,
+ "default_scope" => "public"
}
assert represented == UserView.render("show.json", %{user: follower, for: user})
@@ -177,7 +180,8 @@ test "A blocked user for the blocker" do
"cover_photo" => banner,
"background_image" => nil,
"is_local" => true,
- "locked" => false
+ "locked" => false,
+ "default_scope" => "public"
}
blocker = Repo.get(User, blocker.id)
From c42f28b82c01423d05b85514797bf3bce692628d Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Sun, 24 Jun 2018 05:33:22 +0000
Subject: [PATCH 027/100] transmogrifier: accept Article activities
---
lib/pleroma/web/activity_pub/transmogrifier.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 30cd70fb6..1b60170d9 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -122,7 +122,7 @@ def fix_content_map(object), do: object
# TODO: validate those with a Ecto scheme
# - tags
# - emoji
- def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
+ def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data) when objtype in ["Article", "Note"] do
with nil <- Activity.get_create_activity_by_object_ap_id(object["id"]),
%User{} = user <- User.get_or_fetch_by_ap_id(data["actor"]) do
object = fix_object(data["object"])
From bd479606ba2b645db46ef5312f06323534cfd9c9 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Sun, 24 Jun 2018 06:52:17 +0000
Subject: [PATCH 028/100] utils: make_create_data: add support for Article
objects
---
lib/pleroma/web/activity_pub/utils.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 64329b710..8b41a3bec 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -128,7 +128,7 @@ def lazy_put_object_defaults(map, activity \\ %{}) do
Inserts a full object if it is contained in an activity.
"""
def insert_full_object(%{"object" => %{"type" => type} = object_data})
- when is_map(object_data) and type in ["Note"] do
+ when is_map(object_data) and type in ["Article", "Note"] do
with {:ok, _} <- Object.create(object_data) do
:ok
end
From 121c1f62306e416f1f6106d1751b55a5eb9f9075 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Sun, 24 Jun 2018 05:33:34 +0000
Subject: [PATCH 029/100] twitter api: refactor activity html generation, add
support for Articles
---
.../web/twitter_api/views/activity_view.ex | 41 +++++++++++++++----
1 file changed, 32 insertions(+), 9 deletions(-)
diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex
index 62ce3b7b5..0779872fe 100644
--- a/lib/pleroma/web/twitter_api/views/activity_view.ex
+++ b/lib/pleroma/web/twitter_api/views/activity_view.ex
@@ -228,15 +228,7 @@ def render(
tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags
- summary = activity.data["object"]["summary"]
- content = object["content"]
-
- content =
- if !!summary and summary != "" do
- "#{activity.data["object"]["summary"]}
#{content}"
- else
- content
- end
+ {summary, content} = render_content(object)
html =
HtmlSanitizeEx.basic_html(content)
@@ -266,4 +258,35 @@ def render(
"visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object)
}
end
+
+ def render_content(%{"type" => "Note"} = object) do
+ summary = object["summary"]
+ content =
+ if !!summary and summary != "" do
+ "#{summary}
#{object["content"]}"
+ else
+ object["content"]
+ end
+
+ {summary, content}
+ end
+
+ def render_content(%{"type" => "Article"} = object) do
+ summary = object["name"] || object["summary"]
+ content =
+ if !!summary and summary != "" do
+ "#{summary}
#{object["content"]}"
+ else
+ object["content"]
+ end
+
+ {summary, content}
+ end
+
+ def render_content(object) do
+ summary = object["summary"] || "Unhandled activity type: #{object["type"]}"
+ content = "#{summary}
#{object["content"]}"
+
+ {summary, content}
+ end
end
From ea982e7503767f645dc26235e04c541ce976de71 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Sun, 24 Jun 2018 06:14:17 +0000
Subject: [PATCH 030/100] mastodon api: add interpreter for Article activity
types
---
.../web/mastodon_api/views/status_view.ex | 18 +++++++++++++++++-
1 file changed, 17 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 59898457b..f7ad87bad 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -128,7 +128,7 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
in_reply_to_id: reply_to && to_string(reply_to.id),
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
reblog: nil,
- content: HtmlSanitizeEx.basic_html(object["content"]),
+ content: render_content(object),
created_at: created_at,
reblogs_count: announcement_count,
favourites_count: like_count,
@@ -207,4 +207,20 @@ def get_visibility(object) do
"direct"
end
end
+
+ def render_content(%{"type" => "Article"} = object) do
+ summary = object["name"]
+ content =
+ if !!summary and summary != "" do
+ "#{summary}
#{object["content"]}"
+ else
+ object["content"]
+ end
+
+ HtmlSanitizeEx.basic_html(content)
+ end
+
+ def render_content(object) do
+ HtmlSanitizeEx.basic_html(object["content"])
+ end
end
From 66819ea784509fbed3f7db8056ececf150339b35 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Sun, 24 Jun 2018 06:30:23 +0000
Subject: [PATCH 031/100] twitter api: use ActivityView.render_content() where
appropriate instead of duplicating the logic
---
.../twitter_api/representers/activity_representer.ex | 11 ++---------
.../representers/activity_representer_test.exs | 2 +-
2 files changed, 3 insertions(+), 10 deletions(-)
diff --git a/lib/pleroma/web/twitter_api/representers/activity_representer.ex b/lib/pleroma/web/twitter_api/representers/activity_representer.ex
index 57837205e..bb77e61f3 100644
--- a/lib/pleroma/web/twitter_api/representers/activity_representer.ex
+++ b/lib/pleroma/web/twitter_api/representers/activity_representer.ex
@@ -4,7 +4,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
use Pleroma.Web.TwitterAPI.Representers.BaseRepresenter
alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter
alias Pleroma.{Activity, User}
- alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView}
+ alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView}
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Formatter
@@ -164,14 +164,7 @@ def to_map(
tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags
- summary = activity.data["object"]["summary"]
-
- content =
- if !!summary and summary != "" do
- "#{activity.data["object"]["summary"]}
#{content}"
- else
- content
- end
+ {summary, content} = ActivityView.render_content(object)
html =
HtmlSanitizeEx.basic_html(content)
diff --git a/test/web/twitter_api/representers/activity_representer_test.exs b/test/web/twitter_api/representers/activity_representer_test.exs
index 16c6e7b0d..7505093dd 100644
--- a/test/web/twitter_api/representers/activity_representer_test.exs
+++ b/test/web/twitter_api/representers/activity_representer_test.exs
@@ -126,7 +126,7 @@ test "an activity" do
}
expected_html =
- "2hu
alert('YAY')Some content mentioning 2hu
alert('YAY')Some content mentioning @shp"
From 971bb4f2bde125c1f8397c244a6fbdec0d26716b Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Sun, 24 Jun 2018 06:34:44 +0000
Subject: [PATCH 032/100] activity interpretation: formatting
---
lib/pleroma/web/activity_pub/transmogrifier.ex | 3 ++-
lib/pleroma/web/mastodon_api/views/status_view.ex | 1 +
lib/pleroma/web/twitter_api/views/activity_view.ex | 2 ++
3 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 1b60170d9..59c4b90e7 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -122,7 +122,8 @@ def fix_content_map(object), do: object
# TODO: validate those with a Ecto scheme
# - tags
# - emoji
- def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data) when objtype in ["Article", "Note"] do
+ def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data)
+ when objtype in ["Article", "Note"] do
with nil <- Activity.get_create_activity_by_object_ap_id(object["id"]),
%User{} = user <- User.get_or_fetch_by_ap_id(data["actor"]) do
object = fix_object(data["object"])
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index f7ad87bad..6b48c41c1 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -210,6 +210,7 @@ def get_visibility(object) do
def render_content(%{"type" => "Article"} = object) do
summary = object["name"]
+
content =
if !!summary and summary != "" do
"#{summary}
#{object["content"]}"
diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex
index 0779872fe..f418249e2 100644
--- a/lib/pleroma/web/twitter_api/views/activity_view.ex
+++ b/lib/pleroma/web/twitter_api/views/activity_view.ex
@@ -261,6 +261,7 @@ def render(
def render_content(%{"type" => "Note"} = object) do
summary = object["summary"]
+
content =
if !!summary and summary != "" do
"#{summary}
#{object["content"]}"
@@ -273,6 +274,7 @@ def render_content(%{"type" => "Note"} = object) do
def render_content(%{"type" => "Article"} = object) do
summary = object["name"] || object["summary"]
+
content =
if !!summary and summary != "" do
"#{summary}
#{object["content"]}"
From c06a5af386a3cdfeda0b2c21963d9200fb7d6c89 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tristan=20Mah=C3=A9?=
Date: Thu, 28 Jun 2018 10:49:44 -0700
Subject: [PATCH 033/100] CONFIGURATION.md: add doc about upload and strip_exif
---
CONFIGURATION.md | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/CONFIGURATION.md b/CONFIGURATION.md
index 3f0ecafb5..51a76d1b7 100644
--- a/CONFIGURATION.md
+++ b/CONFIGURATION.md
@@ -13,6 +13,21 @@ Instead, overload the settings by editing the following files:
* `dev.secret.exs`: custom additional configuration for `MIX_ENV=dev`
* `prod.secret.exs`: custom additional configuration for `MIX_ENV=prod`
+## Uploads configuration
+
+To configure where to upload files, and wether or not
+you want to remove automatically EXIF data from pictures
+being uploaded.
+
+ config :pleroma, Pleroma.Upload,
+ uploads: "uploads",
+ strip_exif: false
+
+* `uploads`: where to put the uploaded files, relative to pleroma's main directory.
+* `strip_exif`: whether or not to remove EXIF data from uploaded pics automatically.
+ This needs Imagemagick installed on the system ( apt install imagemagick ).
+
+
## Block functionality
config :pleroma, :activitypub,
From 0bfbf15b379a95e7fad50e8f611cf6c5e67cfa9c Mon Sep 17 00:00:00 2001
From: Jorty
Date: Sat, 30 Jun 2018 15:08:31 -0400
Subject: [PATCH 034/100] Allow emojis to be added automatically
---
.../controllers/util_controller.ex | 20 ++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index 7a0c37ce9..db6142dc8 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -173,7 +173,25 @@ def version(conn, _params) do
end
def emoji(conn, _params) do
- json(conn, Enum.into(Formatter.get_custom_emoji(), %{}))
+ emoji_dir = Path.join(:code.priv_dir(:pleroma), "static/emoji")
+
+ shortcode_emoji_glob =
+ Path.join(
+ emoji_dir,
+ Application.get_env(:pleroma, :emoji, []) |>
+ Keyword.get(:glob, "by-shortcode/**/*.png")
+ )
+ shortcode_emoji =
+ Path.wildcard(shortcode_emoji_glob) |>
+ Enum.map(fn path ->
+ shortcode = Path.basename(path, ".png")
+ serve_path = Path.join("/emoji", Path.relative_to(path, emoji_dir))
+ {shortcode, serve_path}
+ end)
+
+ emoji = Enum.into(Formatter.get_custom_emoji(), shortcode_emoji) |> Enum.into(%{})
+
+ json(conn, emoji)
end
def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
From c171f9790bc2d6a1b215792ade1b1cfc7e458ac4 Mon Sep 17 00:00:00 2001
From: Jorty
Date: Sat, 30 Jun 2018 17:20:08 -0400
Subject: [PATCH 035/100] Move emoji glob setting to config.exs
Also, a bit of formatting, and the glob includes an "/emoji/" prefix to
make it more intuitive to users
---
config/config.exs | 2 ++
.../controllers/util_controller.ex | 35 +++++++++++--------
2 files changed, 23 insertions(+), 14 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index cf6cbaa9d..96350d064 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -12,6 +12,8 @@
config :pleroma, Pleroma.Upload, uploads: "uploads"
+config :pleroma, :emoji, shortcode_glob: "/emoji/by-shortcode/**/*.png"
+
# Configures the endpoint
config :pleroma, Pleroma.Web.Endpoint,
url: [host: "localhost"],
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index db6142dc8..73a46bb5e 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -173,23 +173,30 @@ def version(conn, _params) do
end
def emoji(conn, _params) do
- emoji_dir = Path.join(:code.priv_dir(:pleroma), "static/emoji")
+ static_dir = Path.join(:code.priv_dir(:pleroma), "static")
+
+ emoji_shortcode_glob =
+ Application.get_env(:pleroma, :emoji, [])
+ |> Keyword.get(:shortcode_glob)
- shortcode_emoji_glob =
- Path.join(
- emoji_dir,
- Application.get_env(:pleroma, :emoji, []) |>
- Keyword.get(:glob, "by-shortcode/**/*.png")
- )
shortcode_emoji =
- Path.wildcard(shortcode_emoji_glob) |>
- Enum.map(fn path ->
- shortcode = Path.basename(path, ".png")
- serve_path = Path.join("/emoji", Path.relative_to(path, emoji_dir))
- {shortcode, serve_path}
- end)
+ case emoji_shortcode_glob do
+ nil ->
+ []
- emoji = Enum.into(Formatter.get_custom_emoji(), shortcode_emoji) |> Enum.into(%{})
+ glob ->
+ Path.join(static_dir, glob)
+ |> Path.wildcard()
+ |> Enum.map(fn path ->
+ shortcode = Path.basename(path, ".png")
+ serve_path = Path.join("/", Path.relative_to(path, static_dir))
+ {shortcode, serve_path}
+ end)
+ end
+
+ emoji =
+ Enum.into(Formatter.get_custom_emoji(), shortcode_emoji)
+ |> Enum.into(%{})
json(conn, emoji)
end
From 748fff6544cc70476bb0892a661d4d8c7f6ee295 Mon Sep 17 00:00:00 2001
From: Jorty
Date: Sat, 30 Jun 2018 20:35:34 -0400
Subject: [PATCH 036/100] Fix auto-shortcode emoji
Emoji were broken due to `Pleroma.Formatter` not knowing about the
auto-shortcode emoji. This moves that logic from
`Pleroma.Web.TwitterAPI.UtilController` to `Pleroma.Formatter`.
Additionally, it's now possible to specify multiple shortcode globs, and
the default globs were changed to `["/emoji/custom/**/*.png"]`, since
that's in the .gitignore and the files there would have to be shortcode
emoji anyway.
---
config/config.exs | 2 +-
lib/pleroma/formatter.ex | 23 +++++++++++++++-
.../controllers/util_controller.ex | 27 +------------------
3 files changed, 24 insertions(+), 28 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index 96350d064..0616fe4fb 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -12,7 +12,7 @@
config :pleroma, Pleroma.Upload, uploads: "uploads"
-config :pleroma, :emoji, shortcode_glob: "/emoji/by-shortcode/**/*.png"
+config :pleroma, :emoji, shortcode_globs: ["/emoji/custom/**/*.png"]
# Configures the endpoint
config :pleroma, Pleroma.Web.Endpoint,
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index df7ffbc41..0aaf21538 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -116,7 +116,28 @@ def parse_mentions(text) do
_ -> []
end)
- @emoji @finmoji_with_filenames ++ @emoji_from_file
+ @emoji_from_globs (
+ static_path = Path.join(:code.priv_dir(:pleroma), "static")
+
+ globs =
+ Application.get_env(:pleroma, :emoji, [])
+ |> Keyword.get(:shortcode_globs, [])
+
+ paths =
+ Enum.map(globs, fn glob ->
+ Path.join(static_path, glob)
+ |> Path.wildcard()
+ end)
+ |> Enum.concat()
+
+ Enum.map(paths, fn path ->
+ shortcode = Path.basename(path, Path.extname(path))
+ external_path = Path.join("/", Path.relative_to(path, static_path))
+ {shortcode, external_path}
+ end)
+ )
+
+ @emoji @finmoji_with_filenames ++ @emoji_from_globs ++ @emoji_from_file
def emojify(text, emoji \\ @emoji)
def emojify(text, nil), do: text
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index 73a46bb5e..7a0c37ce9 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -173,32 +173,7 @@ def version(conn, _params) do
end
def emoji(conn, _params) do
- static_dir = Path.join(:code.priv_dir(:pleroma), "static")
-
- emoji_shortcode_glob =
- Application.get_env(:pleroma, :emoji, [])
- |> Keyword.get(:shortcode_glob)
-
- shortcode_emoji =
- case emoji_shortcode_glob do
- nil ->
- []
-
- glob ->
- Path.join(static_dir, glob)
- |> Path.wildcard()
- |> Enum.map(fn path ->
- shortcode = Path.basename(path, ".png")
- serve_path = Path.join("/", Path.relative_to(path, static_dir))
- {shortcode, serve_path}
- end)
- end
-
- emoji =
- Enum.into(Formatter.get_custom_emoji(), shortcode_emoji)
- |> Enum.into(%{})
-
- json(conn, emoji)
+ json(conn, Enum.into(Formatter.get_custom_emoji(), %{}))
end
def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
From 7a351cb36f64ae98fa3d28d5133fa0a49d376659 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier"
Date: Mon, 2 Jul 2018 06:28:21 +0200
Subject: [PATCH 037/100] [Pleroma.Web.MastodonAPI.StatusView]: Fill the url
field for statuses
Closes: https://git.pleroma.social/pleroma/pleroma/issues/231
---
lib/pleroma/web/mastodon_api/views/status_view.ex | 3 +--
1 file changed, 1 insertion(+), 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 59898457b..39abb4c6b 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -54,8 +54,7 @@ def render(
%{
id: to_string(activity.id),
uri: object,
- # TODO: This might be wrong, check with mastodon.
- url: nil,
+ url: object,
account: AccountView.render("account.json", %{user: user}),
in_reply_to_id: nil,
in_reply_to_account_id: nil,
From 4326cb992019f0a050cde3775a4d36219750dbbc Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier"
Date: Tue, 26 Jun 2018 13:51:35 +0200
Subject: [PATCH 038/100] [Pleroma.Web.Nodeinfo.NodeinfoController]: add
mediaProxy metadata
Closes: https://git.pleroma.social/pleroma/pleroma/issues/229
---
lib/pleroma/web/nodeinfo/nodeinfo_controller.ex | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
index aec77168a..e7e2794ae 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
@@ -4,8 +4,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
alias Pleroma.Stats
alias Pleroma.Web
- @instance Application.get_env(:pleroma, :instance)
-
def schemas(conn, _params) do
response = %{
links: [
@@ -21,6 +19,8 @@ def schemas(conn, _params) do
# Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json
def nodeinfo(conn, %{"version" => "2.0"}) do
+ @instance = Application.get_env(:pleroma, :instance)
+ @media_proxy = Application.get_env(:pleroma, :media_proxy)
stats = Stats.get_stats()
response = %{
@@ -42,7 +42,8 @@ def nodeinfo(conn, %{"version" => "2.0"}) do
localPosts: stats.status_count || 0
},
metadata: %{
- nodeName: Keyword.get(@instance, :name)
+ nodeName: Keyword.get(@instance, :name),
+ mediaProxy: Keyword.get(@media_proxy, :enabled)
}
}
From 85465512578d8113366e71d9122d336b8fd49fa4 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Thu, 12 Jul 2018 02:45:48 +0000
Subject: [PATCH 039/100] activitypub: switch to using x509 representation for
public keys instead of pkcs#1
---
lib/pleroma/web/activity_pub/views/user_view.ex | 2 +-
test/web/activity_pub/views/user_view_test.exs | 2 +-
2 files changed, 2 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 f4b2e0610..41bfe5048 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -12,7 +12,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
def render("user.json", %{user: user}) do
{:ok, user} = WebFinger.ensure_keys_present(user)
{:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"])
- public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key)
+ public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key])
%{
diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs
index 0c64e62c3..7fc870e96 100644
--- a/test/web/activity_pub/views/user_view_test.exs
+++ b/test/web/activity_pub/views/user_view_test.exs
@@ -13,6 +13,6 @@ test "Renders a user, including the public key" do
assert result["id"] == user.ap_id
assert result["preferredUsername"] == user.nickname
- assert String.contains?(result["publicKey"]["publicKeyPem"], "BEGIN RSA PUBLIC KEY")
+ assert String.contains?(result["publicKey"]["publicKeyPem"], "BEGIN PUBLIC KEY")
end
end
From b806aa36c89f2f7359580c716f98d41b58cb87f9 Mon Sep 17 00:00:00 2001
From: lambda
Date: Thu, 12 Jul 2018 06:00:55 +0000
Subject: [PATCH 040/100] Update nodeinfo_controller.ex
---
lib/pleroma/web/nodeinfo/nodeinfo_controller.ex | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
index e7e2794ae..12aca4a10 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
@@ -19,22 +19,22 @@ def schemas(conn, _params) do
# Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json
def nodeinfo(conn, %{"version" => "2.0"}) do
- @instance = Application.get_env(:pleroma, :instance)
- @media_proxy = Application.get_env(:pleroma, :media_proxy)
+ instance = Application.get_env(:pleroma, :instance)
+ media_proxy = Application.get_env(:pleroma, :media_proxy)
stats = Stats.get_stats()
response = %{
version: "2.0",
software: %{
name: "pleroma",
- version: Keyword.get(@instance, :version)
+ version: Keyword.get(instance, :version)
},
protocols: ["ostatus", "activitypub"],
services: %{
inbound: [],
outbound: []
},
- openRegistrations: Keyword.get(@instance, :registrations_open),
+ openRegistrations: Keyword.get(instance, :registrations_open),
usage: %{
users: %{
total: stats.user_count || 0
@@ -42,8 +42,8 @@ def nodeinfo(conn, %{"version" => "2.0"}) do
localPosts: stats.status_count || 0
},
metadata: %{
- nodeName: Keyword.get(@instance, :name),
- mediaProxy: Keyword.get(@media_proxy, :enabled)
+ nodeName: Keyword.get(instance, :name),
+ mediaProxy: Keyword.get(media_proxy, :enabled)
}
}
From 3b799f22b7cd6afd9fb8bc8c7800ec770585a957 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Sat, 23 Jun 2018 06:08:09 +0000
Subject: [PATCH 041/100] twitterapi: activity view: expose message summary
text
---
lib/pleroma/web/twitter_api/views/activity_view.ex | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex
index f418249e2..55b5287f5 100644
--- a/lib/pleroma/web/twitter_api/views/activity_view.ex
+++ b/lib/pleroma/web/twitter_api/views/activity_view.ex
@@ -255,7 +255,8 @@ def render(
"tags" => tags,
"activity_type" => "post",
"possibly_sensitive" => possibly_sensitive,
- "visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object)
+ "visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object),
+ "summary" => summary
}
end
From 152a526237abb846b96a3c7b4001c8bd0c7409f9 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Sun, 24 Jun 2018 05:33:34 +0000
Subject: [PATCH 042/100] twitter api: refactor activity html generation, add
support for Articles
---
lib/pleroma/web/twitter_api/views/activity_view.ex | 2 --
1 file changed, 2 deletions(-)
diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex
index 55b5287f5..cb7c4cb96 100644
--- a/lib/pleroma/web/twitter_api/views/activity_view.ex
+++ b/lib/pleroma/web/twitter_api/views/activity_view.ex
@@ -262,7 +262,6 @@ def render(
def render_content(%{"type" => "Note"} = object) do
summary = object["summary"]
-
content =
if !!summary and summary != "" do
"#{summary}
#{object["content"]}"
@@ -275,7 +274,6 @@ def render_content(%{"type" => "Note"} = object) do
def render_content(%{"type" => "Article"} = object) do
summary = object["name"] || object["summary"]
-
content =
if !!summary and summary != "" do
"#{summary}
#{object["content"]}"
From f03e57f7647f4d302359680c7eb7fe48433c7beb Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Sun, 24 Jun 2018 06:22:53 +0000
Subject: [PATCH 043/100] twitter api: activity representer: add summary field
for testsuite
---
.../web/twitter_api/representers/activity_representer.ex | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/web/twitter_api/representers/activity_representer.ex b/lib/pleroma/web/twitter_api/representers/activity_representer.ex
index bb77e61f3..26bfb79af 100644
--- a/lib/pleroma/web/twitter_api/representers/activity_representer.ex
+++ b/lib/pleroma/web/twitter_api/representers/activity_representer.ex
@@ -191,7 +191,8 @@ def to_map(
"tags" => tags,
"activity_type" => "post",
"possibly_sensitive" => possibly_sensitive,
- "visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object)
+ "visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object),
+ "summary" => object["summary"]
}
end
From 4fb64c1d862692bb869dd735e9772195b7cf2705 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Sun, 24 Jun 2018 06:23:19 +0000
Subject: [PATCH 044/100] testsuite: twitter api: add summary where necessary
---
.../web/twitter_api/representers/activity_representer_test.exs | 3 ++-
test/web/twitter_api/views/activity_view_test.exs | 3 ++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/test/web/twitter_api/representers/activity_representer_test.exs b/test/web/twitter_api/representers/activity_representer_test.exs
index 7505093dd..3f85e028b 100644
--- a/test/web/twitter_api/representers/activity_representer_test.exs
+++ b/test/web/twitter_api/representers/activity_representer_test.exs
@@ -155,7 +155,8 @@ test "an activity" do
"activity_type" => "post",
"possibly_sensitive" => true,
"uri" => activity.data["object"]["id"],
- "visibility" => "direct"
+ "visibility" => "direct",
+ "summary" => "2hu"
}
assert ActivityRepresenter.to_map(activity, %{
diff --git a/test/web/twitter_api/views/activity_view_test.exs b/test/web/twitter_api/views/activity_view_test.exs
index 5b2a7466b..a101e4ae8 100644
--- a/test/web/twitter_api/views/activity_view_test.exs
+++ b/test/web/twitter_api/views/activity_view_test.exs
@@ -48,7 +48,8 @@ test "a create activity with a note" do
"text" => "Hey @shp!",
"uri" => activity.data["object"]["id"],
"user" => UserView.render("show.json", %{user: user}),
- "visibility" => "direct"
+ "visibility" => "direct",
+ "summary" => nil
}
assert result == expected
From b832df1e158aa945a3c481804da0a839f18d46c5 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Thu, 12 Jul 2018 16:12:54 +0000
Subject: [PATCH 045/100] formatting
---
lib/pleroma/web/twitter_api/views/activity_view.ex | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex
index cb7c4cb96..55b5287f5 100644
--- a/lib/pleroma/web/twitter_api/views/activity_view.ex
+++ b/lib/pleroma/web/twitter_api/views/activity_view.ex
@@ -262,6 +262,7 @@ def render(
def render_content(%{"type" => "Note"} = object) do
summary = object["summary"]
+
content =
if !!summary and summary != "" do
"#{summary}
#{object["content"]}"
@@ -274,6 +275,7 @@ def render_content(%{"type" => "Note"} = object) do
def render_content(%{"type" => "Article"} = object) do
summary = object["name"] || object["summary"]
+
content =
if !!summary and summary != "" do
"#{summary}
#{object["content"]}"
From 590d4df77c5b6b17b12e9692e77bb24c87ecc4a2 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Thu, 12 Jul 2018 16:37:42 +0000
Subject: [PATCH 046/100] activitypub: more robustly handle object-to-actor
associations
---
.../web/activity_pub/transmogrifier.ex | 20 +++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 59c4b90e7..3dd3df553 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -18,7 +18,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"""
def fix_object(object) do
object
- |> Map.put("actor", object["attributedTo"])
+ |> fix_actor
|> fix_attachments
|> fix_context
|> fix_in_reply_to
@@ -27,6 +27,19 @@ def fix_object(object) do
|> fix_content_map
end
+ def fix_actor(%{"attributedTo" => actor} = object) do
+ # attributedTo can be a list in the case of peertube or plume
+ actor =
+ if is_list(actor) do
+ Enum.at(actor, 0)
+ else
+ actor
+ end
+
+ object
+ |> Map.put("actor", actor)
+ end
+
def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object)
when not is_nil(in_reply_to_id) do
case ActivityPub.fetch_object_from_id(in_reply_to_id) do
@@ -126,7 +139,10 @@ def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = obj
when objtype in ["Article", "Note"] do
with nil <- Activity.get_create_activity_by_object_ap_id(object["id"]),
%User{} = user <- User.get_or_fetch_by_ap_id(data["actor"]) do
- object = fix_object(data["object"])
+ # prefer the activity's actor instead of attributedTo
+ object =
+ fix_object(data["object"])
+ |> Map.put("actor", data["actor"])
params = %{
to: data["to"],
From 0899588e4dfba7b7e65ee606378a1c1c710e4a82 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Thu, 12 Jul 2018 17:13:20 +0000
Subject: [PATCH 047/100] ostatus: return AS2 objects on /notice and
/activities URLs like with /objects.
---
lib/pleroma/web/ostatus/ostatus_controller.ex | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex
index 00bffbd5d..09d1b1110 100644
--- a/lib/pleroma/web/ostatus/ostatus_controller.ex
+++ b/lib/pleroma/web/ostatus/ostatus_controller.ex
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Repo
alias Pleroma.Web.{OStatus, Federator}
alias Pleroma.Web.XML
+ alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.ActivityPub.ActivityPub
@@ -90,7 +91,7 @@ def object(conn, %{"uuid" => uuid}) do
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case get_format(conn) do
"html" -> redirect(conn, to: "/notice/#{activity.id}")
- _ -> represent_activity(conn, activity, user)
+ _ -> represent_activity(conn, nil, activity, user)
end
else
{:public?, false} ->
@@ -110,9 +111,9 @@ def activity(conn, %{"uuid" => uuid}) do
{_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
{_, true} <- {:public?, ActivityPub.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
- case get_format(conn) do
+ case format = get_format(conn) do
"html" -> redirect(conn, to: "/notice/#{activity.id}")
- _ -> represent_activity(conn, activity, user)
+ _ -> represent_activity(conn, format, activity, user)
end
else
{:public?, false} ->
@@ -130,14 +131,14 @@ def notice(conn, %{"id" => id}) do
with {_, %Activity{} = activity} <- {:activity, Repo.get(Activity, id)},
{_, true} <- {:public?, ActivityPub.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
- case get_format(conn) do
+ case format = get_format(conn) do
"html" ->
conn
|> put_resp_content_type("text/html")
|> send_file(200, "priv/static/index.html")
_ ->
- represent_activity(conn, activity, user)
+ represent_activity(conn, format, activity, user)
end
else
{:public?, false} ->
@@ -151,7 +152,13 @@ def notice(conn, %{"id" => id}) do
end
end
- defp represent_activity(conn, activity, user) do
+ defp represent_activity(conn, "activity+json", activity, user) do
+ conn
+ |> put_resp_header("content-type", "application/activity+json")
+ |> json(ObjectView.render("object.json", %{object: activity}))
+ end
+
+ defp represent_activity(conn, _, activity, user) do
response =
activity
|> ActivityRepresenter.to_simple_form(user, true)
From f1a29fc43c2ef47786ff932b7afb42825159a067 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Thu, 12 Jul 2018 20:32:05 +0000
Subject: [PATCH 048/100] test: ostatus controller: add AS2 fetching tests
---
test/web/ostatus/ostatus_controller_test.exs | 25 ++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs
index d5adf3bf3..c23b175e8 100644
--- a/test/web/ostatus/ostatus_controller_test.exs
+++ b/test/web/ostatus/ostatus_controller_test.exs
@@ -155,6 +155,31 @@ test "gets a notice", %{conn: conn} do
assert response(conn, 200)
end
+ test "gets a notice in AS2 format", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ url = "/notice/#{note_activity.id}"
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get(url)
+
+ assert json_response(conn, 200)
+ end
+
+ test "gets an activity in AS2 format", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
+ url = "/activities/#{uuid}"
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get(url)
+
+ assert json_response(conn, 200)
+ end
+
test "404s a private notice", %{conn: conn} do
note_activity = insert(:direct_note_activity)
url = "/notice/#{note_activity.id}"
From 24b5a75d096387ac63b30a09c1a6d8a986eceb61 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier"
Date: Thu, 12 Jul 2018 19:52:17 +0200
Subject: [PATCH 049/100] Add test for Plume Articles
---
.../baptiste.gelex.xyz-article.json | 1 +
.../httpoison_mock/baptiste.gelex.xyz-user.json | 1 +
test/support/httpoison_mock.ex | 16 ++++++++++++++++
test/web/activity_pub/activity_pub_test.exs | 9 +++++++++
4 files changed, 27 insertions(+)
create mode 100644 test/fixtures/httpoison_mock/baptiste.gelex.xyz-article.json
create mode 100644 test/fixtures/httpoison_mock/baptiste.gelex.xyz-user.json
diff --git a/test/fixtures/httpoison_mock/baptiste.gelex.xyz-article.json b/test/fixtures/httpoison_mock/baptiste.gelex.xyz-article.json
new file mode 100644
index 000000000..3f3f0f4fb
--- /dev/null
+++ b/test/fixtures/httpoison_mock/baptiste.gelex.xyz-article.json
@@ -0,0 +1 @@
+{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"Emoji":"toot:Emoji","Hashtag":"as:Hashtag","atomUri":"ostatus:atomUri","conversation":"ostatus:conversation","featured":"toot:featured","focalPoint":{"@container":"@list","@id":"toot:focalPoint"},"inReplyToAtomUri":"ostatus:inReplyToAtomUri","manuallyApprovesFollowers":"as:manuallyApprovesFollowers","movedTo":"as:movedTo","ostatus":"http://ostatus.org#","sensitive":"as:sensitive","toot":"http://joinmastodon.org/ns#"}],"attributedTo":["https://baptiste.gelez.xyz/@/BaptisteGelez"],"cc":[],"content":"It has been one month since the last \"This Month in Plume\" article, so it is time for another edition of our monthly changelog!
\nBug Fixes and Security
\nLet's start with the hidden, but still (very) important changes: bug fixes and security patches.
\nFirst of all, @Trinity protected us against two major security flaws, called XSS and CSRF. The first one allows the attacker to run malicious code if you visit a Plume page where some of their personal data is present. The second one lets them post data with your Plume account by visiting one of their own website. It is two very common attack, and it is great we are now protected against them!
\nThe other big change in this area, is that we are now validating the data you are sending before doing anything with it. It means that, for instance, you will no longer be able to register with an empty username and to break everything.
\nOn the federation side, many issues were reported by @kaniini and redmatrix (respectively contributing to Pleroma and Hubzilla). By fixing some of them, we made it possible to federate Plume articles to Pleroma!
\n@Trinity hopefully noticed that there was a bug in our password check code: we were not checking that your password was correct, but only that the verification process went without errors. Concretely, it means that you could login to any account with any password. I wrote this part of the code when I was still the only contributor to the project, so nobody could review my work. We will now be trying to check every change, especially when it deals with critical parts of Plume, to avoid similar issues in the future, and we I'm really sorry this happened (even if I think nobody exploited it).
\nZanfib and stephenburgess8 also commited some small bugfixes, improving the general experience.
\nNew Features
\nLet's now talk about the features that we introduced during this month.
\nOne of the most easy to spot is the redesign of Plume, made by @Madeorsk. I personaly love what he did, it really improved the readability and gave Plume a bit more of identity than the previous design. And he is still improving it.
\nWe also enabled Mardown in comment, to let you write more structured and nicely formatted responses.
\nAs you may have noticed, I have used mentions in this post. Indeed, it is now possible to mention someone in your articles or in comments. It works exactly the same way as in other apps, and you should receive a notification if someone mentionned you.
\nA dashboard to manage your blogs has also been introduced. In the future it may be used to manage your drafts, and eventually to show some statistics. The goal is to have a more specific homepage for authors.
\nThe federation with other ActivityPub softwares, like Mastodon or Pleroma is starting to work quite well, but the federation between Plume instances is far from being complete. However, we started to work on it, and it is now possible to view a distant user profile or blog from your instance, even if only basic informations are fetched yet (the articles are not loaded for instance).
\nAnother new feature that may not be visible for everyone, is the new NodeInfo endpoint. NodeInfo is a protocol allowing to get informations about a specific federated instance (whatever software it runs). It means that Plume instances can now be listed on sites like fediverse.network.
\nMaybe you wanted to host a Plume instance, but you don't like long install process during which you are just copy/pasting commands that you don't really understand from the documentation. That's why we introduced a setup script: the first you'll launch Plume, it will ask you a few questions and automatically setup your instance in a few minutes. We hope that this feature will help to host small instances, run by non-professional adminsys. You can see a demo of this tool on asciinema.
\nLast but not least, Plume is now translatable! It is already available in English, French, Polish (thanks to @m4sk1n)) and German (thanks to bitkeks). If your browser is configured to display pages in these languages, you should normally see the interface in your language. And if your language is not present yet, feel free to add your translation.
\nOther Changes
\nWe also improved the code a lot. We tried to separate each part as much as possible, making it easier to re-use for other projects. For instance, our database code is now isolated from the rest of the app, which means it will be easier to make import tools from other blogging engines. Some parts of the code are even shared with another project, Aardwolf a federated Facebook alternative. For instance, both of our projects use the same internationalization code, and once Aardwolf will implement federation, this part of the code will probably be shared too. Since the WebFinger module (used to find new users and blogs) and the CSRF protection code (see the \"Bug fixes and Security\" section) have been isolated in their own modules, they may be shared by both projects too.
\nWe also worked a lot on documentation. We now have articles explaining how to setup your Plume instance on various operating systems, but also documenting the translation process. I want to thank BanjoFox (who imported some documentation from their project, Aardwolf, as the setup is quite similar), Kushal and @gled@plume.mastodon.host for working on this.
\nAs you can see, there were many changes this month, but there still a lot to do. Your help will of course be welcome. If you want to contribute to the code, translate Plume in your language, write some documentation, or anything else (or even if you're just curious about the project), feel free to join our Matrix room: #plume:disroot.org. Otherwise, as BanjoFox said on the Aardwolf Team Mastodon account, talking about the project around you is one of the easiest way to help.
\n","id":"https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/","likes":null,"name":"This Month in Plume: June 2018","published":"2018-07-10T20:16:24.087622Z","shares":null,"source":null,"tag":[{"href":"https://baptiste.gelez.xyz/@/Trinity","name":"@Trinity","type":"Mention"},{"href":"https://baptiste.gelez.xyz/@/kaniini/","name":"@kaniini","type":"Mention"},{"href":"https://baptiste.gelez.xyz/@/Trinity","name":"@Trinity","type":"Mention"}],"to":["https://unixcorn.xyz/users/Bat","https://mastodon.host/users/federationbot","https://social.tcit.fr/users/tcit","https://framapiaf.org/users/qwerty","https://mastodon.social/users/lthms","https://eldritch.cafe/users/Nausicaa","https://imaginair.es/users/Elanndelh","https://framapiaf.org/users/Drulac","https://mastodon.partipirate.org/users/NicolasConstant","https://aleph.land/users/Madeorsk","https://maly.io/users/Troll","https://hostux.social/users/superjey","https://mamot.fr/users/Phigger","https://mastodon.social/users/wakest","https://social.coop/users/wakest","https://unixcorn.xyz/users/Ce_lo","https://social.art-software.fr/users/Electron","https://framapiaf.org/users/Quenti","https://toot.plus.yt/users/Djyp","https://mastodon.social/users/brainblasted","https://social.mochi.academy/users/Ambraven","https://social.hacktivis.me/users/lanodan","https://mastodon.eliotberriot.com/users/eliotberriot","https://edolas.world/users/0x1C3B00DA","https://toot.cafe/users/zack","https://manowar.social/users/zatnosk","https://eldritch.cafe/users/fluffy","https://mastodon.social/users/david_ross","https://kosmos.social/users/xiroux","https://mastodon.art/users/EmergencyBattle","https://mastodon.social/users/trwnh","https://octodon.social/users/pybyte","https://anticapitalist.party/users/Trinity","https://mstdn.mx/users/xavavu","https://baptiste.gelez.xyz/@/m4sk1n","https://eldritch.cafe/users/milia","https://mastodon.zaclys.com/users/arx","https://toot.cafe/users/sivy","https://mastodon.social/users/ortegacmanuel","https://mastodon.observer/users/stephen","https://octodon.social/users/chloe","https://unixcorn.xyz/users/AmauryPi","https://cybre.space/users/rick_777","https://mastodon.social/users/wezm","https://baptiste.gelez.xyz/@/idlesong","https://mamot.fr/users/dr4Ke","https://imaginair.es/users/Phigger","https://mamot.fr/users/dlink","https://anticapitalist.party/users/a000d4f7a91939d0e71df1646d7a48","https://framapiaf.org/users/PhieLaidMignon","https://mastodon.social/users/y6nH","https://crazynoisybizarre.town/users/FederationBot","https://social.weho.st/users/dvn","https://mastodon.art/users/Wolthera","https://diaspodon.fr/users/dada","https://pachyder.me/users/Lanza","https://mastodon.xyz/users/ag","https://aleph.land/users/yahananxie","https://mstdn.io/users/chablis_social","https://mastodon.gougere.fr/users/fabien","https://functional.cafe/users/otini","https://social.coop/users/bhaugen","https://octodon.social/users/donblanco","https://chaos.social/users/astro","https://pachyder.me/users/sibear","https://mamot.fr/users/yohann","https://social.wxcafe.net/users/Bat","https://mastodon.social/users/dansup","https://chaos.social/users/juh","https://scifi.fyi/users/paeneultima","https://hostux.social/users/Deuchnord","https://mstdn.fr/users/taziden","https://mamot.fr/users/PifyZ","https://mastodon.social/users/plantabaja","https://mastodon.social/users/gitzgrog","https://mastodon.social/users/Syluban","https://masto.pt/users/eloisa","https://pleroma.soykaf.com/users/notclacke","https://mastodon.social/users/SiegfriedEhret","https://writing.exchange/users/write_as","https://mstdn.io/users/shellkr","https://mastodon.uy/users/jorge","https://mastodon.technology/users/bobstechsite","https://mastodon.social/users/hinterwaeldler","https://mastodon.xyz/users/mgdelacroix","https://mastodon.cloud/users/jjatria","https://baptiste.gelez.xyz/@/Jade/","https://edolas.world/users/pfm","https://mstdn.io/users/jort","https://mastodon.social/users/andreipetcu","https://mastodon.technology/users/0xf00fc7c8","https://mastodon.social/users/khanate","https://mastodon.technology/users/francois","https://mastodon.social/users/glherrmann","https://mastodon.host/users/gled","https://social.holdmybeer.solutions/users/kemonine","https://scholar.social/users/bgcarlisle","https://mastodon.social/users/oldgun","https://baptiste.gelez.xyz/@/snoe/","https://mastodon.at/users/switchingsocial","https://scifi.fyi/users/BrokenBiscuit","https://dev.glitch.social/users/hoodie","https://todon.nl/users/paulfree14","https://mastodon.social/users/aadilayub","https://social.fsck.club/users/anarchosaurus","https://mastodonten.de/users/GiantG","https://mastodon.technology/users/cj","https://cybre.space/users/sam","https://layer8.space/users/silkevicious","https://mastodon.xyz/users/Jimmyrwx","https://fosstodon.org/users/danyspin97","https://mstdn.io/users/cristhyano","https://mastodon.social/users/vanyok","https://hulvr.com/users/rook","https://niu.moe/users/Lucifer","https://mamot.fr/users/Thibaut","https://mastodont.cat/users/bgta","https://mstdn.io/users/hontoni","https://niu.moe/users/lionirdeadman","https://functional.cafe/users/phoe","https://mastodon.social/users/toontoet","https://mastodon.social/users/danipozo","https://scholar.social/users/robertson","https://mastodon.social/users/aldatsa","https://elekk.xyz/users/maloki","https://kitty.town/users/nursemchurt","https://neigh.horse/users/commagray","https://mastodon.social/users/hirojin","https://mastodon.xyz/users/mareklach","https://chaos.social/users/benthor","https://mastodon.social/users/djperreault","https://mastodon.art/users/eylul","https://mastodon.opportunis.me/users/bob","https://tootplanet.space/users/Shutsumon","https://toot.cat/users/woozle","https://mastodon.social/users/StephenLB","https://sleeping.town/users/oct2pus","https://mastodon.indie.host/users/stragu","https://social.coop/users/gilscottfitzgerald","https://icosahedron.website/users/joeld","https://mastodon.social/users/hellion","https://cybre.space/users/cooler_ranch","https://mastodon.social/users/kelsonv","https://mastodon.lat/users/scalpol","https://writing.exchange/users/hnb","https://hex.bz/users/Horst","https://mastodon.social/users/weddle","https://maly.io/users/sonya","https://social.coop/users/medusa","https://mastodon.social/users/DystopianK","https://mstdn.io/users/d_io","https://fosstodon.org/users/brandon","https://fosstodon.org/users/Cando","https://mastodon.host/users/panina","https://floss.social/users/tuxether","https://social.tchncs.de/users/suitbertmonz","https://mastodon.social/users/jrt","https://mastodon.social/users/sirikon","https://mstdn.io/users/yabirgb","https://mastodon.cloud/users/FerdiZ","https://mastodon.social/users/carlchenet","https://social.polonkai.eu/users/calendar_social","https://social.polonkai.eu/users/gergely","https://mastodon.social/users/Jelv","https://mastodon.social/users/srinicame","https://cybre.space/users/mastoabed","https://mastodon.social/users/tagomago","https://lgbt.io/users/bootblackCub","https://niu.moe/users/Nopplyy","https://mastodon.social/users/bpugh","https://www.w3.org/ns/activitystreams#Public"],"type":"Article","uploadMedia":null,"url":"https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/"}
\ No newline at end of file
diff --git a/test/fixtures/httpoison_mock/baptiste.gelex.xyz-user.json b/test/fixtures/httpoison_mock/baptiste.gelex.xyz-user.json
new file mode 100644
index 000000000..b226204ba
--- /dev/null
+++ b/test/fixtures/httpoison_mock/baptiste.gelex.xyz-user.json
@@ -0,0 +1 @@
+{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"Emoji":"toot:Emoji","Hashtag":"as:Hashtag","atomUri":"ostatus:atomUri","conversation":"ostatus:conversation","featured":"toot:featured","focalPoint":{"@container":"@list","@id":"toot:focalPoint"},"inReplyToAtomUri":"ostatus:inReplyToAtomUri","manuallyApprovesFollowers":"as:manuallyApprovesFollowers","movedTo":"as:movedTo","ostatus":"http://ostatus.org#","sensitive":"as:sensitive","toot":"http://joinmastodon.org/ns#"}],"endpoints":{"oauthAuthorizationEndpoint":null,"oauthTokenEndpoint":null,"provideClientKey":null,"proxyUrl":null,"sharedInbox":"https://baptiste.gelez.xyz/inbox/","signClientKey":null},"followers":null,"following":null,"id":"https://baptiste.gelez.xyz/@/BaptisteGelez","inbox":"https://baptiste.gelez.xyz/@/BaptisteGelez/inbox","liked":null,"likes":null,"name":"Baptiste Gelez","outbox":"https://baptiste.gelez.xyz/@/BaptisteGelez/outbox","preferredUsername":"BaptisteGelez","publicKey":{"id":"https://baptiste.gelez.xyz/@/BaptisteGelez#main-key","owner":"https://baptiste.gelez.xyz/@/BaptisteGelez","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA56vPlCAyxZDDy8hNiT1p\n0cdFKnUK/51LiP4nTAxGf5Eb8NmsB2ftDgiDWZfg3LiHkjNcfTDpmN0aZyRxnTg9\nZ4JiQagfynVEbMkcOQhO64OFZpB47GpLtxrb49IcUes/p4ngp/Wkp+arYZSpoSs6\n3I995mZp3ZJ78pNQf1/lV0VIdDe6SqvRj1GmBDXXcecxF0O7rN/WYNO7Jag4i/XA\nU1ToDAMeUFeijRioSNoD3CHkMIu7AN+gqAWzZ21H/ZUvmfxh3WqQi/MDNcUhhA+0\nXv7/dv4S20EGnHadtE7OrBC1IwiHEuRM41zZq0ze9cKpoXg3VK2fiSNrCHlYrA18\n2wIDAQAB\n-----END PUBLIC KEY-----\n"},"shares":null,"source":null,"streams":null,"summary":"Main Plume developer","type":"Person","uploadMedia":null,"url":"https://baptiste.gelez.xyz/@/BaptisteGelez"}
\ No newline at end of file
diff --git a/test/support/httpoison_mock.ex b/test/support/httpoison_mock.ex
index befebad8a..a52d44ed6 100644
--- a/test/support/httpoison_mock.ex
+++ b/test/support/httpoison_mock.ex
@@ -736,6 +736,22 @@ def get("https://shitposter.club/api/statuses/show/7369654.atom", _body, _header
}}
end
+ def get("https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/", _, _) do
+ {:ok,
+ %Response{
+ status_code: 200,
+ body: File.read!("test/fixtures/httpoison_mock/baptiste.gelex.xyz-article.json")
+ }}
+ end
+
+ def get("https://baptiste.gelez.xyz/@/BaptisteGelez", _, _) do
+ {:ok,
+ %Response{
+ status_code: 200,
+ body: File.read!("test/fixtures/httpoison_mock/baptiste.gelex.xyz-user.json")
+ }}
+ end
+
def get(url, body, headers) do
{:error,
"Not implemented the mock response for get #{inspect(url)}, #{inspect(body)}, #{
diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index bc33b4dfc..90c0bd768 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -476,6 +476,15 @@ test "it creates a delete activity and deletes the original object" do
end
end
+ test "it can fetch plume articles" do
+ {:ok, object} =
+ ActivityPub.fetch_object_from_id(
+ "https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/"
+ )
+
+ assert object
+ end
+
describe "update" do
test "it creates an update activity with the new user data" do
user = insert(:user)
From 8472fba2a729f7764ffaf2a4744533c8a95adadb Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier"
Date: Thu, 12 Jul 2018 23:09:42 +0200
Subject: [PATCH 050/100] [Pleroma.Web.ActivityPub.Transmogrifier]: Fix actor
key outside of object
The code here is copied from feature/peertube by lain.
Co-authored-by: lain
---
.../web/activity_pub/transmogrifier.ex | 26 +++++++++----------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 0d2166196..6080303b6 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -13,6 +13,14 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
require Logger
+ def get_actor(%{"actor" => actor}) when is_binary(actor) do
+ actor
+ end
+
+ def get_actor(%{"actor" => actor}) when is_list(actor) do
+ Enum.at(actor, 0)
+ end
+
@doc """
Modifies an incoming AP object (mastodon format) to our internal format.
"""
@@ -28,16 +36,8 @@ def fix_object(object) do
end
def fix_actor(%{"attributedTo" => actor} = object) do
- # attributedTo can be a list in the case of peertube or plume
- actor =
- if is_list(actor) do
- Enum.at(actor, 0)
- else
- actor
- end
-
object
- |> Map.put("actor", actor)
+ |> Map.put("actor", get_actor(%{"actor" => actor}))
end
def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object)
@@ -137,12 +137,12 @@ def fix_content_map(object), do: object
# - emoji
def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data)
when objtype in ["Article", "Note"] do
+ actor = get_actor(data)
+ data = Map.put(data, "actor", actor)
+
with nil <- Activity.get_create_activity_by_object_ap_id(object["id"]),
%User{} = user <- User.get_or_fetch_by_ap_id(data["actor"]) do
- # prefer the activity's actor instead of attributedTo
- object =
- fix_object(data["object"])
- |> Map.put("actor", data["actor"])
+ object = fix_object(data["object"])
params = %{
to: data["to"],
From 7501481db4be56cf7b5babeeebeb7b96273ae4db Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier"
Date: Thu, 12 Jul 2018 23:25:44 +0200
Subject: [PATCH 051/100] [Pleroma.Web.ActivityPub.Transmogrifier] Add Person
finding
---
lib/pleroma/web/activity_pub/transmogrifier.ex | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 6080303b6..2ebc526df 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -21,6 +21,11 @@ def get_actor(%{"actor" => actor}) when is_list(actor) do
Enum.at(actor, 0)
end
+ def get_actor(%{"actor" => actor_list}) do
+ Enum.find(actor_list, fn %{"type" => type} -> type == "Person" end)
+ |> Map.get("id")
+ end
+
@doc """
Modifies an incoming AP object (mastodon format) to our internal format.
"""
From f944f8157ac078aef5edda64059a5d6d3837bf53 Mon Sep 17 00:00:00 2001
From: hakabahitoyo
Date: Sat, 14 Jul 2018 00:21:38 +0900
Subject: [PATCH 052/100] /api/v1/suggestions endpoint
---
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 5 +++++
lib/pleroma/web/router.ex | 2 ++
2 files changed, 7 insertions(+)
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 09e6b0b59..0184d52c8 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -1070,4 +1070,9 @@ def errors(conn, _) do
|> put_status(500)
|> json("Something went wrong")
end
+
+ def suggestions(conn, _) do
+ conn
+ |> json("SUGGESTIONS!")
+ end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 34652cdde..f1c08c681 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -140,6 +140,8 @@ def user_fetcher(username) do
get("/domain_blocks", MastodonAPIController, :domain_blocks)
post("/domain_blocks", MastodonAPIController, :block_domain)
delete("/domain_blocks", MastodonAPIController, :unblock_domain)
+
+ get("/suggestions", MastodonAPIController, :suggestions)
end
scope "/api/web", Pleroma.Web.MastodonAPI do
From 4a21c1b343c3f62d78da1651ed490daf4dde5d97 Mon Sep 17 00:00:00 2001
From: hakabahitoyo
Date: Sat, 14 Jul 2018 00:44:18 +0900
Subject: [PATCH 053/100] mock /api/v1/suggestions
---
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 0184d52c8..0f50eba84 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -1071,8 +1071,12 @@ def errors(conn, _) do
|> json("Something went wrong")
end
- def suggestions(conn, _) do
+ def suggestions(%{assigns: %{user: user}} = conn, _) do
+ res = %{
+ host: (String.replace Web.base_url(), "https://", ""),
+ user: user.nickname
+ }
conn
- |> json("SUGGESTIONS!")
+ |> json(res)
end
end
From 3812b627ca546287e20a55cd49544a36eefffa0b Mon Sep 17 00:00:00 2001
From: hakabahitoyo
Date: Sat, 14 Jul 2018 00:52:23 +0900
Subject: [PATCH 054/100] better mock /api/v1/suggestions
---
.../mastodon_api/mastodon_api_controller.ex | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 0f50eba84..fb334352d 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -1072,10 +1072,20 @@ def errors(conn, _) do
end
def suggestions(%{assigns: %{user: user}} = conn, _) do
- res = %{
- host: (String.replace Web.base_url(), "https://", ""),
- user: user.nickname
- }
+ res = [
+ %{
+ username: "vaginaplant",
+ acct: "vaginaplant@3.distsn.org",
+ display_name: "Hakaba Hitoyo",
+ note: "Recommendation Fairness Warrior",
+ avatar: "https://3.distsn.org/media/1c0cbe9d-8b87-496f-b964-1af8116b8f67/D38B0A8B021DC5565D06CF40EBB744E4B7CF8F7F16347094F9CD469348DCC267.jpeg",
+ avatar_static: "https://3.distsn.org/media/1c0cbe9d-8b87-496f-b964-1af8116b8f67/D38B0A8B021DC5565D06CF40EBB744E4B7CF8F7F16347094F9CD469348DCC267.jpeg"
+ },
+ %{
+ username: user.nickname,
+ acct: user.nickname <> "@" <> (String.replace Web.base_url(), "https://", "")
+ }
+ ]
conn
|> json(res)
end
From eb0afda3a72aa08a991e25b05c98ae798655f413 Mon Sep 17 00:00:00 2001
From: hakabahitoyo
Date: Sat, 14 Jul 2018 10:04:37 +0900
Subject: [PATCH 055/100] http access to third party user recommendation
---
.../mastodon_api/mastodon_api_controller.ex | 30 +++++++++----------
1 file changed, 14 insertions(+), 16 deletions(-)
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index fb334352d..4f48a141b 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -11,6 +11,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
import Ecto.Query
require Logger
+ @httpoison Application.get_env(:pleroma, :httpoison)
+
action_fallback(:errors)
def create_app(conn, params) do
@@ -1072,21 +1074,17 @@ def errors(conn, _) do
end
def suggestions(%{assigns: %{user: user}} = conn, _) do
- res = [
- %{
- username: "vaginaplant",
- acct: "vaginaplant@3.distsn.org",
- display_name: "Hakaba Hitoyo",
- note: "Recommendation Fairness Warrior",
- avatar: "https://3.distsn.org/media/1c0cbe9d-8b87-496f-b964-1af8116b8f67/D38B0A8B021DC5565D06CF40EBB744E4B7CF8F7F16347094F9CD469348DCC267.jpeg",
- avatar_static: "https://3.distsn.org/media/1c0cbe9d-8b87-496f-b964-1af8116b8f67/D38B0A8B021DC5565D06CF40EBB744E4B7CF8F7F16347094F9CD469348DCC267.jpeg"
- },
- %{
- username: user.nickname,
- acct: user.nickname <> "@" <> (String.replace Web.base_url(), "https://", "")
- }
- ]
- conn
- |> json(res)
+ host = String.replace Web.base_url(), "https://", ""
+ user = user.nickname
+ api = "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-filtered-api.cgi?{{host}}+{{user}}"
+ url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user)
+ with {:ok, %{status_code: 200, body: body}} <-
+ @httpoison.get(url),
+ {:ok, data} <- Jason.decode(body) do
+ conn
+ |> json(data)
+ else
+ e -> Logger.error("Could not decode user at fetch #{url}, #{inspect(e)}")
+ end
end
end
From 127882a5d51d8243eae44ad7ed73bf6010804760 Mon Sep 17 00:00:00 2001
From: hakabahitoyo
Date: Sat, 14 Jul 2018 11:41:09 +0900
Subject: [PATCH 056/100] configurable
---
config/config.exs | 3 +++
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 4 +++-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/config/config.exs b/config/config.exs
index 0616fe4fb..9299d0dc6 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -95,6 +95,9 @@
ip: {0, 0, 0, 0},
port: 9999
+config :pleroma, :suggestions,
+ third_party_engine: "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-filtered-api.cgi?{{host}}+{{user}}"
+
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 4f48a141b..fd60db3d6 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -1073,10 +1073,12 @@ def errors(conn, _) do
|> json("Something went wrong")
end
+ @suggestions Application.get_env(:pleroma, :suggestions)
+
def suggestions(%{assigns: %{user: user}} = conn, _) do
host = String.replace Web.base_url(), "https://", ""
user = user.nickname
- api = "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-filtered-api.cgi?{{host}}+{{user}}"
+ api = Keyword.get(@suggestions, :third_party_engine, "")
url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user)
with {:ok, %{status_code: 200, body: body}} <-
@httpoison.get(url),
From f96244006e22740036effd9b6ef3ac753250494e Mon Sep 17 00:00:00 2001
From: hakabahitoyo
Date: Sat, 14 Jul 2018 12:07:09 +0900
Subject: [PATCH 057/100] change api
---
config/config.exs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/config.exs b/config/config.exs
index 9299d0dc6..1f69e6244 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -96,7 +96,7 @@
port: 9999
config :pleroma, :suggestions,
- third_party_engine: "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-filtered-api.cgi?{{host}}+{{user}}"
+ third_party_engine: "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}"
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
From 2eeaf01627efb2a13d73f4cde764d3cb1fef3e98 Mon Sep 17 00:00:00 2001
From: hakabahitoyo
Date: Sat, 14 Jul 2018 14:03:30 +0900
Subject: [PATCH 058/100] id field
---
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index fd60db3d6..d87a6cb19 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -1081,10 +1081,13 @@ def suggestions(%{assigns: %{user: user}} = conn, _) do
api = Keyword.get(@suggestions, :third_party_engine, "")
url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user)
with {:ok, %{status_code: 200, body: body}} <-
- @httpoison.get(url),
+ @httpoison.get(url, [], [timeout: 300000, recv_timeout: 300000]),
{:ok, data} <- Jason.decode(body) do
+ data2 = Enum.slice(data, 0, 40) |> Enum.map(fn(x) ->
+ Map.put(x, "id", User.get_or_fetch(x["acct"]).id)
+ end)
conn
- |> json(data)
+ |> json(data2)
else
e -> Logger.error("Could not decode user at fetch #{url}, #{inspect(e)}")
end
From e7c580828c109247de11624bae8c7286b02441e5 Mon Sep 17 00:00:00 2001
From: hakabahitoyo
Date: Sun, 15 Jul 2018 20:36:26 +0900
Subject: [PATCH 059/100] format
---
config/config.exs | 3 ++-
.../web/mastodon_api/mastodon_api_controller.ex | 14 +++++++++-----
2 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index 1f69e6244..3583e7b46 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -96,7 +96,8 @@
port: 9999
config :pleroma, :suggestions,
- third_party_engine: "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}"
+ third_party_engine:
+ "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}"
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index d87a6cb19..e397b911d 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -1076,16 +1076,20 @@ def errors(conn, _) do
@suggestions Application.get_env(:pleroma, :suggestions)
def suggestions(%{assigns: %{user: user}} = conn, _) do
- host = String.replace Web.base_url(), "https://", ""
+ host = String.replace(Web.base_url(), "https://", "")
user = user.nickname
api = Keyword.get(@suggestions, :third_party_engine, "")
url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user)
+
with {:ok, %{status_code: 200, body: body}} <-
- @httpoison.get(url, [], [timeout: 300000, recv_timeout: 300000]),
+ @httpoison.get(url, [], timeout: 300_000, recv_timeout: 300_000),
{:ok, data} <- Jason.decode(body) do
- data2 = Enum.slice(data, 0, 40) |> Enum.map(fn(x) ->
- Map.put(x, "id", User.get_or_fetch(x["acct"]).id)
- end)
+ data2 =
+ Enum.slice(data, 0, 40)
+ |> Enum.map(fn x ->
+ Map.put(x, "id", User.get_or_fetch(x["acct"]).id)
+ end)
+
conn
|> json(data2)
else
From f10291a1d322eed22ae594024c9d3d9011a7d5fe Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Tue, 17 Jul 2018 03:35:08 +0000
Subject: [PATCH 060/100] upload: use generic Document object type instead of
Image (mastodon compatibility)
Mastodon does not use the object name as alt text when the object is an Image.
---
lib/pleroma/upload.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index 43df0d418..92a89e296 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -19,7 +19,7 @@ def store(%Plug.Upload{} = file, should_dedupe) do
end
%{
- "type" => "Image",
+ "type" => "Document",
"url" => [
%{
"type" => "Link",
From cd19d37a90cfceb8bafb380fd8ff8e5a765b7e3d Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Tue, 17 Jul 2018 03:36:11 +0000
Subject: [PATCH 061/100] mastodon api: use object name as alt text
---
lib/pleroma/web/mastodon_api/views/status_view.ex | 3 ++-
1 file changed, 2 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 4c20581d6..5dbd59dd9 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -169,7 +169,8 @@ def render("attachment.json", %{attachment: attachment}) do
remote_url: href,
preview_url: MediaProxy.url(href),
text_url: href,
- type: type
+ type: type,
+ description: attachment["name"]
}
end
From 99c0252314fc3391e6fae58bdad8110d0a053fb9 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Tue, 17 Jul 2018 03:36:50 +0000
Subject: [PATCH 062/100] mastodon api: support descriptions in media api, add
PUT endpoint for updating metadata about a media upload
---
.../mastodon_api/mastodon_api_controller.ex | 35 ++++++++++++++++---
lib/pleroma/web/router.ex | 1 +
2 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 09e6b0b59..f270e1146 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -1,6 +1,6 @@
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller
- alias Pleroma.{Repo, Activity, User, Notification, Stats}
+ alias Pleroma.{Repo, Object, Activity, User, Notification, Stats}
alias Pleroma.Web
alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView, ListView}
alias Pleroma.Web.ActivityPub.ActivityPub
@@ -428,16 +428,43 @@ def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
render(conn, AccountView, "relationships.json", %{user: user, targets: targets})
end
- def upload(%{assigns: %{user: _}} = conn, %{"file" => file}) do
- with {:ok, object} <- ActivityPub.upload(file) do
+ def update_media(%{assigns: %{user: _}} = conn, data) do
+ with %Object{} = object <- Repo.get(Object, data["id"]),
+ true <- is_binary(data["description"]),
+ description <- data["description"] do
+ new_data = %{object.data | "name" => description}
+
+ change = Object.change(object, %{data: new_data})
+ {:ok, media_obj} = Repo.update(change)
+
data =
- object.data
+ new_data
|> Map.put("id", object.id)
render(conn, StatusView, "attachment.json", %{attachment: data})
end
end
+ def upload(%{assigns: %{user: _}} = conn, %{"file" => file} = data) do
+ with {:ok, object} <- ActivityPub.upload(file) do
+ objdata =
+ if Map.has_key?(data, "description") do
+ Map.put(object.data, "name", data["description"])
+ else
+ object.data
+ end
+
+ change = Object.change(object, %{data: objdata})
+ {:ok, object} = Repo.update(change)
+
+ objdata =
+ objdata
+ |> Map.put("id", object.id)
+
+ render(conn, StatusView, "attachment.json", %{attachment: objdata})
+ end
+ end
+
def favourited_by(conn, %{"id" => id}) do
with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do
q = from(u in User, where: u.ap_id in ^likes)
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 34652cdde..fc7a947aa 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -127,6 +127,7 @@ def user_fetcher(username) do
get("/notifications/:id", MastodonAPIController, :get_notification)
post("/media", MastodonAPIController, :upload)
+ put("/media/:id", MastodonAPIController, :update_media)
get("/lists", MastodonAPIController, :get_lists)
get("/lists/:id", MastodonAPIController, :get_list)
From 489453c2467b12970258927015209c9895d5cf6e Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Tue, 17 Jul 2018 03:37:26 +0000
Subject: [PATCH 063/100] tests: verify media description api support is
working
---
test/web/mastodon_api/mastodon_api_controller_test.exs | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index d1812457d..9e33c1d04 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -736,16 +736,19 @@ test "media upload", %{conn: conn} do
filename: "an_image.jpg"
}
+ desc = "Description of the image"
+
user = insert(:user)
conn =
conn
|> assign(:user, user)
- |> post("/api/v1/media", %{"file" => file})
+ |> post("/api/v1/media", %{"file" => file, "description" => desc})
assert media = json_response(conn, 200)
assert media["type"] == "image"
+ assert media["description"] == desc
end
test "hashtag timeline", %{conn: conn} do
From 18cac1e36bd91e1554de1f495a7f178b27b2fbc3 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Tue, 17 Jul 2018 03:37:40 +0000
Subject: [PATCH 064/100] test: mastodon attachments: update for added
description field
---
test/web/mastodon_api/status_view_test.exs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/test/web/mastodon_api/status_view_test.exs b/test/web/mastodon_api/status_view_test.exs
index d28c3cbad..03c798bef 100644
--- a/test/web/mastodon_api/status_view_test.exs
+++ b/test/web/mastodon_api/status_view_test.exs
@@ -102,7 +102,8 @@ test "attachments" do
url: "someurl",
remote_url: "someurl",
preview_url: "someurl",
- text_url: "someurl"
+ text_url: "someurl",
+ description: nil
}
assert expected == StatusView.render("attachment.json", %{attachment: object})
From df3233e7e798563afe4b5a937b0bdd13e101973b Mon Sep 17 00:00:00 2001
From: Hakaba Hitoyo
Date: Tue, 17 Jul 2018 13:20:58 +0900
Subject: [PATCH 065/100] improve getting host name
---
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index e397b911d..2f8139fe6 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -1076,7 +1076,11 @@ def errors(conn, _) do
@suggestions Application.get_env(:pleroma, :suggestions)
def suggestions(%{assigns: %{user: user}} = conn, _) do
- host = String.replace(Web.base_url(), "https://", "")
+ host =
+ Application.get_env(:pleroma, Pleroma.Web.Endpoint)
+ |> Keyword.get(:url)
+ |> Keyword.get(:host)
+
user = user.nickname
api = Keyword.get(@suggestions, :third_party_engine, "")
url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user)
From d76f0d87bebd655bcbc4259a47a4ee8fd4f00887 Mon Sep 17 00:00:00 2001
From: Hakaba Hitoyo
Date: Tue, 17 Jul 2018 16:45:18 +0900
Subject: [PATCH 066/100] do nothing if configuration is skipped
---
config/config.exs | 6 +--
.../mastodon_api/mastodon_api_controller.ex | 41 +++++++++++--------
2 files changed, 26 insertions(+), 21 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index 3583e7b46..4d5a7564a 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -95,9 +95,9 @@
ip: {0, 0, 0, 0},
port: 9999
-config :pleroma, :suggestions,
- third_party_engine:
- "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}"
+# config :pleroma, :suggestions,
+# third_party_engine:
+# "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}"
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 2f8139fe6..ac8f794e9 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -1076,28 +1076,33 @@ def errors(conn, _) do
@suggestions Application.get_env(:pleroma, :suggestions)
def suggestions(%{assigns: %{user: user}} = conn, _) do
- host =
- Application.get_env(:pleroma, Pleroma.Web.Endpoint)
- |> Keyword.get(:url)
- |> Keyword.get(:host)
+ api = Keyword.get(@suggestions, :third_party_engine, false)
- user = user.nickname
- api = Keyword.get(@suggestions, :third_party_engine, "")
- url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user)
+ if api do
+ host =
+ Application.get_env(:pleroma, Pleroma.Web.Endpoint)
+ |> Keyword.get(:url)
+ |> Keyword.get(:host)
- with {:ok, %{status_code: 200, body: body}} <-
- @httpoison.get(url, [], timeout: 300_000, recv_timeout: 300_000),
- {:ok, data} <- Jason.decode(body) do
- data2 =
- Enum.slice(data, 0, 40)
- |> Enum.map(fn x ->
- Map.put(x, "id", User.get_or_fetch(x["acct"]).id)
- end)
+ user = user.nickname
+ url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user)
- conn
- |> json(data2)
+ with {:ok, %{status_code: 200, body: body}} <-
+ @httpoison.get(url, [], timeout: 300_000, recv_timeout: 300_000),
+ {:ok, data} <- Jason.decode(body) do
+ data2 =
+ Enum.slice(data, 0, 40)
+ |> Enum.map(fn x ->
+ Map.put(x, "id", User.get_or_fetch(x["acct"]).id)
+ end)
+
+ conn
+ |> json(data2)
+ else
+ e -> Logger.error("Could not decode user at fetch #{url}, #{inspect(e)}")
+ end
else
- e -> Logger.error("Could not decode user at fetch #{url}, #{inspect(e)}")
+ json(conn, [])
end
end
end
From 2b7b1b3e6b276e1db7e610d46298f74a0f0c70f1 Mon Sep 17 00:00:00 2001
From: Hakaba Hitoyo
Date: Tue, 17 Jul 2018 16:56:30 +0900
Subject: [PATCH 067/100] add suggestionsThirdPartyEngine into
/nodeinfo/2.0.json
---
lib/pleroma/web/nodeinfo/nodeinfo_controller.ex | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
index 12aca4a10..77401c554 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
@@ -21,6 +21,7 @@ def schemas(conn, _params) do
def nodeinfo(conn, %{"version" => "2.0"}) do
instance = Application.get_env(:pleroma, :instance)
media_proxy = Application.get_env(:pleroma, :media_proxy)
+ suggestions = Application.get_env(:pleroma, :suggestions)
stats = Stats.get_stats()
response = %{
@@ -43,7 +44,8 @@ def nodeinfo(conn, %{"version" => "2.0"}) do
},
metadata: %{
nodeName: Keyword.get(instance, :name),
- mediaProxy: Keyword.get(media_proxy, :enabled)
+ mediaProxy: Keyword.get(media_proxy, :enabled),
+ suggestionsThirdPartyEngine: Keyword.get(@suggestions, :third_party_engine, false)
}
}
From 8ff336e02a5a4861ba856b3d10e57cb44f1f3a45 Mon Sep 17 00:00:00 2001
From: Hakaba Hitoyo
Date: Tue, 17 Jul 2018 17:00:14 +0900
Subject: [PATCH 068/100] debug
---
lib/pleroma/web/nodeinfo/nodeinfo_controller.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
index 77401c554..5f7d6e86e 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
@@ -45,7 +45,7 @@ def nodeinfo(conn, %{"version" => "2.0"}) do
metadata: %{
nodeName: Keyword.get(instance, :name),
mediaProxy: Keyword.get(media_proxy, :enabled),
- suggestionsThirdPartyEngine: Keyword.get(@suggestions, :third_party_engine, false)
+ suggestionsThirdPartyEngine: Keyword.get(suggestions, :third_party_engine, false)
}
}
From 091b7925d39110d2ade4e1db9105a3404dc91da1 Mon Sep 17 00:00:00 2001
From: Hakaba Hitoyo
Date: Tue, 17 Jul 2018 17:20:13 +0900
Subject: [PATCH 069/100] debug
---
config/config.exs | 1 +
1 file changed, 1 insertion(+)
diff --git a/config/config.exs b/config/config.exs
index 4d5a7564a..baaf43c7c 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -95,6 +95,7 @@
ip: {0, 0, 0, 0},
port: 9999
+config :pleroma, :suggestions, third_party_engine: false
# config :pleroma, :suggestions,
# third_party_engine:
# "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}"
From 5b4a21317de6a32001699b33964c7eaeb4f0bec8 Mon Sep 17 00:00:00 2001
From: Hakaba Hitoyo
Date: Tue, 17 Jul 2018 17:29:18 +0900
Subject: [PATCH 070/100] correct error message
---
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index ac8f794e9..2ad6521cc 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -1099,7 +1099,7 @@ def suggestions(%{assigns: %{user: user}} = conn, _) do
conn
|> json(data2)
else
- e -> Logger.error("Could not decode user at fetch #{url}, #{inspect(e)}")
+ e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
end
else
json(conn, [])
From cf219b6addab9fffca0b2e996fa8de1d7fbcd198 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Tue, 17 Jul 2018 15:10:14 +0000
Subject: [PATCH 071/100] config: make instance description configurable
---
config/config.exs | 1 +
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 2 +-
lib/pleroma/web/nodeinfo/nodeinfo_controller.ex | 4 +++-
3 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index 0616fe4fb..51b953a94 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -52,6 +52,7 @@
version: version,
name: "Pleroma",
email: "example@example.com",
+ description: "A Pleroma instance, an alternative fediverse server",
limit: 5000,
upload_limit: 16_000_000,
registrations_open: true,
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 09e6b0b59..f521960c2 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -125,7 +125,7 @@ def masto_instance(conn, _params) do
response = %{
uri: Web.base_url(),
title: Keyword.get(@instance, :name),
- description: "A Pleroma instance, an alternative fediverse server",
+ description: Keyword.get(@instance, :description),
version: "#{@mastodon_api_level} (compatible; #{Keyword.get(@instance, :version)})",
email: Keyword.get(@instance, :email),
urls: %{
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
index 12aca4a10..7c67bbf1c 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
@@ -43,7 +43,9 @@ def nodeinfo(conn, %{"version" => "2.0"}) do
},
metadata: %{
nodeName: Keyword.get(instance, :name),
- mediaProxy: Keyword.get(media_proxy, :enabled)
+ nodeDescription: Keyword.get(instance, :description),
+ mediaProxy: Keyword.get(media_proxy, :enabled),
+ private: !Keyword.get(instance, :public, true)
}
}
From b23630076f8d1597c417e98dfc702c972b29a82c Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Tue, 17 Jul 2018 15:20:39 +0000
Subject: [PATCH 072/100] TwitterAPI: present pleroma frontend config in API
---
config/config.exs | 12 ++++++++++++
.../controllers/util_controller.ex | 19 ++++++++++++++++++-
2 files changed, 30 insertions(+), 1 deletion(-)
diff --git a/config/config.exs b/config/config.exs
index 51b953a94..9e2a66620 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -61,6 +61,18 @@
public: true,
quarantined_instances: []
+config :pleroma, :fe,
+ theme: "pleroma-dark",
+ logo: "/static/logo.png",
+ background: "/static/aurora_borealis.jpg",
+ redirect_root_no_login: "/main/all",
+ redirect_root_login: "/main/friends",
+ show_instance_panel: true,
+ show_who_to_follow_panel: false,
+ who_to_follow_provider: "https://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-osa-api.cgi?{{host}}+{{user}}",
+ who_to_follow_link: "https://vinayaka.distsn.org/?{{host}}+{{user}}",
+ scope_options_enabled: false
+
config :pleroma, :activitypub,
accept_blocks: true,
unfollow_blocked: true,
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index 7a0c37ce9..47fc79350 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -126,6 +126,8 @@ def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}
end
@instance Application.get_env(:pleroma, :instance)
+ @instance_fe Application.get_env(:pleroma, :fe)
+ @instance_chat Application.get_env(:pleroma, :chat)
def config(conn, _params) do
case get_format(conn) do
"xml" ->
@@ -148,9 +150,24 @@ def config(conn, _params) do
json(conn, %{
site: %{
name: Keyword.get(@instance, :name),
+ description: Keyword.get(@instance, :description),
server: Web.base_url(),
textlimit: to_string(Keyword.get(@instance, :limit)),
- closed: if(Keyword.get(@instance, :registrations_open), do: "0", else: "1")
+ closed: if(Keyword.get(@instance, :registrations_open), do: "0", else: "1"),
+ private: if(Keyword.get(@instance, :public, true), do: "0", else: "1"),
+ pleromafe: %{
+ theme: Keyword.get(@instance_fe, :theme),
+ background: Keyword.get(@instance_fe, :background),
+ logo: Keyword.get(@instance_fe, :logo),
+ redirectRootNoLogin: Keyword.get(@instance_fe, :redirect_root_no_login),
+ redirectRootLogin: Keyword.get(@instance_fe, :redirect_root_login),
+ chatDisabled: !Keyword.get(@instance_chat, :enabled),
+ showInstanceSpecificPanel: Keyword.get(@instance_fe, :show_instance_panel),
+ showWhoToFollowPanel: Keyword.get(@instance_fe, :show_who_to_follow_panel),
+ scopeOptionsEnabled: Keyword.get(@instance_fe, :scope_options_enabled),
+ whoToFollowProvider: Keyword.get(@instance_fe, :who_to_follow_provider),
+ whoToFollowLink: Keyword.get(@instance_fe, :who_to_follow_link)
+ }
}
})
end
From 2b3f049b06aa2d248028890bd8fdfbeb9e1e279e Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Wed, 18 Jul 2018 00:05:36 +0000
Subject: [PATCH 073/100] config: formatting
---
config/config.exs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/config/config.exs b/config/config.exs
index 9e2a66620..922acea4b 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -69,7 +69,8 @@
redirect_root_login: "/main/friends",
show_instance_panel: true,
show_who_to_follow_panel: false,
- who_to_follow_provider: "https://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-osa-api.cgi?{{host}}+{{user}}",
+ who_to_follow_provider:
+ "https://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-osa-api.cgi?{{host}}+{{user}}",
who_to_follow_link: "https://vinayaka.distsn.org/?{{host}}+{{user}}",
scope_options_enabled: false
From e4dd58307a692aaa503fccc9cdf9ebae61293f6f Mon Sep 17 00:00:00 2001
From: Hakaba Hitoyo
Date: Wed, 18 Jul 2018 09:58:59 +0900
Subject: [PATCH 074/100] better configuration
---
config/config.exs | 8 ++++----
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 4 ++--
lib/pleroma/web/nodeinfo/nodeinfo_controller.ex | 1 +
3 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index baaf43c7c..c0a75b786 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -95,10 +95,10 @@
ip: {0, 0, 0, 0},
port: 9999
-config :pleroma, :suggestions, third_party_engine: false
-# config :pleroma, :suggestions,
-# third_party_engine:
-# "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}"
+config :pleroma, :suggestions,
+ enabled: false,
+ third_party_engine:
+ "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}"
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 2ad6521cc..5b79f9600 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -1076,9 +1076,9 @@ def errors(conn, _) do
@suggestions Application.get_env(:pleroma, :suggestions)
def suggestions(%{assigns: %{user: user}} = conn, _) do
- api = Keyword.get(@suggestions, :third_party_engine, false)
+ if Keyword.get(@suggestions, :enabled, false) do
+ api = Keyword.get(@suggestions, :third_party_engine, false)
- if api do
host =
Application.get_env(:pleroma, Pleroma.Web.Endpoint)
|> Keyword.get(:url)
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
index 5f7d6e86e..e80e63f27 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
@@ -45,6 +45,7 @@ def nodeinfo(conn, %{"version" => "2.0"}) do
metadata: %{
nodeName: Keyword.get(instance, :name),
mediaProxy: Keyword.get(media_proxy, :enabled),
+ suggestions: Keyword.get(suggestions, :enabled, false),
suggestionsThirdPartyEngine: Keyword.get(suggestions, :third_party_engine, false)
}
}
From b12d17d2ce270e0d964f599efd4f16fba95b17ce Mon Sep 17 00:00:00 2001
From: Hakaba Hitoyo
Date: Wed, 18 Jul 2018 13:36:20 +0900
Subject: [PATCH 075/100] configurable timeout
---
config/config.exs | 3 ++-
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 5 +++--
lib/pleroma/web/nodeinfo/nodeinfo_controller.ex | 7 +++++--
3 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index c0a75b786..8fb7b7c22 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -98,7 +98,8 @@
config :pleroma, :suggestions,
enabled: false,
third_party_engine:
- "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}"
+ "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}",
+ timeout: 300_000
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 5b79f9600..396f11a70 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -1077,7 +1077,8 @@ def errors(conn, _) do
def suggestions(%{assigns: %{user: user}} = conn, _) do
if Keyword.get(@suggestions, :enabled, false) do
- api = Keyword.get(@suggestions, :third_party_engine, false)
+ api = Keyword.get(@suggestions, :third_party_engine, "")
+ timeout = Keyword.get(@suggestions, :timeout, 5000)
host =
Application.get_env(:pleroma, Pleroma.Web.Endpoint)
@@ -1088,7 +1089,7 @@ def suggestions(%{assigns: %{user: user}} = conn, _) do
url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user)
with {:ok, %{status_code: 200, body: body}} <-
- @httpoison.get(url, [], timeout: 300_000, recv_timeout: 300_000),
+ @httpoison.get(url, [], timeout: timeout, recv_timeout: timeout),
{:ok, data} <- Jason.decode(body) do
data2 =
Enum.slice(data, 0, 40)
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
index e80e63f27..42d322f89 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
@@ -45,8 +45,11 @@ def nodeinfo(conn, %{"version" => "2.0"}) do
metadata: %{
nodeName: Keyword.get(instance, :name),
mediaProxy: Keyword.get(media_proxy, :enabled),
- suggestions: Keyword.get(suggestions, :enabled, false),
- suggestionsThirdPartyEngine: Keyword.get(suggestions, :third_party_engine, false)
+ suggestions: %{
+ enabled: Keyword.get(suggestions, :enabled, false),
+ thirdPartyEngine: Keyword.get(suggestions, :third_party_engine, ""),
+ timeout: Keyword.get(suggestions, :timeout, 5000)
+ }
}
}
From 9c2afb2e71d7ed072fbb43e2ef002e0d629ca877 Mon Sep 17 00:00:00 2001
From: hakabahitoyo
Date: Sat, 21 Jul 2018 01:44:35 +0900
Subject: [PATCH 076/100] improve test
---
.../activity_pub/activity_pub_controller_test.exs | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs
index 25b47ee31..b9294efe1 100644
--- a/test/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/web/activity_pub/activity_pub_controller_test.exs
@@ -50,6 +50,20 @@ test "it inserts an incoming activity into the database", %{conn: conn} do
end
end
+ describe "/users/:nickname/outbox" do
+ test "it returns a note action in a collection", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ user = User.get_cached_by_ap_id(note_activity.data["actor"])
+
+ conn =
+ conn
+ |> put_req_header("Accept", "application/activity+json")
+ |> get("/users/#{user.nickname}/outbox")
+
+ assert response(conn, 200) =~ note_activity.data["object"]["content"]
+ end
+ end
+
describe "/users/:nickname/followers" do
test "it returns the followers in a collection", %{conn: conn} do
user = insert(:user)
From 908cefd84a6cf9bddd04ad9521be2ff5b7d8f379 Mon Sep 17 00:00:00 2001
From: hakabahitoyo
Date: Sat, 21 Jul 2018 02:19:20 +0900
Subject: [PATCH 077/100] debug
---
test/web/activity_pub/activity_pub_controller_test.exs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs
index b9294efe1..1daa5627c 100644
--- a/test/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/web/activity_pub/activity_pub_controller_test.exs
@@ -57,7 +57,7 @@ test "it returns a note action in a collection", %{conn: conn} do
conn =
conn
- |> put_req_header("Accept", "application/activity+json")
+ |> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}/outbox")
assert response(conn, 200) =~ note_activity.data["object"]["content"]
From 9c1b6f11c501756362342b5652769c9dfd12e77c Mon Sep 17 00:00:00 2001
From: hakabahitoyo
Date: Sat, 21 Jul 2018 02:57:56 +0900
Subject: [PATCH 078/100] improve test
---
test/support/factory.ex | 20 +++++++++++++++++++
.../activity_pub_controller_test.exs | 14 ++++++++++++-
2 files changed, 33 insertions(+), 1 deletion(-)
diff --git a/test/support/factory.ex b/test/support/factory.ex
index b2e98c8d1..e9b4beb7d 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -65,6 +65,26 @@ def note_activity_factory do
}
end
+ def announce_activity_factory do
+ note_activity = insert(:note_activity)
+ user = insert(:user)
+
+ data = %{
+ "type" => "Announce",
+ "actor" => note_activity.actor,
+ "object" => note_activity.data["id"],
+ "to" => [user.follower_address, note_activity.data["actor"]],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "context" => note_activity.data["context"]
+ }
+
+ %Pleroma.Activity{
+ data: data,
+ actor: user.ap_id,
+ recipients: data["to"]
+ }
+ end
+
def like_activity_factory do
note_activity = insert(:note_activity)
user = insert(:user)
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs
index 1daa5627c..8a1c0d361 100644
--- a/test/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/web/activity_pub/activity_pub_controller_test.exs
@@ -51,7 +51,7 @@ test "it inserts an incoming activity into the database", %{conn: conn} do
end
describe "/users/:nickname/outbox" do
- test "it returns a note action in a collection", %{conn: conn} do
+ test "it returns a note activity in a collection", %{conn: conn} do
note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
@@ -62,6 +62,18 @@ test "it returns a note action in a collection", %{conn: conn} do
assert response(conn, 200) =~ note_activity.data["object"]["content"]
end
+
+ test "it returns an announce activity in a collection", %{conn: conn} do
+ announce_activity = insert(:announce_activity)
+ user = User.get_cached_by_ap_id(announce_activity.data["actor"])
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get("/users/#{user.nickname}/outbox")
+
+ assert response(conn, 200) =~ announce_activity.data["object"]
+ end
end
describe "/users/:nickname/followers" do
From 2890aef9e8924a6ddc170034679b7313372b3567 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Tue, 31 Jul 2018 21:41:18 +0000
Subject: [PATCH 079/100] activitypub: add digest header to outbound messages
and sign it
---
lib/pleroma/web/activity_pub/activity_pub.ex | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 464832a1e..90a39ce69 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -641,8 +641,14 @@ def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
Logger.info("Federating #{id} to #{inbox}")
host = URI.parse(inbox).host
+ digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
+
signature =
- Pleroma.Web.HTTPSignatures.sign(actor, %{host: host, "content-length": byte_size(json)})
+ Pleroma.Web.HTTPSignatures.sign(actor, %{
+ host: host,
+ "content-length": byte_size(json),
+ digest: digest
+ })
@httpoison.post(
inbox,
From 8da406afa2a53f1769e2a1a3a5e99571f30f8dea Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Tue, 31 Jul 2018 23:17:47 +0000
Subject: [PATCH 080/100] activitypub: verify remote http signature digests by
recomputing the digest and replacing the digest header
---
lib/pleroma/plugs/digest.ex | 10 ++++++++++
lib/pleroma/plugs/http_signature.ex | 10 ++++++++++
lib/pleroma/web/endpoint.ex | 3 ++-
3 files changed, 22 insertions(+), 1 deletion(-)
create mode 100644 lib/pleroma/plugs/digest.ex
diff --git a/lib/pleroma/plugs/digest.ex b/lib/pleroma/plugs/digest.ex
new file mode 100644
index 000000000..9d6bbb085
--- /dev/null
+++ b/lib/pleroma/plugs/digest.ex
@@ -0,0 +1,10 @@
+defmodule Pleroma.Web.Plugs.DigestPlug do
+ alias Plug.Conn
+ require Logger
+
+ def read_body(conn, opts) do
+ {:ok, body, conn} = Conn.read_body(conn, opts)
+ digest = "SHA-256=" <> (:crypto.hash(:sha256, body) |> Base.encode64())
+ {:ok, body, Conn.assign(conn, :digest, digest)}
+ end
+end
diff --git a/lib/pleroma/plugs/http_signature.ex b/lib/pleroma/plugs/http_signature.ex
index 38bcd3a78..9e53371b7 100644
--- a/lib/pleroma/plugs/http_signature.ex
+++ b/lib/pleroma/plugs/http_signature.ex
@@ -19,6 +19,8 @@ def call(conn, _opts) do
cond do
signature && String.contains?(signature, user) ->
+ # set (request-target) header to the appropriate value
+ # we also replace the digest header with the one we computed
conn =
conn
|> put_req_header(
@@ -26,6 +28,14 @@ def call(conn, _opts) do
String.downcase("#{conn.method}") <> " #{conn.request_path}"
)
+ conn =
+ if conn.assigns[:digest] do
+ conn
+ |> put_req_header("digest", conn.assigns[:digest])
+ else
+ conn
+ end
+
assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
signature ->
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index 1a012c1b4..cbedca004 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -35,7 +35,8 @@ defmodule Pleroma.Web.Endpoint do
parsers: [:urlencoded, :multipart, :json],
pass: ["*/*"],
json_decoder: Jason,
- length: Application.get_env(:pleroma, :instance) |> Keyword.get(:upload_limit)
+ length: Application.get_env(:pleroma, :instance) |> Keyword.get(:upload_limit),
+ body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
)
plug(Plug.MethodOverride)
From 3be58ad34ea69b1764245546c4d67a92e4d70328 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Wed, 1 Aug 2018 10:22:03 +0000
Subject: [PATCH 081/100] activitypub: actually send digest header when
federating
this is needed for backwards compatibility with non-digest pleroma instances
---
lib/pleroma/web/activity_pub/activity_pub.ex | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 90a39ce69..ec605b694 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -653,7 +653,11 @@ def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
@httpoison.post(
inbox,
json,
- [{"Content-Type", "application/activity+json"}, {"signature", signature}],
+ [
+ {"Content-Type", "application/activity+json"},
+ {"signature", signature},
+ {"digest", digest}
+ ],
hackney: [pool: :default]
)
end
From f72cfada1ad5cf7ca398ceddf4f70b3b192310ca Mon Sep 17 00:00:00 2001
From: Hakaba Hitoyo
Date: Thu, 2 Aug 2018 18:03:35 +0900
Subject: [PATCH 082/100] add suggestions/web config
---
config/config.exs | 3 ++-
lib/pleroma/web/nodeinfo/nodeinfo_controller.ex | 3 ++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index 614a3e1fe..aa35f14fb 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -113,7 +113,8 @@
enabled: false,
third_party_engine:
"http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}",
- timeout: 300_000
+ timeout: 300_000,
+ web: "https://vinayaka.distsn.org/?{{host}}+{{user}}"
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
index f9a6d9c25..2fab60274 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
@@ -50,7 +50,8 @@ def nodeinfo(conn, %{"version" => "2.0"}) do
suggestions: %{
enabled: Keyword.get(suggestions, :enabled, false),
thirdPartyEngine: Keyword.get(suggestions, :third_party_engine, ""),
- timeout: Keyword.get(suggestions, :timeout, 5000)
+ timeout: Keyword.get(suggestions, :timeout, 5000),
+ web: Keyword.get(suggestions, :web, "")
}
}
}
From 0ee29994a5506420f9b1c26568ad15a5d7db3d6e Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Sun, 5 Aug 2018 00:35:29 +0000
Subject: [PATCH 083/100] formatter: preserve case of hashtags
when generating hashtag links, we used the casefolded version that we use in
the link URLs, instead of the original version.
accordingly, adjust the formatter to use the original text for the links, while
keeping the casefolded version for the URLs.
---
lib/pleroma/formatter.ex | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index 0aaf21538..d199c9243 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -244,8 +244,8 @@ def add_hashtag_links({subs, text}, tags) do
subs =
subs ++
- Enum.map(tags, fn {_, tag, uuid} ->
- url = "##{tag}"
+ Enum.map(tags, fn {tag_text, tag, uuid} ->
+ url = "#{tag_text}"
{uuid, url}
end)
From 275c42e4382b47099d6bf70404ab8a97768628b5 Mon Sep 17 00:00:00 2001
From: William Pitcock
Date: Sun, 5 Aug 2018 01:10:05 +0000
Subject: [PATCH 084/100] user: filter out duplicate follow requests
---
lib/pleroma/user.ex | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index df22d29a8..fa0ea171d 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -398,6 +398,7 @@ def get_follow_requests(%User{} = user) do
Enum.map(reqs, fn req -> req.actor end)
|> Enum.uniq()
|> Enum.map(fn ap_id -> get_by_ap_id(ap_id) end)
+ |> Enum.filter(fn u -> !following?(u, user) end)
{:ok, users}
end
From c2d1a5e9c4f4ef316a2833914c8f134c00c95b75 Mon Sep 17 00:00:00 2001
From: eal
Date: Tue, 7 Aug 2018 21:56:50 +0300
Subject: [PATCH 085/100] MastoAPI AccountView: render profile emoji.
---
lib/pleroma/web/mastodon_api/views/account_view.ex | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index f33d615cf..cc5261616 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -14,6 +14,18 @@ def render("account.json", %{user: user}) do
header = User.banner_url(user) |> MediaProxy.url()
user_info = User.user_info(user)
+ emojis =
+ (user.info["source_data"]["tag"] || [])
+ |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
+ |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
+ %{
+ "shortcode" => String.trim(name, ":"),
+ "url" => MediaProxy.url(url),
+ "static_url" => MediaProxy.url(url),
+ "visible_in_picker" => false
+ }
+ end)
+
%{
id: to_string(user.id),
username: hd(String.split(user.nickname, "@")),
@@ -30,7 +42,7 @@ def render("account.json", %{user: user}) do
avatar_static: image,
header: header,
header_static: header,
- emojis: [],
+ emojis: emojis,
fields: [],
source: %{
note: "",
From cee63ad3f725a90fdd1a438520c33377cee8ad81 Mon Sep 17 00:00:00 2001
From: eal
Date: Wed, 8 Aug 2018 08:38:25 +0300
Subject: [PATCH 086/100] TwitterAPI user view: add screen_name_html and
description_html.
---
lib/pleroma/web/twitter_api/views/user_view.ex | 14 +++++++++++++-
test/web/twitter_api/views/user_view_test.exs | 8 ++++++++
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex
index 9c8460378..30cf266bd 100644
--- a/lib/pleroma/web/twitter_api/views/user_view.ex
+++ b/lib/pleroma/web/twitter_api/views/user_view.ex
@@ -1,6 +1,7 @@
defmodule Pleroma.Web.TwitterAPI.UserView do
use Pleroma.Web, :view
alias Pleroma.User
+ alias Pleroma.Formatter
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MediaProxy
@@ -28,9 +29,19 @@ def render("user.json", %{user: user = %User{}} = assigns) do
user_info = User.get_cached_user_info(user)
+ emoji =
+ (user.info["source_data"]["tag"] || [])
+ |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
+ |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
+ {String.trim(name, ":"), url}
+ end)
+
+ bio = HtmlSanitizeEx.strip_tags(user.bio)
+
data = %{
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
- "description" => HtmlSanitizeEx.strip_tags(user.bio),
+ "description" => bio,
+ "description_html" => bio |> Formatter.emojify(emoji),
"favourites_count" => 0,
"followers_count" => user_info[:follower_count],
"following" => following,
@@ -47,6 +58,7 @@ def render("user.json", %{user: user = %User{}} = assigns) do
"delete_others_notice" => !!user.info["is_moderator"]
},
"screen_name" => user.nickname,
+ "screen_name_html" => Formatter.emojify(user.nickname, emoji),
"statuses_count" => user_info[:note_count],
"statusnet_profile_url" => user.ap_id,
"cover_photo" => User.banner_url(user) |> MediaProxy.url(),
diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs
index 49f73c2fe..000c589af 100644
--- a/test/web/twitter_api/views/user_view_test.exs
+++ b/test/web/twitter_api/views/user_view_test.exs
@@ -40,7 +40,9 @@ test "A user" do
"id" => user.id,
"name" => user.name,
"screen_name" => user.nickname,
+ "screen_name_html" => user.nickname,
"description" => HtmlSanitizeEx.strip_tags(user.bio),
+ "description_html" => HtmlSanitizeEx.strip_tags(user.bio),
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
"favourites_count" => 0,
"statuses_count" => 1,
@@ -77,7 +79,9 @@ test "A user for a given other follower", %{user: user} do
"id" => user.id,
"name" => user.name,
"screen_name" => user.nickname,
+ "screen_name_html" => user.nickname,
"description" => HtmlSanitizeEx.strip_tags(user.bio),
+ "description_html" => HtmlSanitizeEx.strip_tags(user.bio),
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
"favourites_count" => 0,
"statuses_count" => 0,
@@ -115,7 +119,9 @@ test "A user that follows you", %{user: user} do
"id" => follower.id,
"name" => follower.name,
"screen_name" => follower.nickname,
+ "screen_name_html" => follower.nickname,
"description" => HtmlSanitizeEx.strip_tags(follower.bio),
+ "description_html" => HtmlSanitizeEx.strip_tags(follower.bio),
"created_at" => follower.inserted_at |> Utils.format_naive_asctime(),
"favourites_count" => 0,
"statuses_count" => 0,
@@ -160,7 +166,9 @@ test "A blocked user for the blocker" do
"id" => user.id,
"name" => user.name,
"screen_name" => user.nickname,
+ "screen_name_html" => user.nickname,
"description" => HtmlSanitizeEx.strip_tags(user.bio),
+ "description_html" => HtmlSanitizeEx.strip_tags(user.bio),
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
"favourites_count" => 0,
"statuses_count" => 0,
From ed9738e031e02a9338bedd3a8f3ff73329c101e7 Mon Sep 17 00:00:00 2001
From: eal
Date: Wed, 8 Aug 2018 09:24:50 +0300
Subject: [PATCH 087/100] Add tests for emoji in user profiles
Also use the correct field in TwitterAPI...
---
.../web/twitter_api/views/user_view.ex | 2 +-
test/web/mastodon_api/account_view_test.exs | 22 +++++++++++--
test/web/twitter_api/views/user_view_test.exs | 32 ++++++++++++++++---
3 files changed, 49 insertions(+), 7 deletions(-)
diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex
index 30cf266bd..30aaaf1ce 100644
--- a/lib/pleroma/web/twitter_api/views/user_view.ex
+++ b/lib/pleroma/web/twitter_api/views/user_view.ex
@@ -50,6 +50,7 @@ def render("user.json", %{user: user = %User{}} = assigns) do
"friends_count" => user_info[:following_count],
"id" => user.id,
"name" => user.name,
+ "name_html" => Formatter.emojify(user.name, emoji),
"profile_image_url" => image,
"profile_image_url_https" => image,
"profile_image_url_profile_size" => image,
@@ -58,7 +59,6 @@ def render("user.json", %{user: user = %User{}} = assigns) do
"delete_others_notice" => !!user.info["is_moderator"]
},
"screen_name" => user.nickname,
- "screen_name_html" => Formatter.emojify(user.nickname, emoji),
"statuses_count" => user_info[:note_count],
"statusnet_profile_url" => user.ap_id,
"cover_photo" => User.banner_url(user) |> MediaProxy.url(),
diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/account_view_test.exs
index b93418b3f..8bf194e6b 100644
--- a/test/web/mastodon_api/account_view_test.exs
+++ b/test/web/mastodon_api/account_view_test.exs
@@ -5,10 +5,21 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
alias Pleroma.User
test "Represent a user account" do
+ source_data = %{
+ "tag" => [
+ %{
+ "type" => "Emoji",
+ "icon" => %{"url" => "/file.png"},
+ "name" => ":karjalanpiirakka:"
+ }
+ ]
+ }
+
user =
insert(:user, %{
- info: %{"note_count" => 5, "follower_count" => 3},
+ info: %{"note_count" => 5, "follower_count" => 3, "source_data" => source_data},
nickname: "shp@shitposter.club",
+ name: ":karjalanpiirakka: shp",
inserted_at: ~N[2017-08-15 15:47:06.597036]
})
@@ -28,7 +39,14 @@ test "Represent a user account" do
avatar_static: "http://localhost:4001/images/avi.png",
header: "http://localhost:4001/images/banner.png",
header_static: "http://localhost:4001/images/banner.png",
- emojis: [],
+ emojis: [
+ %{
+ "static_url" => "/file.png",
+ "url" => "/file.png",
+ "shortcode" => "karjalanpiirakka",
+ "visible_in_picker" => false
+ }
+ ],
fields: [],
source: %{
note: "",
diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs
index 000c589af..fefb6bdcc 100644
--- a/test/web/twitter_api/views/user_view_test.exs
+++ b/test/web/twitter_api/views/user_view_test.exs
@@ -20,6 +20,30 @@ test "A user with an avatar object", %{user: user} do
assert represented["profile_image_url"] == image
end
+ test "A user with emoji in username", %{user: user} do
+ expected =
+ " man"
+
+ user = %{
+ user
+ | info: %{
+ "source_data" => %{
+ "tag" => [
+ %{
+ "type" => "Emoji",
+ "icon" => %{"url" => "/file.png"},
+ "name" => ":karjalanpiirakka:"
+ }
+ ]
+ }
+ }
+ }
+
+ user = %{user | name: ":karjalanpiirakka: man"}
+ represented = UserView.render("show.json", %{user: user})
+ assert represented["name_html"] == expected
+ end
+
test "A user" do
note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
@@ -40,7 +64,7 @@ test "A user" do
"id" => user.id,
"name" => user.name,
"screen_name" => user.nickname,
- "screen_name_html" => user.nickname,
+ "name_html" => user.name,
"description" => HtmlSanitizeEx.strip_tags(user.bio),
"description_html" => HtmlSanitizeEx.strip_tags(user.bio),
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
@@ -79,7 +103,7 @@ test "A user for a given other follower", %{user: user} do
"id" => user.id,
"name" => user.name,
"screen_name" => user.nickname,
- "screen_name_html" => user.nickname,
+ "name_html" => user.name,
"description" => HtmlSanitizeEx.strip_tags(user.bio),
"description_html" => HtmlSanitizeEx.strip_tags(user.bio),
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
@@ -119,7 +143,7 @@ test "A user that follows you", %{user: user} do
"id" => follower.id,
"name" => follower.name,
"screen_name" => follower.nickname,
- "screen_name_html" => follower.nickname,
+ "name_html" => follower.name,
"description" => HtmlSanitizeEx.strip_tags(follower.bio),
"description_html" => HtmlSanitizeEx.strip_tags(follower.bio),
"created_at" => follower.inserted_at |> Utils.format_naive_asctime(),
@@ -166,7 +190,7 @@ test "A blocked user for the blocker" do
"id" => user.id,
"name" => user.name,
"screen_name" => user.nickname,
- "screen_name_html" => user.nickname,
+ "name_html" => user.name,
"description" => HtmlSanitizeEx.strip_tags(user.bio),
"description_html" => HtmlSanitizeEx.strip_tags(user.bio),
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
From 37b802682ce1231f99976538a11c1584d48f47f4 Mon Sep 17 00:00:00 2001
From: eal
Date: Thu, 9 Aug 2018 13:07:03 +0300
Subject: [PATCH 088/100] HTML-sanitize usernames before emojifying.
---
lib/pleroma/web/twitter_api/views/user_view.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex
index 30aaaf1ce..7d0f0e703 100644
--- a/lib/pleroma/web/twitter_api/views/user_view.ex
+++ b/lib/pleroma/web/twitter_api/views/user_view.ex
@@ -50,7 +50,7 @@ def render("user.json", %{user: user = %User{}} = assigns) do
"friends_count" => user_info[:following_count],
"id" => user.id,
"name" => user.name,
- "name_html" => Formatter.emojify(user.name, emoji),
+ "name_html" => HtmlSanitizeEx.strip_tags(user.name) |> Formatter.emojify(emoji),
"profile_image_url" => image,
"profile_image_url_https" => image,
"profile_image_url_profile_size" => image,
From db5cdfa333c80eb9da419956d843ac123835994c Mon Sep 17 00:00:00 2001
From: eal
Date: Thu, 9 Aug 2018 20:47:29 +0300
Subject: [PATCH 089/100] Remote follow: don't show confusing error if already
following
---
.../web/twitter_api/controllers/util_controller.ex | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index 47fc79350..24ebdf007 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -99,6 +99,10 @@ def do_remote_follow(conn, %{
conn
|> render("followed.html", %{error: false})
else
+ # Was already following user
+ {:error, "Could not follow user:" <> _rest} ->
+ render(conn, "followed.html", %{error: false})
+
_e ->
conn
|> render("follow_login.html", %{
@@ -117,6 +121,11 @@ def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}
conn
|> render("followed.html", %{error: false})
else
+ # Was already following user
+ {:error, "Could not follow user:" <> _rest} ->
+ conn
+ |> render("followed.html", %{error: false})
+
e ->
Logger.debug("Remote follow failed with error #{inspect(e)}")
From e7b00f202f717ca3c7ca2d624d6ad7d79f499ac6 Mon Sep 17 00:00:00 2001
From: "trqx@goat.si"
Date: Thu, 9 Aug 2018 16:17:45 +0200
Subject: [PATCH 090/100] fix gopher server informational messages
some gopher clients did not accept those lines due to a missing tab
---
lib/pleroma/gopher/server.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex
index f6abcd4d0..4bdd27959 100644
--- a/lib/pleroma/gopher/server.ex
+++ b/lib/pleroma/gopher/server.ex
@@ -54,7 +54,7 @@ def info(text) do
String.split(text, "\r")
|> Enum.map(fn text ->
- "i#{text}\tfake\(NULL)\t0\r\n"
+ "i#{text}\tfake\t(NULL)\t0\r\n"
end)
|> Enum.join("")
end
From af30f3f648db36338c997c3900a9504a8fca6fd5 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier"
Date: Fri, 10 Aug 2018 18:01:42 +0200
Subject: [PATCH 091/100] lib/pleroma/gopher/server.ex: Fix errorneous empty
lines
---
lib/pleroma/gopher/server.ex | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex
index 4bdd27959..97a1dea77 100644
--- a/lib/pleroma/gopher/server.ex
+++ b/lib/pleroma/gopher/server.ex
@@ -77,14 +77,14 @@ def render_activities(activities) do
link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <>
info("#{like_count} likes, #{announcement_count} repeats") <>
- "\r\n" <>
+ "i\tfake\t(NULL)\t0\r\n" <>
info(
HtmlSanitizeEx.strip_tags(
String.replace(activity.data["object"]["content"], "
", "\r")
)
)
end)
- |> Enum.join("\r\n")
+ |> Enum.join("i\tfake\t(NULL)\t0\r\n")
end
def response("") do
From 1e9d152d608c83c906ed9a4ca2c6a21d644e2728 Mon Sep 17 00:00:00 2001
From: lambda
Date: Sun, 12 Aug 2018 11:11:08 +0000
Subject: [PATCH 092/100] Update generate_invite_token.ex
---
lib/mix/tasks/generate_invite_token.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/mix/tasks/generate_invite_token.ex b/lib/mix/tasks/generate_invite_token.ex
index a5f41ef0e..c4daa9a6c 100644
--- a/lib/mix/tasks/generate_invite_token.ex
+++ b/lib/mix/tasks/generate_invite_token.ex
@@ -1,7 +1,7 @@
defmodule Mix.Tasks.GenerateInviteToken do
use Mix.Task
- @shortdoc "Generate password reset link for user"
+ @shortdoc "Generate invite token for user"
def run([]) do
Mix.Task.run("app.start")
From 4a612b8c1b4c6a499f34ad7110f1eeda537bc878 Mon Sep 17 00:00:00 2001
From: lain
Date: Sun, 12 Aug 2018 15:29:30 +0200
Subject: [PATCH 093/100] Update Mastofe.
---
priv/static/packs/about.js | Bin 37096 -> 38086 bytes
priv/static/packs/about.js.map | Bin 294831 -> 290326 bytes
priv/static/packs/admin.js | Bin 1229 -> 1260 bytes
priv/static/packs/admin.js.map | Bin 5078 -> 5587 bytes
priv/static/packs/appcache/manifest.appcache | 27 +++++++---
priv/static/packs/application.js | Bin 45442 -> 46483 bytes
priv/static/packs/application.js.map | Bin 327168 -> 334504 bytes
priv/static/packs/base_polyfills.js | Bin 94821 -> 95199 bytes
priv/static/packs/base_polyfills.js.map | Bin 662324 -> 664565 bytes
priv/static/packs/common.js | Bin 814943 -> 828839 bytes
priv/static/packs/common.js.map | Bin 4428191 -> 4549761 bytes
.../packs/containers/media_container.js | Bin 0 -> 30575 bytes
.../packs/containers/media_container.js.map | Bin 0 -> 241564 bytes
priv/static/packs/contrast.css | 2 +
priv/static/packs/contrast.css.map | 1 +
priv/static/packs/contrast.js | Bin 0 -> 84 bytes
priv/static/packs/contrast.js.map | Bin 0 -> 393 bytes
priv/static/packs/default.css | 2 +-
priv/static/packs/default.js | Bin 83 -> 83 bytes
priv/static/packs/default.js.map | Bin 390 -> 390 bytes
...-fren-d16fd77f9a9387e7d146b5f9d4dc1e7f.png | Bin 40859 -> 0 bytes
...inted-8864342480c3612e3061702851d3a798.svg | 1 -
...eting-475430963d0b00fe82b07b17857ebf6c.svg | 1 -
...plane-e3f2d57c12c376e189c274cbe81af8dd.svg | 1 -
...rking-2e653cc278c2ac871c23aeb10de1c0e2.svg | 1 -
priv/static/packs/emoji_picker.js | Bin 621235 -> 515801 bytes
priv/static/packs/emoji_picker.js.map | Bin 1894302 -> 1945247 bytes
priv/static/packs/extra_polyfills.js | Bin 12461 -> 12461 bytes
priv/static/packs/extra_polyfills.js.map | Bin 92207 -> 92207 bytes
priv/static/packs/features/account_gallery.js | Bin 20896 -> 24921 bytes
.../packs/features/account_gallery.js.map | Bin 150077 -> 161131 bytes
.../static/packs/features/account_timeline.js | Bin 31264 -> 36585 bytes
.../packs/features/account_timeline.js.map | Bin 231748 -> 246411 bytes
priv/static/packs/features/blocks.js | Bin 9397 -> 9224 bytes
priv/static/packs/features/blocks.js.map | Bin 77786 -> 69783 bytes
.../packs/features/community_timeline.js | Bin 18318 -> 27398 bytes
.../packs/features/community_timeline.js.map | Bin 149033 -> 187981 bytes
priv/static/packs/features/compose.js | Bin 76368 -> 96443 bytes
priv/static/packs/features/compose.js.map | Bin 466683 -> 653603 bytes
priv/static/packs/features/direct_timeline.js | Bin 0 -> 19172 bytes
.../packs/features/direct_timeline.js.map | Bin 0 -> 142975 bytes
priv/static/packs/features/domain_blocks.js | Bin 0 -> 15237 bytes
.../packs/features/domain_blocks.js.map | Bin 0 -> 117285 bytes
.../packs/features/favourited_statuses.js | Bin 17260 -> 18102 bytes
.../packs/features/favourited_statuses.js.map | Bin 141928 -> 135731 bytes
priv/static/packs/features/favourites.js | Bin 8557 -> 8384 bytes
priv/static/packs/features/favourites.js.map | Bin 71186 -> 63174 bytes
priv/static/packs/features/follow_requests.js | Bin 6694 -> 6698 bytes
.../packs/features/follow_requests.js.map | Bin 55876 -> 56095 bytes
priv/static/packs/features/followers.js | Bin 23128 -> 27150 bytes
priv/static/packs/features/followers.js.map | Bin 164117 -> 175026 bytes
priv/static/packs/features/following.js | Bin 23128 -> 27150 bytes
priv/static/packs/features/following.js.map | Bin 164117 -> 175026 bytes
.../packs/features/generic_not_found.js | Bin 2648 -> 2704 bytes
.../packs/features/generic_not_found.js.map | Bin 21852 -> 22068 bytes
priv/static/packs/features/getting_started.js | Bin 7316 -> 11514 bytes
.../packs/features/getting_started.js.map | Bin 49138 -> 75368 bytes
.../static/packs/features/hashtag_timeline.js | Bin 16710 -> 17573 bytes
.../packs/features/hashtag_timeline.js.map | Bin 135310 -> 129359 bytes
priv/static/packs/features/home_timeline.js | Bin 26523 -> 27386 bytes
.../packs/features/home_timeline.js.map | Bin 192895 -> 186924 bytes
.../packs/features/keyboard_shortcuts.js | Bin 6918 -> 7097 bytes
.../packs/features/keyboard_shortcuts.js.map | Bin 49226 -> 50133 bytes
priv/static/packs/features/list_editor.js | Bin 5343 -> 5344 bytes
priv/static/packs/features/list_editor.js.map | Bin 41068 -> 41045 bytes
priv/static/packs/features/list_timeline.js | Bin 18395 -> 19316 bytes
.../packs/features/list_timeline.js.map | Bin 146257 -> 140542 bytes
priv/static/packs/features/lists.js | Bin 0 -> 7048 bytes
priv/static/packs/features/lists.js.map | Bin 0 -> 56868 bytes
priv/static/packs/features/mutes.js | Bin 0 -> 9230 bytes
priv/static/packs/features/mutes.js.map | Bin 0 -> 69677 bytes
priv/static/packs/features/notifications.js | Bin 35678 -> 36064 bytes
.../packs/features/notifications.js.map | Bin 243970 -> 235484 bytes
priv/static/packs/features/pinned_statuses.js | Bin 0 -> 18908 bytes
.../packs/features/pinned_statuses.js.map | Bin 0 -> 142757 bytes
priv/static/packs/features/public_timeline.js | Bin 18325 -> 27599 bytes
.../packs/features/public_timeline.js.map | Bin 149048 -> 188839 bytes
priv/static/packs/features/reblogs.js | Bin 8558 -> 8385 bytes
priv/static/packs/features/reblogs.js.map | Bin 71125 -> 63113 bytes
priv/static/packs/features/status.js | Bin 31282 -> 34175 bytes
priv/static/packs/features/status.js.map | Bin 232929 -> 233971 bytes
priv/static/packs/locale_ar.js | Bin 26431 -> 28517 bytes
priv/static/packs/locale_ar.js.map | Bin 74613 -> 79785 bytes
priv/static/packs/locale_bg.js | Bin 17141 -> 18317 bytes
priv/static/packs/locale_bg.js.map | Bin 51839 -> 55193 bytes
priv/static/packs/locale_ca.js | Bin 18847 -> 20338 bytes
priv/static/packs/locale_ca.js.map | Bin 56741 -> 60725 bytes
priv/static/packs/locale_co.js | Bin 0 -> 24386 bytes
priv/static/packs/locale_co.js.map | Bin 0 -> 73454 bytes
priv/static/packs/locale_de.js | Bin 17843 -> 19547 bytes
priv/static/packs/locale_de.js.map | Bin 53601 -> 58013 bytes
priv/static/packs/locale_el.js | Bin 0 -> 26567 bytes
priv/static/packs/locale_el.js.map | Bin 0 -> 71780 bytes
priv/static/packs/locale_en.js | Bin 22054 -> 23405 bytes
priv/static/packs/locale_en.js.map | Bin 67812 -> 71495 bytes
priv/static/packs/locale_eo.js | Bin 16430 -> 17751 bytes
priv/static/packs/locale_eo.js.map | Bin 50137 -> 53775 bytes
priv/static/packs/locale_es.js | Bin 29320 -> 30481 bytes
priv/static/packs/locale_es.js.map | Bin 87126 -> 90449 bytes
priv/static/packs/locale_eu.js | Bin 0 -> 18635 bytes
priv/static/packs/locale_eu.js.map | Bin 0 -> 55876 bytes
priv/static/packs/locale_fa.js | Bin 21124 -> 24089 bytes
priv/static/packs/locale_fa.js.map | Bin 59852 -> 66784 bytes
priv/static/packs/locale_fi.js | Bin 18035 -> 18806 bytes
priv/static/packs/locale_fi.js.map | Bin 53733 -> 56273 bytes
priv/static/packs/locale_fr.js | Bin 20827 -> 22596 bytes
priv/static/packs/locale_fr.js.map | Bin 62133 -> 66730 bytes
priv/static/packs/locale_gl.js | Bin 17163 -> 18621 bytes
priv/static/packs/locale_gl.js.map | Bin 51977 -> 55891 bytes
priv/static/packs/locale_he.js | Bin 19625 -> 20729 bytes
priv/static/packs/locale_he.js.map | Bin 57592 -> 60798 bytes
priv/static/packs/locale_hr.js | Bin 16932 -> 18094 bytes
priv/static/packs/locale_hr.js.map | Bin 52254 -> 55578 bytes
priv/static/packs/locale_hu.js | Bin 17736 -> 18885 bytes
priv/static/packs/locale_hu.js.map | Bin 53098 -> 56396 bytes
priv/static/packs/locale_hy.js | Bin 22991 -> 24122 bytes
priv/static/packs/locale_hy.js.map | Bin 63587 -> 66849 bytes
priv/static/packs/locale_id.js | Bin 16689 -> 17857 bytes
priv/static/packs/locale_id.js.map | Bin 50676 -> 54013 bytes
priv/static/packs/locale_io.js | Bin 21958 -> 23132 bytes
priv/static/packs/locale_io.js.map | Bin 67548 -> 70899 bytes
priv/static/packs/locale_it.js | Bin 16463 -> 18927 bytes
priv/static/packs/locale_it.js.map | Bin 50761 -> 56716 bytes
priv/static/packs/locale_ja.js | Bin 18538 -> 20148 bytes
priv/static/packs/locale_ja.js.map | Bin 54256 -> 58467 bytes
priv/static/packs/locale_ko.js | Bin 17249 -> 18904 bytes
priv/static/packs/locale_ko.js.map | Bin 51724 -> 56025 bytes
priv/static/packs/locale_nl.js | Bin 17777 -> 19317 bytes
priv/static/packs/locale_nl.js.map | Bin 53461 -> 57541 bytes
priv/static/packs/locale_no.js | Bin 16032 -> 17202 bytes
priv/static/packs/locale_no.js.map | Bin 49320 -> 52661 bytes
priv/static/packs/locale_oc.js | Bin 17191 -> 18602 bytes
priv/static/packs/locale_oc.js.map | Bin 53466 -> 57291 bytes
priv/static/packs/locale_pl.js | Bin 18206 -> 19620 bytes
priv/static/packs/locale_pl.js.map | Bin 54949 -> 58743 bytes
priv/static/packs/locale_pt-BR.js | Bin 19386 -> 20880 bytes
priv/static/packs/locale_pt-BR.js.map | Bin 57987 -> 61977 bytes
priv/static/packs/locale_pt.js | Bin 18914 -> 20073 bytes
priv/static/packs/locale_pt.js.map | Bin 56996 -> 60315 bytes
priv/static/packs/locale_ru.js | Bin 23964 -> 25553 bytes
priv/static/packs/locale_ru.js.map | Bin 66623 -> 70806 bytes
priv/static/packs/locale_sk.js | Bin 18341 -> 19885 bytes
priv/static/packs/locale_sk.js.map | Bin 54914 -> 59003 bytes
priv/static/packs/locale_sl.js | Bin 0 -> 18367 bytes
priv/static/packs/locale_sl.js.map | Bin 0 -> 56035 bytes
priv/static/packs/locale_sr-Latn.js | Bin 19801 -> 20956 bytes
priv/static/packs/locale_sr-Latn.js.map | Bin 59679 -> 62989 bytes
priv/static/packs/locale_sr.js | Bin 24613 -> 25729 bytes
priv/static/packs/locale_sr.js.map | Bin 69232 -> 72464 bytes
priv/static/packs/locale_sv.js | Bin 17266 -> 18458 bytes
priv/static/packs/locale_sv.js.map | Bin 52537 -> 55923 bytes
priv/static/packs/locale_te.js | Bin 0 -> 18072 bytes
priv/static/packs/locale_te.js.map | Bin 0 -> 54692 bytes
priv/static/packs/locale_th.js | Bin 16238 -> 17546 bytes
priv/static/packs/locale_th.js.map | Bin 49695 -> 53373 bytes
priv/static/packs/locale_tr.js | Bin 17243 -> 18419 bytes
priv/static/packs/locale_tr.js.map | Bin 52098 -> 55452 bytes
priv/static/packs/locale_uk.js | Bin 21621 -> 22773 bytes
priv/static/packs/locale_uk.js.map | Bin 61919 -> 65225 bytes
priv/static/packs/locale_zh-CN.js | Bin 20922 -> 22165 bytes
priv/static/packs/locale_zh-CN.js.map | Bin 63580 -> 67046 bytes
priv/static/packs/locale_zh-HK.js | Bin 20350 -> 21494 bytes
priv/static/packs/locale_zh-HK.js.map | Bin 62418 -> 65698 bytes
priv/static/packs/locale_zh-TW.js | Bin 19945 -> 21161 bytes
priv/static/packs/locale_zh-TW.js.map | Bin 61635 -> 65048 bytes
.../logo-fe5141d38a25f50068b4c69b77ca1ec8.svg | 1 -
...o_alt-6090911445f54a587465e41da77a6969.svg | 1 -
..._full-96e7a97fe469f75a23a74852b2478fa3.svg | 1 -
...arent-6900bab180aa3a46c34425e5367a218f.svg | 1 -
priv/static/packs/mailer.js | Bin 110 -> 110 bytes
priv/static/packs/mailer.js.map | Bin 716 -> 716 bytes
priv/static/packs/manifest.json | 46 ++++++++++++++----
priv/static/packs/mastodon-light.css | 2 +
priv/static/packs/mastodon-light.css.map | 1 +
priv/static/packs/mastodon-light.js | Bin 0 -> 90 bytes
priv/static/packs/mastodon-light.js.map | Bin 0 -> 411 bytes
priv/static/packs/modals/embed_modal.js | Bin 0 -> 1963 bytes
priv/static/packs/modals/embed_modal.js.map | Bin 0 -> 13540 bytes
priv/static/packs/modals/mute_modal.js | Bin 0 -> 8434 bytes
priv/static/packs/modals/mute_modal.js.map | Bin 0 -> 46799 bytes
priv/static/packs/modals/onboarding_modal.js | Bin 72400 -> 62340 bytes
.../packs/modals/onboarding_modal.js.map | Bin 414645 -> 392357 bytes
priv/static/packs/modals/report_modal.js | Bin 0 -> 11702 bytes
priv/static/packs/modals/report_modal.js.map | Bin 0 -> 70893 bytes
...eview-9a17d32fc48369e8ccd910a75260e67d.jpg | Bin 292252 -> 0 bytes
priv/static/packs/public.js | Bin 34199 -> 2902 bytes
priv/static/packs/public.js.map | Bin 265752 -> 15143 bytes
priv/static/packs/share.js | Bin 78115 -> 66445 bytes
priv/static/packs/share.js.map | Bin 488748 -> 456955 bytes
priv/static/packs/status/media_gallery.js | Bin 4734 -> 4989 bytes
priv/static/packs/status/media_gallery.js.map | Bin 30883 -> 32220 bytes
priv/static/sw.js | Bin 65165 -> 64559 bytes
192 files changed, 63 insertions(+), 26 deletions(-)
create mode 100644 priv/static/packs/containers/media_container.js
create mode 100644 priv/static/packs/containers/media_container.js.map
create mode 100644 priv/static/packs/contrast.css
create mode 100644 priv/static/packs/contrast.css.map
create mode 100644 priv/static/packs/contrast.js
create mode 100644 priv/static/packs/contrast.js.map
delete mode 100644 priv/static/packs/elephant-fren-d16fd77f9a9387e7d146b5f9d4dc1e7f.png
delete mode 100644 priv/static/packs/elephant_ui_disappointed-8864342480c3612e3061702851d3a798.svg
delete mode 100644 priv/static/packs/elephant_ui_greeting-475430963d0b00fe82b07b17857ebf6c.svg
delete mode 100644 priv/static/packs/elephant_ui_plane-e3f2d57c12c376e189c274cbe81af8dd.svg
delete mode 100644 priv/static/packs/elephant_ui_working-2e653cc278c2ac871c23aeb10de1c0e2.svg
create mode 100644 priv/static/packs/features/direct_timeline.js
create mode 100644 priv/static/packs/features/direct_timeline.js.map
create mode 100644 priv/static/packs/features/domain_blocks.js
create mode 100644 priv/static/packs/features/domain_blocks.js.map
create mode 100644 priv/static/packs/features/lists.js
create mode 100644 priv/static/packs/features/lists.js.map
create mode 100644 priv/static/packs/features/mutes.js
create mode 100644 priv/static/packs/features/mutes.js.map
create mode 100644 priv/static/packs/features/pinned_statuses.js
create mode 100644 priv/static/packs/features/pinned_statuses.js.map
create mode 100644 priv/static/packs/locale_co.js
create mode 100644 priv/static/packs/locale_co.js.map
create mode 100644 priv/static/packs/locale_el.js
create mode 100644 priv/static/packs/locale_el.js.map
create mode 100644 priv/static/packs/locale_eu.js
create mode 100644 priv/static/packs/locale_eu.js.map
create mode 100644 priv/static/packs/locale_sl.js
create mode 100644 priv/static/packs/locale_sl.js.map
create mode 100644 priv/static/packs/locale_te.js
create mode 100644 priv/static/packs/locale_te.js.map
delete mode 100644 priv/static/packs/logo-fe5141d38a25f50068b4c69b77ca1ec8.svg
delete mode 100644 priv/static/packs/logo_alt-6090911445f54a587465e41da77a6969.svg
delete mode 100644 priv/static/packs/logo_full-96e7a97fe469f75a23a74852b2478fa3.svg
delete mode 100644 priv/static/packs/logo_transparent-6900bab180aa3a46c34425e5367a218f.svg
create mode 100644 priv/static/packs/mastodon-light.css
create mode 100644 priv/static/packs/mastodon-light.css.map
create mode 100644 priv/static/packs/mastodon-light.js
create mode 100644 priv/static/packs/mastodon-light.js.map
create mode 100644 priv/static/packs/modals/embed_modal.js
create mode 100644 priv/static/packs/modals/embed_modal.js.map
create mode 100644 priv/static/packs/modals/mute_modal.js
create mode 100644 priv/static/packs/modals/mute_modal.js.map
create mode 100644 priv/static/packs/modals/report_modal.js
create mode 100644 priv/static/packs/modals/report_modal.js.map
delete mode 100644 priv/static/packs/preview-9a17d32fc48369e8ccd910a75260e67d.jpg
diff --git a/priv/static/packs/about.js b/priv/static/packs/about.js
index 0a386cbbad52fb9e771055e1777950276b5a2f2a..6bb569c92582511b08856f3236b0768d961373b7 100644
GIT binary patch
delta 6228
zcmcgwTW}lKd7cGzGj*3GiqwS!AsOBk4_5?13gm)IS_Dax5^t8McqE2Gme{kvl8aq%
zcR>;cVObf=u~WO2{I{9OOeT@zw9{m!?Ih?!J?TVl9Ve5{#BSnFnm)ASc-2Y>FnY3M40+e3w89UO6X_JeCu8zI~n3st33WTqu#iF0JrxLu{c>AQ`MZ
zmk@}7|1tb8vNGCL{O|4-2)gCZ_4{7?eYbq97aKCS-RA_divL*5;$JK-kP`mK@qbZ<
z&u;tSPMfN>qPbvgJ6`*~y)Aq=91RCmkqX&t^yYny1|EsS-tC7b!iuS4(@xJ#Sa*lQ
zSFK`rO*f3NVL3Fc;D=2MhE5$4UbHO7b-XT`vT7A=-K9?0wXmNE;q4bgWCqObv)mFC
z=8wQXZokf15Z^Jt*-+T=jpGG@r-C%IxI{B9Ulf;FQK%5hoYmo##-m*Y-888h5r%3_
zR_c40wH~ASMXJJo?K*LNbZU5XBtptX+mNW3r7j=A&xn9-M{V1(C9P;?T-`Epu(D0v
zqHTsK0s$LqLsn3m!XqYlms$^zfwH{t0Xm>&4D%GH=Tk+_9SB;){QD&~X))
z%4Mx+7*58f)P$kE0yhQv-m%lkp02JzS46vI7~@pW=G;!&>53Udu<7~+2=6-#d-i<=
zTKC`MG&sN=hIH~I>_71QQ3TLsQz!nYX}Y;IP+|&YUE`U#U6!%1s@P!*_8)47w+=R+u%c1k
z&FPL@qQ52k4~<{xx~BY99Jrh)cnHhlk;-XGQqdksJo(no@FRF(#?0B8^ay<_m5q
zz#Y*b+!PfG@~Wtashn=8HZ{eJRWx1RWTcXbMsdo~s5YeV0>1=SQ};1EG@N8Ih>nu%
zY;0hgjg58{zT0#f;r2n(S@_%wJK*Zk*SG@w=g}9gZ^m7;4!h^jjULn%oSRuLiWcSN@_{4pM-NX9aXc{5U}kX1`p!(F5pjT!|0rs))X
zx%o^;72x~L=eCsusRX}mz8*^S2fVuL5JJNCV1yXd@uR;7zSQ!AP+yl%){nsJ$3{-{
z#e8<{?nC4<(nhcqe6Z!J-0{~#NZ4QC_NuTGr`^>usW
z?K`!*8an-TF4Pwf%KNH6ee5`vdp74&J@WX;hEQLSDt6ysQ}ZTWD)c(_*l
zlz>yhjDYnSKP6y1U`D`l(VsLf45y4`&5Jsg0rGOdS_M%P__ARsYC4YymCCv*MOaJ}
z9g$h#V=i*2!lJX*0m)`FeD-h-Jl7FBlaafUnE^eS>F5xY`OJd6MR1vgO)QwQB^z+J
zvm5r3-h&1nxJ7KC5+Fsz&>7#Wn{Ev|I^?+a8<;s1wKJ;}JOn6<22~}Bgq$+;%(CQx
zK8Ihg*k;F-^ROY#=Xb+XUNkLcNs%*oM|Ob5n@VM
zQ#z?(=zPJ^oz7guBXwHga~KMCO
z;ECbZ>)x5Mqr@bRWyjCry+~r1#nE8+Y23#dmIHC{NuorDbwn9Qj~H^;A&b0-RLSH7
z5+xcIeWHwE7vxDaEcrZ%L#Dkl(j@XDQYr$%qet|$=G9CHWS!OYVcW}I6U6%*eqhVV
z`}uPZQr>eN*D#Rpc9I)Ncw-cygX!zC2cZ<>3b~dwYe~}*3$|5oM6Is%gz5}^YK`dV
z&rqogQ#?yPsiKWs-baVXdy(fNeBazdnXql53JX09gh{hszrer<{Zrtk#iPXBmf1ho6%#n3bcUTwQ
z%qB72u;Q#@ySicsL6~Mh?CL#|VKL*gbuErqO~>AJTGO)#01O!7S>TPXgWFmAIe4?{
zO6{u+x?^JvhU`E&c0#pk$Y9AsmSsA`x~z)|DI@RuQSXqvvOb|o7G_D)Qy^AnOz=`=
zjSuV!i6B=|_q-1l0r1mc0
zy3d`DXq}M`DpUkagJwo?F>Ep1kP}ckU}_&VD3f*kWpuzYTDd}WQr9^WhnT%!80)7
zLGBk1Th=;}QR`hHkZ3vG5RbDw+lr}j_a0x>xJWbcIGpTxGt}1)Kkpgm642f|#KkeC
z+T7%wr-tjbB=TLIl4lX>6jwpoTod!Yes_{V?wny)
zgmphf%xxyr5~6??k`p4TsX39U9L3G#Hg6S+;-bJ~o#6NK$QLg{Ouasu81w{Z5?)CR
zb7}a8L{kd_gOpMFGw`MSZx4tw@Vi9Qjmx!QW%XHc*0xr4mD)^mv6G#vtIvsJwvuId
zMwgq}(k4s{`e>iEY*#UO=4&|^OSLlI#fEK75Ddem36+oJEmhfXbYj%Fif&RnA_xLJ
z(I+?H+Tgjqc06RfDR3z~-r2gV!Y7Xx&xGA#`!QE}*Vy266L_fXa1+@6zQavmvnjCp
zpGM)^^5XI8FZ)Nh23z2v@=|+EtqhUfy|1VR6
z|9SrO*0i8-s|~New{QK{o5>*j_=V>~JxsYu+!Aio#Q(`g;SMcTTV8yMLs%`p6t4~6
z2_L-l7}|Dxv3+MoK*3+zn48sizPOL`PRt8j_%pfEJc+PKrxjK8BZ9>MCRQ>H^5CDh
z_?27HkxtLk1-uhXR~v@jZ8)aM8IdVJl&QWb!wjGel`E14|1o@gPY#Rf#)iQ=d=3t!
zW*b?`_HIBFeyvFGr>Vt*E8hJFuSvfClAnZ@k;~lGYJNoFxJ&Tf=oFWOlVhjw_xxCd
zn*?>N2YNs^+ooJ%z`(>De0KabZmRm>_yo5v?GjHzUtml=4PzIakS4(S
z@#FBFi=TmCPws*5jqirk#TJCu!o@4_vkRZu?x3t)hP{)sT&j9~GRHlt1&d-CRTW&j
zRMz01r{axTG+GRbP1EO&Xd4^2$Re(@vYD2`X;7wT(D%2e^LTS=n`zrW<_<+1ieaQi
zBsy>jre-EON8DsaacCs3n?;vKq##v~1?C*St<;Q{;O&`T?$Izio1%((AfmyY*)~pv
zmD$7FCtb9|$`?++4rJM3EP1=hq+qNI=@F$M%E)zJ3bMQOq-U5D$SeyzWYTHRFe{J|
zG@y7K^U5OvxrAj5%~7v>Ng(H$PbAuL&v0HKSD2v-El+!vD?*42SAY1_Z?QM469nhJ`WFF~oG
zJU%xyxoJz|p{>{ZQ_
zuWzYi-z9PD#+00dm8Fw7#H&k3!a27>Ch(LsSn>nB-m|`rGg&PBh+D3bm5m0R%s1UpznArI44oyZH%Rysh0G&Nq
zX=~Qz-33|gFgsL|n>QC^TA>EM(`xW+MdFc=@RnNIWUtzbPA(et^^A}39DGoj#A$Ut
zn?SGhS*d<=huP*f23U=dVYb8D*`W`^zdbwnkVUg4?BL)>&wU3&Z{wzLSo2|;lgsIJ
z&dnQ=R!M>~+5$hli8-QzPhATFlhv2tqnqPr*$@|9QKgzvG+b0V1=QE8iczGFRK{l_
ziyubu&6GNd@qYh#=G}%}p?EiZ{0B!vednO*<<>*JDDR8@I}cvak)h(%v6r9Wjs$OU
z?4}ajjM}@B@YXADhjr|sN24}gG`u@XM9FwBW)T4&?uw!gZ~UQy3p)Dhhq!0#W&6bK
g0enTddb=BcU%vhLevRY=USZR!ka-lse|J0jzasepw*UYD
delta 5767
zcmcgwYit}>71pflHf>Ueyq(yIolUZxiLa+#zrFT2@go~M@x~iJnx<~D9q-)T8Sm`O
zdS=#-u9v6}TKXc7lOOF5Xj8SI@=z%YNC?_Wg%*Lz8-e%%Mf^Y@L1{q<5USwZnOQ&b
zDEvVDnYriObMKsc9^W~4-g+tUyDNe1^K?2VCo{)v)5!5t&7EgRA>7&pzYgvJeb<4D
zrz3k*$q)@*5lBih_$GnmTskF?EYj^4UpdRE@czJ7cspEMI?V0j;PL_SoJF0yWz?tg
zM$*wtgQr4)@uxMMe;k_;9O048FOUJ;QD8xLpv4Cb=ud6T>HlClayI!M>4Tku-P0zJqlT?^u7%A^7F1
zl`({Zaji1Mc4Q}SM<{A#tkr7lNPKi`;zRWEb@Aj}Wn4Adp`~#Td=Od(4+exYsC<%{
ztV19=zQdgY$uk3clIl(AInxzL}|;J6l|1;{XrLjNRNWkU4zgD~+!(3NA9^v>*f4
zReT%XxoN0EAk!dJ#si}g9KNs}E>_;l>F{o44`)Eb%`b$r0{SB2+dMC7j1-E*GzO_o
z9U58fF^5l!6yCd~iCcu$bv!)IMsF@=l7GH
zoD5smw^pP?8IG;*tyRU$+HJ~8siFoitUrPRGdB`Yp>jjp#%VGt@U|#3{hK>G{g|zV
zhc*mUuL;^N3?4sf;a3}~PluV^3?8jI?Z)z=Kt`E)JG%rDV}@BRkAD18)h)N{vhBpoyiH@~9ED9)0yhKQRncK$
zNRDVHEmPNrsFqe8Vj&}`Yt(Rjh9((}w9)D|#e}IVKotHT7!nV$~PV
zsj{Ky_zC|zQjQn+Wfuws++mF#l0oO|2W5vANlO#vvMH|GV1&wZRQk}>d0od?5lthC
zBUB8|Zv3j7{$YrIU81uezHj!k=D%ck>ZuRTO4UCV
z|4hQ7N%Vuh$egCEwuO%&2A+Gbx?)j7bn%flSlA{*p;T{ED5M;c(YG-e+1|Vh!2tE>_I1ii
z_RI;ui5#^02hwV=XQUqSzQUh0Z
z%-oa{ga|ydeIIzcbTZ5Y^Lg5|sfOO@W=btS;5CD{Evj!Tu~x?%;cd?)xVe7>-6
zU!bcQGhHbCdEfI~psNMPr1Zvt8XUJW1SowFI#3b74|)}>_S}i0;Fb}SSOiNCvE9$Q
zP>ktpdsy6piXs*xL-)bh)o-Qlp}uSC9;%H8JB^Of>^@f2$2K@1`tV@lAQ?a3pfbl$0yvG1%Zq(2E(UuR|M@A
z#{o)?G4SDX(AN-#`;H3S9K2R@0A4akwWLufHO=0@bi?7-
zCrpFM#!QQ1xaZM;luQM^E@D`*gCw{j5In;&!NT*hC1{FnL`X))Wy{fIT`0Q&e%Rbl
zht0#BsVVhMgt;ZCXmdeQSmB{eBeb<_tYous7;c$d9@W7cEzt_JA*^q$owk;HABGo_
zB+)}`$rxGjmF7Fi>TNn?xJBJ+3=0t^zw9luNP=lO7YR1_5)EO*0k+dTJkdIeg-veT
zwmpe7Dru3z%CRFRJlQ7fmsf?1wmM|II(VEeMuy=RZFOfe7&*FTJFDUXwJ2M8Ic$*U
zRmBMtQT|%?3dgaqx%Fi66SM6#8VgPqdSuxZuCyDk%kEfVsj|%+yu!#?G)E0Ds#yUe
ztI^yQW85OqU_(dU&FthYt%!SVTq~qIz82_ef?($m*AAJ^L&$n3SO?$j9Oo7;p6oxj
zrfEUESc5p2@~rm@vo?`+e=Ud~tc$_a$1aA)rjqqAtqe3+57XX;1J=W|x8W4)VOYDE
zk^IhYnz?$!Ov*YHc!ob2QI}m3y8{`4C?jUQ0%dpmOBO{G9q_<(zPt}sEnMH75S379
zo82c5?}IO8mcZ3aesnM~+TnKIuT`F{iX6&Aj6KqyvF`2HK@l
z*loNQttq`E4RYKpyxY6aHOO5!)(-8tCB|+-f&jVAtNXUxfd62n`cUTF~j(=gNH|g1i%_I_2^bGa}W2HMszF*O&N{G7H;$YeJFa}{k
zJya-0RKK6tt0Rt?0r6rxW`4sfV$b6gbuITqtmFg(5wpV{#sHx3uZTk-kkcn>#L3QUL`o-sW6dv2yw
zbJs9em2pU()!<_cQ^vqLWd~FNuH03_#o*Zq9n8CTq7@&zdlCY7-&JX2aT|cH({bco
z7&%hgIYy!ti=M}8>U)rQ$wM=Pxeo>Z&R0+@Mw8FU9sHz)@Se=M{g}R
zSPA&$R2I9cQZf`N@(kBEGMH=Jth)YKW(uZ^yBXr1R^a*5=eFJwmlnGfpovbhDJU+jk)JI
zF6FlX)T3MoHoURv{sADSPZ@MHMJ#`jD(5KCJVG`WiHt+|oM;$K|
zzrzib?)m6a1#0>82aX_U?SAn1b;OsCE>2RF(;URSM7IcFrL!DvuOYRWhx
zQSSn*!sj24@CbD_t56>Bm*H(uN`*pRk@u1$1yxUsph{Cubl|S=L}abWXPL?O`{5oW
zvp2FjnS~us_TI<{cpQm?zdiLO%;e90LfEQ$QJ<0uiG=E8^@v*RhNs4MUOHdH(6LxN
zgFg$4d=&0DKZLsF&hM?7#S4gg4=ECCcgi=u|4Z41w+HsSMLUn>nzcZy%k=;Z9og-c
z>|S``$isoQj%D3q*isDtFIo!LOTUpOj5p?Y&hOdGeq`poi#XndS<_hBc;S8yK6UY%
z^=bCQH59T!A=CT&=l{nI3iwAPFQ(yvOE#+U*Gqp2q*!s-0UtmYXF0BT@L*l0a$s1
diff --git a/priv/static/packs/about.js.map b/priv/static/packs/about.js.map
index 741caf4b2d9ea4129799687d63d7068e18d5a219..c5ba0e31239f80105e44b483b7618796d2eab73d 100644
GIT binary patch
delta 46751
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