aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/pleroma/database.ex10
-rw-r--r--lib/mix/tasks/pleroma/emoji.ex28
-rw-r--r--lib/mix/tasks/pleroma/relay.ex10
-rw-r--r--lib/mix/tasks/pleroma/user.ex30
-rw-r--r--lib/pleroma/application.ex10
-rw-r--r--lib/pleroma/bbs/handler.ex5
-rw-r--r--lib/pleroma/conversation.ex8
-rw-r--r--lib/pleroma/conversation/participation.ex51
-rw-r--r--lib/pleroma/daemons/digest_email_daemon.ex2
-rw-r--r--lib/pleroma/emails/user_email.ex2
-rw-r--r--lib/pleroma/following_relationship.ex110
-rw-r--r--lib/pleroma/formatter.ex2
-rw-r--r--lib/pleroma/html.ex143
-rw-r--r--lib/pleroma/marker.ex74
-rw-r--r--lib/pleroma/moderation_log.ex138
-rw-r--r--lib/pleroma/notification.ex117
-rw-r--r--lib/pleroma/object.ex4
-rw-r--r--lib/pleroma/object/containment.ex29
-rw-r--r--lib/pleroma/object/fetcher.ex35
-rw-r--r--lib/pleroma/plugs/admin_secret_authentication_plug.ex2
-rw-r--r--lib/pleroma/plugs/oauth_plug.ex2
-rw-r--r--lib/pleroma/plugs/user_enabled_plug.ex2
-rw-r--r--lib/pleroma/plugs/user_is_admin_plug.ex2
-rw-r--r--lib/pleroma/reverse_proxy/reverse_proxy.ex6
-rw-r--r--lib/pleroma/stats.ex7
-rw-r--r--lib/pleroma/upload.ex33
-rw-r--r--lib/pleroma/user.ex843
-rw-r--r--lib/pleroma/user/info.ex478
-rw-r--r--lib/pleroma/user/query.ex56
-rw-r--r--lib/pleroma/user/search.ex160
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex174
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex10
-rw-r--r--lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex2
-rw-r--r--lib/pleroma/web/activity_pub/publisher.ex10
-rw-r--r--lib/pleroma/web/activity_pub/relay.ex25
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex114
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex99
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex31
-rw-r--r--lib/pleroma/web/activity_pub/visibility.ex5
-rw-r--r--lib/pleroma/web/admin_api/admin_api_controller.ex173
-rw-r--r--lib/pleroma/web/admin_api/report.ex5
-rw-r--r--lib/pleroma/web/admin_api/views/account_view.ex11
-rw-r--r--lib/pleroma/web/common_api/common_api.ex23
-rw-r--r--lib/pleroma/web/federator/federator.ex52
-rw-r--r--lib/pleroma/web/federator/publisher.ex26
-rw-r--r--lib/pleroma/web/masto_fe_controller.ex8
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/account_controller.ex64
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex4
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/marker_controller.ex32
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex8
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api.ex1
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex68
-rw-r--r--lib/pleroma/web/mastodon_api/views/conversation_view.ex6
-rw-r--r--lib/pleroma/web/mastodon_api/views/marker_view.ex17
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex8
-rw-r--r--lib/pleroma/web/mastodon_api/websocket_handler.ex7
-rw-r--r--lib/pleroma/web/oauth/oauth_controller.ex4
-rw-r--r--lib/pleroma/web/ostatus/activity_representer.ex313
-rw-r--r--lib/pleroma/web/ostatus/feed_representer.ex66
-rw-r--r--lib/pleroma/web/ostatus/handlers/delete_handler.ex18
-rw-r--r--lib/pleroma/web/ostatus/handlers/follow_handler.ex26
-rw-r--r--lib/pleroma/web/ostatus/handlers/note_handler.ex168
-rw-r--r--lib/pleroma/web/ostatus/handlers/unfollow_handler.ex22
-rw-r--r--lib/pleroma/web/ostatus/ostatus.ex395
-rw-r--r--lib/pleroma/web/ostatus/ostatus_controller.ex46
-rw-r--r--lib/pleroma/web/ostatus/user_representer.ex41
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/account_controller.ex18
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex4
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex9
-rw-r--r--lib/pleroma/web/push/impl.ex4
-rw-r--r--lib/pleroma/web/rel_me.ex14
-rw-r--r--lib/pleroma/web/router.ex27
-rw-r--r--lib/pleroma/web/salmon/salmon.ex254
-rw-r--r--lib/pleroma/web/streamer/streamer.ex6
-rw-r--r--lib/pleroma/web/streamer/worker.ex12
-rw-r--r--lib/pleroma/web/templates/feed/feed/feed.xml.eex2
-rw-r--r--lib/pleroma/web/templates/masto_fe/index.html.eex6
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api_controller.ex11
-rw-r--r--lib/pleroma/web/views/masto_fe_view.ex27
-rw-r--r--lib/pleroma/web/web_finger/web_finger.ex12
-rw-r--r--lib/pleroma/web/websub/websub.ex332
-rw-r--r--lib/pleroma/web/websub/websub_client_subscription.ex20
-rw-r--r--lib/pleroma/web/websub/websub_controller.ex99
-rw-r--r--lib/pleroma/web/websub/websub_server_subscription.ex17
-rw-r--r--lib/pleroma/workers/receiver_worker.ex4
-rw-r--r--lib/pleroma/workers/subscriber_worker.ex26
86 files changed, 1990 insertions, 3395 deletions
diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index cfd9eeada..e2b5251bc 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -28,7 +28,7 @@ defmodule Mix.Tasks.Pleroma.Database do
Logger.info("Removing embedded objects")
Repo.query!(
- "update activities set data = jsonb_set(data, '{object}'::text[], data->'object'->'id') where data->'object'->>'id' is not null;",
+ "update activities set data = safe_jsonb_set(data, '{object}'::text[], data->'object'->'id') where data->'object'->>'id' is not null;",
[],
timeout: :infinity
)
@@ -52,9 +52,9 @@ defmodule Mix.Tasks.Pleroma.Database do
def run(["update_users_following_followers_counts"]) do
start_pleroma()
- users = Repo.all(User)
- Enum.each(users, &User.remove_duplicated_following/1)
- Enum.each(users, &User.update_follower_count/1)
+ User
+ |> Repo.all()
+ |> Enum.each(&User.update_follower_count/1)
end
def run(["prune_objects" | args]) do
@@ -126,7 +126,7 @@ defmodule Mix.Tasks.Pleroma.Database do
set: [
data:
fragment(
- "jsonb_set(?, '{likes}', '[]'::jsonb, true)",
+ "safe_jsonb_set(?, '{likes}', '[]'::jsonb, true)",
object.data
)
]
diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex
index 6ef0a635d..35669af27 100644
--- a/lib/mix/tasks/pleroma/emoji.ex
+++ b/lib/mix/tasks/pleroma/emoji.ex
@@ -111,19 +111,21 @@ defmodule Mix.Tasks.Pleroma.Emoji do
file_list: files_to_unzip
)
- IO.puts(IO.ANSI.format(["Writing emoji.txt for ", :bright, pack_name]))
-
- emoji_txt_str =
- Enum.map(
- files,
- fn {shortcode, path} ->
- emojo_path = Path.join("/emoji/#{pack_name}", path)
- "#{shortcode}, #{emojo_path}"
- end
- )
- |> Enum.join("\n")
-
- File.write!(Path.join(pack_path, "emoji.txt"), emoji_txt_str)
+ IO.puts(IO.ANSI.format(["Writing pack.json for ", :bright, pack_name]))
+
+ pack_json = %{
+ pack: %{
+ "license" => pack["license"],
+ "homepage" => pack["homepage"],
+ "description" => pack["description"],
+ "fallback-src" => pack["src"],
+ "fallback-src-sha256" => pack["src_sha256"],
+ "share-files" => true
+ },
+ files: files
+ }
+
+ File.write!(Path.join(pack_path, "pack.json"), Jason.encode!(pack_json, pretty: true))
else
IO.puts(IO.ANSI.format([:bright, :red, "No pack named \"#{pack_name}\" found"]))
end
diff --git a/lib/mix/tasks/pleroma/relay.ex b/lib/mix/tasks/pleroma/relay.ex
index d7a7b599f..7ef5f9678 100644
--- a/lib/mix/tasks/pleroma/relay.ex
+++ b/lib/mix/tasks/pleroma/relay.ex
@@ -5,7 +5,6 @@
defmodule Mix.Tasks.Pleroma.Relay do
use Mix.Task
import Mix.Pleroma
- alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay
@shortdoc "Manages remote relays"
@@ -36,13 +35,10 @@ defmodule Mix.Tasks.Pleroma.Relay do
def run(["list"]) do
start_pleroma()
- with %User{following: following} = _user <- Relay.get_actor() do
- following
- |> Enum.map(fn entry -> URI.parse(entry).host end)
- |> Enum.uniq()
- |> Enum.each(&shell_info(&1))
+ with {:ok, list} <- Relay.list() do
+ list |> Enum.each(&shell_info(&1))
else
- e -> shell_error("Error while fetching relay subscription list: #{inspect(e)}")
+ {:error, e} -> shell_error("Error while fetching relay subscription list: #{inspect(e)}")
end
end
end
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index 134b5bccc..4e3b80db3 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -5,6 +5,7 @@
defmodule Mix.Tasks.Pleroma.User do
use Mix.Task
import Mix.Pleroma
+ alias Ecto.Changeset
alias Pleroma.User
alias Pleroma.UserInviteToken
alias Pleroma.Web.OAuth
@@ -109,10 +110,10 @@ defmodule Mix.Tasks.Pleroma.User do
start_pleroma()
with %User{} = user <- User.get_cached_by_nickname(nickname) do
- {:ok, user} = User.deactivate(user, !user.info.deactivated)
+ {:ok, user} = User.deactivate(user, !user.deactivated)
shell_info(
- "Activation status of #{nickname}: #{if(user.info.deactivated, do: "de", else: "")}activated"
+ "Activation status of #{nickname}: #{if(user.deactivated, do: "de", else: "")}activated"
)
else
_ ->
@@ -162,7 +163,7 @@ defmodule Mix.Tasks.Pleroma.User do
user = User.get_cached_by_id(user.id)
- if Enum.empty?(user.following) do
+ if Enum.empty?(User.get_friends(user)) do
shell_info("Successfully unsubscribed all followers from #{user.nickname}")
end
else
@@ -340,7 +341,7 @@ defmodule Mix.Tasks.Pleroma.User do
with %User{} = user <- User.get_cached_by_nickname(nickname) do
{:ok, user} = User.toggle_confirmation(user)
- message = if user.info.confirmation_pending, do: "needs", else: "doesn't need"
+ message = if user.confirmation_pending, do: "needs", else: "doesn't need"
shell_info("#{nickname} #{message} confirmation.")
else
@@ -364,23 +365,32 @@ defmodule Mix.Tasks.Pleroma.User do
end
defp set_moderator(user, value) do
- {:ok, user} = User.update_info(user, &User.Info.admin_api_update(&1, %{is_moderator: value}))
+ {:ok, user} =
+ user
+ |> Changeset.change(%{is_moderator: value})
+ |> User.update_and_set_cache()
- shell_info("Moderator status of #{user.nickname}: #{user.info.is_moderator}")
+ shell_info("Moderator status of #{user.nickname}: #{user.is_moderator}")
user
end
defp set_admin(user, value) do
- {:ok, user} = User.update_info(user, &User.Info.admin_api_update(&1, %{is_admin: value}))
+ {:ok, user} =
+ user
+ |> Changeset.change(%{is_admin: value})
+ |> User.update_and_set_cache()
- shell_info("Admin status of #{user.nickname}: #{user.info.is_admin}")
+ shell_info("Admin status of #{user.nickname}: #{user.is_admin}")
user
end
defp set_locked(user, value) do
- {:ok, user} = User.update_info(user, &User.Info.user_upgrade(&1, %{locked: value}))
+ {:ok, user} =
+ user
+ |> Changeset.change(%{locked: value})
+ |> User.update_and_set_cache()
- shell_info("Locked status of #{user.nickname}: #{user.info.locked}")
+ shell_info("Locked status of #{user.nickname}: #{user.locked}")
user
end
end
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 0bf218bc7..d681eecc8 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -161,11 +161,6 @@ defmodule Pleroma.Application do
id: :web_push_init,
start: {Task, :start_link, [&Pleroma.Web.Push.init/0]},
restart: :temporary
- },
- %{
- id: :federator_init,
- start: {Task, :start_link, [&Pleroma.Web.Federator.init/0]},
- restart: :temporary
}
]
end
@@ -178,11 +173,6 @@ defmodule Pleroma.Application do
restart: :temporary
},
%{
- id: :federator_init,
- start: {Task, :start_link, [&Pleroma.Web.Federator.init/0]},
- restart: :temporary
- },
- %{
id: :internal_fetch_init,
start: {Task, :start_link, [&Pleroma.Web.ActivityPub.InternalFetchActor.init/0]},
restart: :temporary
diff --git a/lib/pleroma/bbs/handler.ex b/lib/pleroma/bbs/handler.ex
index fa838a4e4..054d422b0 100644
--- a/lib/pleroma/bbs/handler.ex
+++ b/lib/pleroma/bbs/handler.ex
@@ -5,6 +5,7 @@
defmodule Pleroma.BBS.Handler do
use Sshd.ShellHandler
alias Pleroma.Activity
+ alias Pleroma.HTML
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
@@ -44,7 +45,7 @@ defmodule Pleroma.BBS.Handler do
def puts_activity(activity) do
status = Pleroma.Web.MastodonAPI.StatusView.render("show.json", %{activity: activity})
IO.puts("-- #{status.id} by #{status.account.display_name} (#{status.account.acct})")
- IO.puts(HtmlSanitizeEx.strip_tags(status.content))
+ IO.puts(HTML.strip_tags(status.content))
IO.puts("")
end
@@ -97,7 +98,7 @@ defmodule Pleroma.BBS.Handler do
|> Map.put("user", user)
activities =
- [user.ap_id | user.following]
+ [user.ap_id | Pleroma.User.following(user)]
|> ActivityPub.fetch_activities(params)
Enum.each(activities, fn activity ->
diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex
index 098016af2..ade3a526a 100644
--- a/lib/pleroma/conversation.ex
+++ b/lib/pleroma/conversation.ex
@@ -67,7 +67,13 @@ defmodule Pleroma.Conversation do
participations =
Enum.map(users, fn user ->
- User.increment_unread_conversation_count(conversation, user)
+ invisible_conversation = Enum.any?(users, &User.blocks?(user, &1))
+
+ unless invisible_conversation do
+ User.increment_unread_conversation_count(conversation, user)
+ end
+
+ opts = Keyword.put(opts, :invisible_conversation, invisible_conversation)
{:ok, participation} =
Participation.create_for_user_and_conversation(user, conversation, opts)
diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex
index ab81f3217..176b82a20 100644
--- a/lib/pleroma/conversation/participation.ex
+++ b/lib/pleroma/conversation/participation.ex
@@ -32,11 +32,20 @@ defmodule Pleroma.Conversation.Participation do
def create_for_user_and_conversation(user, conversation, opts \\ []) do
read = !!opts[:read]
+ invisible_conversation = !!opts[:invisible_conversation]
+
+ update_on_conflict =
+ if(invisible_conversation, do: [], else: [read: read])
+ |> Keyword.put(:updated_at, NaiveDateTime.utc_now())
%__MODULE__{}
- |> creation_cng(%{user_id: user.id, conversation_id: conversation.id, read: read})
+ |> creation_cng(%{
+ user_id: user.id,
+ conversation_id: conversation.id,
+ read: invisible_conversation || read
+ })
|> Repo.insert(
- on_conflict: [set: [read: read, updated_at: NaiveDateTime.utc_now()]],
+ on_conflict: [set: update_on_conflict],
returning: true,
conflict_target: [:user_id, :conversation_id]
)
@@ -48,6 +57,12 @@ defmodule Pleroma.Conversation.Participation do
|> validate_required([:read])
end
+ def mark_as_read(%User{} = user, %Conversation{} = conversation) do
+ with %__MODULE__{} = participation <- for_user_and_conversation(user, conversation) do
+ mark_as_read(participation)
+ end
+ end
+
def mark_as_read(participation) do
participation
|> read_cng(%{read: true})
@@ -63,6 +78,38 @@ defmodule Pleroma.Conversation.Participation do
end
end
+ def mark_all_as_read(%User{local: true} = user, %User{} = target_user) do
+ target_conversation_ids =
+ __MODULE__
+ |> where([p], p.user_id == ^target_user.id)
+ |> select([p], p.conversation_id)
+ |> Repo.all()
+
+ __MODULE__
+ |> where([p], p.user_id == ^user.id)
+ |> where([p], p.conversation_id in ^target_conversation_ids)
+ |> update([p], set: [read: true])
+ |> Repo.update_all([])
+
+ {:ok, user} = User.set_unread_conversation_count(user)
+ {:ok, user, []}
+ end
+
+ def mark_all_as_read(%User{} = user, %User{}), do: {:ok, user, []}
+
+ def mark_all_as_read(%User{} = user) do
+ {_, participations} =
+ __MODULE__
+ |> where([p], p.user_id == ^user.id)
+ |> where([p], not p.read)
+ |> update([p], set: [read: true])
+ |> select([p], p)
+ |> Repo.update_all([])
+
+ {:ok, user} = User.set_unread_conversation_count(user)
+ {:ok, user, participations}
+ end
+
def mark_as_unread(participation) do
participation
|> read_cng(%{read: false})
diff --git a/lib/pleroma/daemons/digest_email_daemon.ex b/lib/pleroma/daemons/digest_email_daemon.ex
index 462ad2c55..b4c8eaad9 100644
--- a/lib/pleroma/daemons/digest_email_daemon.ex
+++ b/lib/pleroma/daemons/digest_email_daemon.ex
@@ -17,7 +17,7 @@ defmodule Pleroma.Daemons.DigestEmailDaemon do
now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
from(u in inactive_users_query,
- where: fragment(~s(? #> '{"email_notifications","digest"}' @> 'true'), u.info),
+ where: fragment(~s(? ->'digest' @> 'true'), u.email_notifications),
where: u.last_digest_emailed_at < datetime_add(^now, ^negative_interval, "day"),
select: u
)
diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex
index 40b67ff56..a10f88f93 100644
--- a/lib/pleroma/emails/user_email.ex
+++ b/lib/pleroma/emails/user_email.ex
@@ -72,7 +72,7 @@ defmodule Pleroma.Emails.UserEmail do
Endpoint,
:confirm_email,
user.id,
- to_string(user.info.confirmation_token)
+ to_string(user.confirmation_token)
)
html_body = """
diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex
new file mode 100644
index 000000000..2ffac17ee
--- /dev/null
+++ b/lib/pleroma/following_relationship.ex
@@ -0,0 +1,110 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.FollowingRelationship do
+ use Ecto.Schema
+
+ import Ecto.Changeset
+ import Ecto.Query
+
+ alias FlakeId.Ecto.CompatType
+ alias Pleroma.Repo
+ alias Pleroma.User
+
+ schema "following_relationships" do
+ field(:state, :string, default: "accept")
+
+ belongs_to(:follower, User, type: CompatType)
+ belongs_to(:following, User, type: CompatType)
+
+ timestamps()
+ end
+
+ def changeset(%__MODULE__{} = following_relationship, attrs) do
+ following_relationship
+ |> cast(attrs, [:state])
+ |> put_assoc(:follower, attrs.follower)
+ |> put_assoc(:following, attrs.following)
+ |> validate_required([:state, :follower, :following])
+ end
+
+ def get(%User{} = follower, %User{} = following) do
+ __MODULE__
+ |> where(follower_id: ^follower.id, following_id: ^following.id)
+ |> Repo.one()
+ end
+
+ def update(follower, following, "reject"), do: unfollow(follower, following)
+
+ def update(%User{} = follower, %User{} = following, state) do
+ case get(follower, following) do
+ nil ->
+ follow(follower, following, state)
+
+ following_relationship ->
+ following_relationship
+ |> cast(%{state: state}, [:state])
+ |> validate_required([:state])
+ |> Repo.update()
+ end
+ end
+
+ def follow(%User{} = follower, %User{} = following, state \\ "accept") do
+ %__MODULE__{}
+ |> changeset(%{follower: follower, following: following, state: state})
+ |> Repo.insert(on_conflict: :nothing)
+ end
+
+ def unfollow(%User{} = follower, %User{} = following) do
+ case get(follower, following) do
+ nil -> {:ok, nil}
+ %__MODULE__{} = following_relationship -> Repo.delete(following_relationship)
+ end
+ end
+
+ def follower_count(%User{} = user) do
+ %{followers: user, deactivated: false}
+ |> User.Query.build()
+ |> Repo.aggregate(:count, :id)
+ end
+
+ def following_count(%User{id: nil}), do: 0
+
+ def following_count(%User{} = user) do
+ %{friends: user, deactivated: false}
+ |> User.Query.build()
+ |> Repo.aggregate(:count, :id)
+ end
+
+ def get_follow_requests(%User{id: id}) do
+ __MODULE__
+ |> join(:inner, [r], f in assoc(r, :follower))
+ |> where([r], r.state == "pending")
+ |> where([r], r.following_id == ^id)
+ |> select([r, f], f)
+ |> Repo.all()
+ end
+
+ def following?(%User{id: follower_id}, %User{id: followed_id}) do
+ __MODULE__
+ |> where(follower_id: ^follower_id, following_id: ^followed_id, state: "accept")
+ |> Repo.exists?()
+ end
+
+ def following(%User{} = user) do
+ following =
+ __MODULE__
+ |> join(:inner, [r], u in User, on: r.following_id == u.id)
+ |> where([r], r.follower_id == ^user.id)
+ |> where([r], r.state == "accept")
+ |> select([r, u], u.follower_address)
+ |> Repo.all()
+
+ if not user.local or user.nickname in [nil, "internal.fetch"] do
+ following
+ else
+ [user.follower_address | following]
+ end
+ end
+end
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index 931b9af2b..19b9af46c 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -127,7 +127,7 @@ defmodule Pleroma.Formatter do
end
end
- defp get_ap_id(%User{info: %{source_data: %{"url" => url}}}) when is_binary(url), do: url
+ defp get_ap_id(%User{source_data: %{"url" => url}}) when is_binary(url), do: url
defp get_ap_id(%User{ap_id: ap_id}), do: ap_id
defp get_nickname_text(nickname, %{mentions_format: :full}), do: User.full_nickname(nickname)
diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex
index 937bafed5..997e965f0 100644
--- a/lib/pleroma/html.ex
+++ b/lib/pleroma/html.ex
@@ -3,8 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTML do
- alias HtmlSanitizeEx.Scrubber
-
defp get_scrubbers(scrubber) when is_atom(scrubber), do: [scrubber]
defp get_scrubbers(scrubbers) when is_list(scrubbers), do: scrubbers
defp get_scrubbers(_), do: [Pleroma.HTML.Scrubber.Default]
@@ -24,9 +22,13 @@ defmodule Pleroma.HTML do
end)
end
- def filter_tags(html, scrubber), do: Scrubber.scrub(html, scrubber)
+ def filter_tags(html, scrubber) do
+ {:ok, content} = FastSanitize.Sanitizer.scrub(html, scrubber)
+ content
+ end
+
def filter_tags(html), do: filter_tags(html, nil)
- def strip_tags(html), do: Scrubber.scrub(html, Scrubber.StripTags)
+ def strip_tags(html), do: filter_tags(html, FastSanitize.Sanitizer.StripTags)
def get_cached_scrubbed_html_for_activity(
content,
@@ -46,7 +48,7 @@ defmodule Pleroma.HTML do
def get_cached_stripped_html_for_activity(content, activity, key) do
get_cached_scrubbed_html_for_activity(
content,
- HtmlSanitizeEx.Scrubber.StripTags,
+ FastSanitize.Sanitizer.StripTags,
activity,
key,
&HtmlEntities.decode/1
@@ -106,16 +108,15 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
@valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
- require HtmlSanitizeEx.Scrubber.Meta
- alias HtmlSanitizeEx.Scrubber.Meta
+ require FastSanitize.Sanitizer.Meta
+ alias FastSanitize.Sanitizer.Meta
- Meta.remove_cdata_sections_before_scrub()
Meta.strip_comments()
# links
- Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
+ Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
- Meta.allow_tag_with_this_attribute_values("a", "class", [
+ Meta.allow_tag_with_this_attribute_values(:a, "class", [
"hashtag",
"u-url",
"mention",
@@ -123,29 +124,29 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
"mention u-url"
])
- Meta.allow_tag_with_this_attribute_values("a", "rel", [
+ Meta.allow_tag_with_this_attribute_values(:a, "rel", [
"tag",
"nofollow",
"noopener",
"noreferrer"
])
- Meta.allow_tag_with_these_attributes("a", ["name", "title"])
+ Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
# paragraphs and linebreaks
- Meta.allow_tag_with_these_attributes("br", [])
- Meta.allow_tag_with_these_attributes("p", [])
+ Meta.allow_tag_with_these_attributes(:br, [])
+ Meta.allow_tag_with_these_attributes(:p, [])
# microformats
- Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card"])
- Meta.allow_tag_with_these_attributes("span", [])
+ Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
+ Meta.allow_tag_with_these_attributes(:span, [])
# allow inline images for custom emoji
if Pleroma.Config.get([:markup, :allow_inline_images]) do
# restrict img tags to http/https only, because of MediaProxy.
- Meta.allow_tag_with_uri_attributes("img", ["src"], ["http", "https"])
+ Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
- Meta.allow_tag_with_these_attributes("img", [
+ Meta.allow_tag_with_these_attributes(:img, [
"width",
"height",
"class",
@@ -160,19 +161,18 @@ end
defmodule Pleroma.HTML.Scrubber.Default do
@doc "The default HTML scrubbing policy: no "
- require HtmlSanitizeEx.Scrubber.Meta
- alias HtmlSanitizeEx.Scrubber.Meta
+ require FastSanitize.Sanitizer.Meta
+ alias FastSanitize.Sanitizer.Meta
# credo:disable-for-previous-line
# No idea how to fix this one…
@valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
- Meta.remove_cdata_sections_before_scrub()
Meta.strip_comments()
- Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
+ Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
- Meta.allow_tag_with_this_attribute_values("a", "class", [
+ Meta.allow_tag_with_this_attribute_values(:a, "class", [
"hashtag",
"u-url",
"mention",
@@ -180,7 +180,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
"mention u-url"
])
- Meta.allow_tag_with_this_attribute_values("a", "rel", [
+ Meta.allow_tag_with_this_attribute_values(:a, "rel", [
"tag",
"nofollow",
"noopener",
@@ -188,37 +188,37 @@ defmodule Pleroma.HTML.Scrubber.Default do
"ugc"
])
- Meta.allow_tag_with_these_attributes("a", ["name", "title"])
-
- Meta.allow_tag_with_these_attributes("abbr", ["title"])
-
- Meta.allow_tag_with_these_attributes("b", [])
- Meta.allow_tag_with_these_attributes("blockquote", [])
- Meta.allow_tag_with_these_attributes("br", [])
- Meta.allow_tag_with_these_attributes("code", [])
- Meta.allow_tag_with_these_attributes("del", [])
- Meta.allow_tag_with_these_attributes("em", [])
- Meta.allow_tag_with_these_attributes("i", [])
- Meta.allow_tag_with_these_attributes("li", [])
- Meta.allow_tag_with_these_attributes("ol", [])
- Meta.allow_tag_with_these_attributes("p", [])
- Meta.allow_tag_with_these_attributes("pre", [])
- Meta.allow_tag_with_these_attributes("strong", [])
- Meta.allow_tag_with_these_attributes("sub", [])
- Meta.allow_tag_with_these_attributes("sup", [])
- Meta.allow_tag_with_these_attributes("u", [])
- Meta.allow_tag_with_these_attributes("ul", [])
-
- Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card"])
- Meta.allow_tag_with_these_attributes("span", [])
+ Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
+
+ Meta.allow_tag_with_these_attributes(:abbr, ["title"])
+
+ Meta.allow_tag_with_these_attributes(:b, [])
+ Meta.allow_tag_with_these_attributes(:blockquote, [])
+ Meta.allow_tag_with_these_attributes(:br, [])
+ Meta.allow_tag_with_these_attributes(:code, [])
+ Meta.allow_tag_with_these_attributes(:del, [])
+ Meta.allow_tag_with_these_attributes(:em, [])
+ Meta.allow_tag_with_these_attributes(:i, [])
+ Meta.allow_tag_with_these_attributes(:li, [])
+ Meta.allow_tag_with_these_attributes(:ol, [])
+ Meta.allow_tag_with_these_attributes(:p, [])
+ Meta.allow_tag_with_these_attributes(:pre, [])
+ Meta.allow_tag_with_these_attributes(:strong, [])
+ Meta.allow_tag_with_these_attributes(:sub, [])
+ Meta.allow_tag_with_these_attributes(:sup, [])
+ Meta.allow_tag_with_these_attributes(:u, [])
+ Meta.allow_tag_with_these_attributes(:ul, [])
+
+ Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
+ Meta.allow_tag_with_these_attributes(:span, [])
@allow_inline_images Pleroma.Config.get([:markup, :allow_inline_images])
if @allow_inline_images do
# restrict img tags to http/https only, because of MediaProxy.
- Meta.allow_tag_with_uri_attributes("img", ["src"], ["http", "https"])
+ Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
- Meta.allow_tag_with_these_attributes("img", [
+ Meta.allow_tag_with_these_attributes(:img, [
"width",
"height",
"class",
@@ -228,24 +228,24 @@ defmodule Pleroma.HTML.Scrubber.Default do
end
if Pleroma.Config.get([:markup, :allow_tables]) do
- Meta.allow_tag_with_these_attributes("table", [])
- Meta.allow_tag_with_these_attributes("tbody", [])
- Meta.allow_tag_with_these_attributes("td", [])
- Meta.allow_tag_with_these_attributes("th", [])
- Meta.allow_tag_with_these_attributes("thead", [])
- Meta.allow_tag_with_these_attributes("tr", [])
+ Meta.allow_tag_with_these_attributes(:table, [])
+ Meta.allow_tag_with_these_attributes(:tbody, [])
+ Meta.allow_tag_with_these_attributes(:td, [])
+ Meta.allow_tag_with_these_attributes(:th, [])
+ Meta.allow_tag_with_these_attributes(:thead, [])
+ Meta.allow_tag_with_these_attributes(:tr, [])
end
if Pleroma.Config.get([:markup, :allow_headings]) do
- Meta.allow_tag_with_these_attributes("h1", [])
- Meta.allow_tag_with_these_attributes("h2", [])
- Meta.allow_tag_with_these_attributes("h3", [])
- Meta.allow_tag_with_these_attributes("h4", [])
- Meta.allow_tag_with_these_attributes("h5", [])
+ Meta.allow_tag_with_these_attributes(:h1, [])
+ Meta.allow_tag_with_these_attributes(:h2, [])
+ Meta.allow_tag_with_these_attributes(:h3, [])
+ Meta.allow_tag_with_these_attributes(:h4, [])
+ Meta.allow_tag_with_these_attributes(:h5, [])
end
if Pleroma.Config.get([:markup, :allow_fonts]) do
- Meta.allow_tag_with_these_attributes("font", ["face"])
+ Meta.allow_tag_with_these_attributes(:font, ["face"])
end
Meta.strip_everything_not_covered()
@@ -258,7 +258,7 @@ defmodule Pleroma.HTML.Transform.MediaProxy do
def before_scrub(html), do: html
- def scrub_attribute("img", {"src", "http" <> target}) do
+ def scrub_attribute(:img, {"src", "http" <> target}) do
media_url =
("http" <> target)
|> MediaProxy.url()
@@ -268,16 +268,16 @@ defmodule Pleroma.HTML.Transform.MediaProxy do
def scrub_attribute(_tag, attribute), do: attribute
- def scrub({"img", attributes, children}) do
+ def scrub({:img, attributes, children}) do
attributes =
attributes
- |> Enum.map(fn attr -> scrub_attribute("img", attr) end)
+ |> Enum.map(fn attr -> scrub_attribute(:img, attr) end)
|> Enum.reject(&is_nil(&1))
- {"img", attributes, children}
+ {:img, attributes, children}
end
- def scrub({:comment, _children}), do: ""
+ def scrub({:comment, _text, _children}), do: ""
def scrub({tag, attributes, children}), do: {tag, attributes, children}
def scrub({_tag, children}), do: children
@@ -291,16 +291,15 @@ defmodule Pleroma.HTML.Scrubber.LinksOnly do
@valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
- require HtmlSanitizeEx.Scrubber.Meta
- alias HtmlSanitizeEx.Scrubber.Meta
+ require FastSanitize.Sanitizer.Meta
+ alias FastSanitize.Sanitizer.Meta
- Meta.remove_cdata_sections_before_scrub()
Meta.strip_comments()
# links
- Meta.allow_tag_with_uri_attributes("a", ["href"], @valid_schemes)
+ Meta.allow_tag_with_uri_attributes(:a, ["href"], @valid_schemes)
- Meta.allow_tag_with_this_attribute_values("a", "rel", [
+ Meta.allow_tag_with_this_attribute_values(:a, "rel", [
"tag",
"nofollow",
"noopener",
@@ -309,6 +308,6 @@ defmodule Pleroma.HTML.Scrubber.LinksOnly do
"ugc"
])
- Meta.allow_tag_with_these_attributes("a", ["name", "title"])
+ Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
Meta.strip_everything_not_covered()
end
diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex
new file mode 100644
index 000000000..7f87c86c3
--- /dev/null
+++ b/lib/pleroma/marker.ex
@@ -0,0 +1,74 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Marker do
+ use Ecto.Schema
+
+ import Ecto.Changeset
+ import Ecto.Query
+
+ alias Ecto.Multi
+ alias Pleroma.Repo
+ alias Pleroma.User
+
+ @timelines ["notifications"]
+
+ schema "markers" do
+ field(:last_read_id, :string, default: "")
+ field(:timeline, :string, default: "")
+ field(:lock_version, :integer, default: 0)
+
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
+ timestamps()
+ end
+
+ def get_markers(user, timelines \\ []) do
+ Repo.all(get_query(user, timelines))
+ end
+
+ def upsert(%User{} = user, attrs) do
+ attrs
+ |> Map.take(@timelines)
+ |> Enum.reduce(Multi.new(), fn {timeline, timeline_attrs}, multi ->
+ marker =
+ user
+ |> get_marker(timeline)
+ |> changeset(timeline_attrs)
+
+ Multi.insert(multi, timeline, marker,
+ returning: true,
+ on_conflict: {:replace, [:last_read_id]},
+ conflict_target: [:user_id, :timeline]
+ )
+ end)
+ |> Repo.transaction()
+ end
+
+ defp get_marker(user, timeline) do
+ case Repo.find_resource(get_query(user, timeline)) do
+ {:ok, marker} -> %__MODULE__{marker | user: user}
+ _ -> %__MODULE__{timeline: timeline, user_id: user.id}
+ end
+ end
+
+ @doc false
+ defp changeset(marker, attrs) do
+ marker
+ |> cast(attrs, [:last_read_id])
+ |> validate_required([:user_id, :timeline, :last_read_id])
+ |> validate_inclusion(:timeline, @timelines)
+ end
+
+ defp by_timeline(query, timeline) do
+ from(m in query, where: m.timeline in ^List.wrap(timeline))
+ end
+
+ defp by_user_id(query, id), do: from(m in query, where: m.user_id == ^id)
+
+ defp get_query(user, timelines) do
+ __MODULE__
+ |> by_user_id(user.id)
+ |> by_timeline(timelines)
+ end
+end
diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex
index 352cad433..9dc4a94c9 100644
--- a/lib/pleroma/moderation_log.ex
+++ b/lib/pleroma/moderation_log.ex
@@ -86,18 +86,18 @@ defmodule Pleroma.ModerationLog do
parsed_datetime
end
- @spec insert_log(%{actor: User, subject: User, action: String.t(), permission: String.t()}) ::
+ @spec insert_log(%{actor: User, subject: [User], action: String.t(), permission: String.t()}) ::
{:ok, ModerationLog} | {:error, any}
def insert_log(%{
actor: %User{} = actor,
- subject: %User{} = subject,
+ subject: subjects,
action: action,
permission: permission
}) do
%ModerationLog{
data: %{
"actor" => user_to_map(actor),
- "subject" => user_to_map(subject),
+ "subject" => user_to_map(subjects),
"action" => action,
"permission" => permission,
"message" => ""
@@ -303,13 +303,16 @@ defmodule Pleroma.ModerationLog do
end
@spec insert_log_entry_with_message(ModerationLog) :: {:ok, ModerationLog} | {:error, any}
-
defp insert_log_entry_with_message(entry) do
entry.data["message"]
|> put_in(get_log_entry_message(entry))
|> Repo.insert()
end
+ defp user_to_map(users) when is_list(users) do
+ users |> Enum.map(&user_to_map/1)
+ end
+
defp user_to_map(%User{} = user) do
user
|> Map.from_struct()
@@ -349,10 +352,10 @@ defmodule Pleroma.ModerationLog do
data: %{
"actor" => %{"nickname" => actor_nickname},
"action" => "delete",
- "subject" => %{"nickname" => subject_nickname, "type" => "user"}
+ "subject" => subjects
}
}) do
- "@#{actor_nickname} deleted user @#{subject_nickname}"
+ "@#{actor_nickname} deleted users: #{users_to_nicknames_string(subjects)}"
end
@spec get_log_entry_message(ModerationLog) :: String.t()
@@ -363,12 +366,25 @@ defmodule Pleroma.ModerationLog do
"subjects" => subjects
}
}) do
- nicknames =
- subjects
- |> Enum.map(&"@#{&1["nickname"]}")
- |> Enum.join(", ")
+ "@#{actor_nickname} created users: #{users_to_nicknames_string(subjects)}"
+ end
- "@#{actor_nickname} created users: #{nicknames}"
+ @spec get_log_entry_message(ModerationLog) :: String.t()
+ def get_log_entry_message(%ModerationLog{
+ data: %{
+ "actor" => %{"nickname" => actor_nickname},
+ "action" => "activate",
+ "subject" => user
+ }
+ })
+ when is_map(user) do
+ get_log_entry_message(%ModerationLog{
+ data: %{
+ "actor" => %{"nickname" => actor_nickname},
+ "action" => "activate",
+ "subject" => [user]
+ }
+ })
end
@spec get_log_entry_message(ModerationLog) :: String.t()
@@ -376,10 +392,10 @@ defmodule Pleroma.ModerationLog do
data: %{
"actor" => %{"nickname" => actor_nickname},
"action" => "activate",
- "subject" => %{"nickname" => subject_nickname, "type" => "user"}
+ "subject" => users
}
}) do
- "@#{actor_nickname} activated user @#{subject_nickname}"
+ "@#{actor_nickname} activated users: #{users_to_nicknames_string(users)}"
end
@spec get_log_entry_message(ModerationLog) :: String.t()
@@ -387,10 +403,28 @@ defmodule Pleroma.ModerationLog do
data: %{
"actor" => %{"nickname" => actor_nickname},
"action" => "deactivate",
- "subject" => %{"nickname" => subject_nickname, "type" => "user"}
+ "subject" => user
+ }
+ })
+ when is_map(user) do
+ get_log_entry_message(%ModerationLog{
+ data: %{
+ "actor" => %{"nickname" => actor_nickname},
+ "action" => "deactivate",
+ "subject" => [user]
+ }
+ })
+ end
+
+ @spec get_log_entry_message(ModerationLog) :: String.t()
+ def get_log_entry_message(%ModerationLog{
+ data: %{
+ "actor" => %{"nickname" => actor_nickname},
+ "action" => "deactivate",
+ "subject" => users
}
}) do
- "@#{actor_nickname} deactivated user @#{subject_nickname}"
+ "@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}"
end
@spec get_log_entry_message(ModerationLog) :: String.t()
@@ -402,14 +436,9 @@ defmodule Pleroma.ModerationLog do
"action" => "tag"
}
}) do
- nicknames_string =
- nicknames
- |> Enum.map(&"@#{&1}")
- |> Enum.join(", ")
-
tags_string = tags |> Enum.join(", ")
- "@#{actor_nickname} added tags: #{tags_string} to users: #{nicknames_string}"
+ "@#{actor_nickname} added tags: #{tags_string} to users: #{nicknames_to_string(nicknames)}"
end
@spec get_log_entry_message(ModerationLog) :: String.t()
@@ -421,14 +450,9 @@ defmodule Pleroma.ModerationLog do
"action" => "untag"
}
}) do
- nicknames_string =
- nicknames
- |> Enum.map(&"@#{&1}")
- |> Enum.join(", ")
-
tags_string = tags |> Enum.join(", ")
- "@#{actor_nickname} removed tags: #{tags_string} from users: #{nicknames_string}"
+ "@#{actor_nickname} removed tags: #{tags_string} from users: #{nicknames_to_string(nicknames)}"
end
@spec get_log_entry_message(ModerationLog) :: String.t()
@@ -436,11 +460,31 @@ defmodule Pleroma.ModerationLog do
data: %{
"actor" => %{"nickname" => actor_nickname},
"action" => "grant",
- "subject" => %{"nickname" => subject_nickname},
+ "subject" => user,
+ "permission" => permission
+ }
+ })
+ when is_map(user) do
+ get_log_entry_message(%ModerationLog{
+ data: %{
+ "actor" => %{"nickname" => actor_nickname},
+ "action" => "grant",
+ "subject" => [user],
+ "permission" => permission
+ }
+ })
+ end
+
+ @spec get_log_entry_message(ModerationLog) :: String.t()
+ def get_log_entry_message(%ModerationLog{
+ data: %{
+ "actor" => %{"nickname" => actor_nickname},
+ "action" => "grant",
+ "subject" => users,
"permission" => permission
}
}) do
- "@#{actor_nickname} made @#{subject_nickname} #{permission}"
+ "@#{actor_nickname} made #{users_to_nicknames_string(users)} #{permission}"
end
@spec get_log_entry_message(ModerationLog) :: String.t()
@@ -448,11 +492,31 @@ defmodule Pleroma.ModerationLog do
data: %{
"actor" => %{"nickname" => actor_nickname},
"action" => "revoke",
- "subject" => %{"nickname" => subject_nickname},
+ "subject" => user,
+ "permission" => permission
+ }
+ })
+ when is_map(user) do
+ get_log_entry_message(%ModerationLog{
+ data: %{
+ "actor" => %{"nickname" => actor_nickname},
+ "action" => "revoke",
+ "subject" => [user],
+ "permission" => permission
+ }
+ })
+ end
+
+ @spec get_log_entry_message(ModerationLog) :: String.t()
+ def get_log_entry_message(%ModerationLog{
+ data: %{
+ "actor" => %{"nickname" => actor_nickname},
+ "action" => "revoke",
+ "subject" => users,
"permission" => permission
}
}) do
- "@#{actor_nickname} revoked #{permission} role from @#{subject_nickname}"
+ "@#{actor_nickname} revoked #{permission} role from #{users_to_nicknames_string(users)}"
end
@spec get_log_entry_message(ModerationLog) :: String.t()
@@ -551,4 +615,16 @@ defmodule Pleroma.ModerationLog do
}) do
"@#{actor_nickname} deleted status ##{subject_id}"
end
+
+ defp nicknames_to_string(nicknames) do
+ nicknames
+ |> Enum.map(&"@#{&1}")
+ |> Enum.join(", ")
+ end
+
+ defp users_to_nicknames_string(users) do
+ users
+ |> Enum.map(&"@#{&1["nickname"]}")
+ |> Enum.join(", ")
+ end
end
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index d94ae5971..b7ecf51e4 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -17,6 +17,7 @@ defmodule Pleroma.Notification do
import Ecto.Query
import Ecto.Changeset
+ require Logger
@type t :: %__MODULE__{}
@@ -34,43 +35,97 @@ defmodule Pleroma.Notification do
end
def for_user_query(user, opts \\ []) do
- query =
- Notification
- |> where(user_id: ^user.id)
- |> where(
- [n, a],
+ Notification
+ |> where(user_id: ^user.id)
+ |> where(
+ [n, a],
+ fragment(
+ "? not in (SELECT ap_id FROM users WHERE deactivated = 'true')",
+ a.actor
+ )
+ )
+ |> join(:inner, [n], activity in assoc(n, :activity))
+ |> join(:left, [n, a], object in Object,
+ on:
fragment(
- "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
- a.actor
+ "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
+ object.data,
+ a.data
)
- )
- |> join(:inner, [n], activity in assoc(n, :activity))
- |> join(:left, [n, a], object in Object,
- on:
- fragment(
- "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
- object.data,
- a.data
- )
- )
- |> preload([n, a, o], activity: {a, object: o})
+ )
+ |> preload([n, a, o], activity: {a, object: o})
+ |> exclude_muted(user, opts)
+ |> exclude_blocked(user)
+ |> exclude_visibility(opts)
+ end
+
+ defp exclude_blocked(query, user) do
+ query
+ |> where([n, a], a.actor not in ^user.blocks)
+ |> where(
+ [n, a],
+ fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.domain_blocks
+ )
+ end
+
+ defp exclude_muted(query, _, %{with_muted: true}) do
+ query
+ end
+
+ defp exclude_muted(query, user, _opts) do
+ query
+ |> where([n, a], a.actor not in ^user.muted_notifications)
+ |> join(:left, [n, a], tm in Pleroma.ThreadMute,
+ on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
+ )
+ |> where([n, a, o, tm], is_nil(tm.user_id))
+ end
+
+ @valid_visibilities ~w[direct unlisted public private]
- if opts[:with_muted] do
+ defp exclude_visibility(query, %{exclude_visibilities: visibility})
+ when is_list(visibility) do
+ if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
query
- else
- where(query, [n, a], a.actor not in ^user.info.muted_notifications)
- |> where([n, a], a.actor not in ^user.info.blocks)
|> where(
[n, a],
- fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks
- )
- |> join(:left, [n, a], tm in Pleroma.ThreadMute,
- on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
+ not fragment(
+ "activity_visibility(?, ?, ?) = ANY (?)",
+ a.actor,
+ a.recipients,
+ a.data,
+ ^visibility
+ )
)
- |> where([n, a, o, tm], is_nil(tm.user_id))
+ else
+ Logger.error("Could not exclude visibility to #{visibility}")
+ query
end
end
+ defp exclude_visibility(query, %{exclude_visibilities: visibility})
+ when visibility in @valid_visibilities do
+ query
+ |> where(
+ [n, a],
+ not fragment(
+ "activity_visibility(?, ?, ?) = (?)",
+ a.actor,
+ a.recipients,
+ a.data,
+ ^visibility
+ )
+ )
+ end
+
+ defp exclude_visibility(query, %{exclude_visibilities: visibility})
+ when visibility not in @valid_visibilities do
+ Logger.error("Could not exclude visibility to #{visibility}")
+ query
+ end
+
+ defp exclude_visibility(query, _visibility), do: query
+
def for_user(user, opts \\ %{}) do
user
|> for_user_query(opts)
@@ -259,7 +314,7 @@ defmodule Pleroma.Notification do
def skip?(
:followers,
activity,
- %{info: %{notification_settings: %{"followers" => false}}} = user
+ %{notification_settings: %{"followers" => false}} = user
) do
actor = activity.data["actor"]
follower = User.get_cached_by_ap_id(actor)
@@ -269,14 +324,14 @@ defmodule Pleroma.Notification do
def skip?(
:non_followers,
activity,
- %{info: %{notification_settings: %{"non_followers" => false}}} = user
+ %{notification_settings: %{"non_followers" => false}} = user
) do
actor = activity.data["actor"]
follower = User.get_cached_by_ap_id(actor)
!User.following?(follower, user)
end
- def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
+ def skip?(:follows, activity, %{notification_settings: %{"follows" => false}} = user) do
actor = activity.data["actor"]
followed = User.get_cached_by_ap_id(actor)
User.following?(user, followed)
@@ -285,7 +340,7 @@ defmodule Pleroma.Notification do
def skip?(
:non_follows,
activity,
- %{info: %{notification_settings: %{"non_follows" => false}}} = user
+ %{notification_settings: %{"non_follows" => false}} = user
) do
actor = activity.data["actor"]
followed = User.get_cached_by_ap_id(actor)
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index cdfbacb0e..d9b41d710 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -181,7 +181,7 @@ defmodule Pleroma.Object do
data:
fragment(
"""
- jsonb_set(?, '{repliesCount}',
+ safe_jsonb_set(?, '{repliesCount}',
(coalesce((?->>'repliesCount')::int, 0) + 1)::varchar::jsonb, true)
""",
o.data,
@@ -204,7 +204,7 @@ defmodule Pleroma.Object do
data:
fragment(
"""
- jsonb_set(?, '{repliesCount}',
+ safe_jsonb_set(?, '{repliesCount}',
(greatest(0, (?->>'repliesCount')::int - 1))::varchar::jsonb, true)
""",
o.data,
diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex
index f077a9f32..68535c09e 100644
--- a/lib/pleroma/object/containment.ex
+++ b/lib/pleroma/object/containment.ex
@@ -32,6 +32,23 @@ defmodule Pleroma.Object.Containment do
get_actor(%{"actor" => actor})
end
+ # TODO: We explicitly allow 'tag' URIs through, due to references to legacy OStatus
+ # objects being present in the test suite environment. Once these objects are
+ # removed, please also remove this.
+ if Mix.env() == :test do
+ defp compare_uris(_, %URI{scheme: "tag"}), do: :ok
+ end
+
+ defp compare_uris(%URI{} = id_uri, %URI{} = other_uri) do
+ if id_uri.host == other_uri.host do
+ :ok
+ else
+ :error
+ end
+ end
+
+ defp compare_uris(_, _), do: :error
+
@doc """
Checks that an imported AP object's actor matches the domain it came from.
"""
@@ -41,11 +58,7 @@ defmodule Pleroma.Object.Containment do
id_uri = URI.parse(id)
actor_uri = URI.parse(get_actor(params))
- if id_uri.host == actor_uri.host do
- :ok
- else
- :error
- end
+ compare_uris(actor_uri, id_uri)
end
def contain_origin(id, %{"attributedTo" => actor} = params),
@@ -57,11 +70,7 @@ defmodule Pleroma.Object.Containment do
id_uri = URI.parse(id)
other_uri = URI.parse(other_id)
- if id_uri.host == other_uri.host do
- :ok
- else
- :error
- end
+ compare_uris(id_uri, other_uri)
end
def contain_child(%{"object" => %{"id" => id, "attributedTo" => _} = object}),
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index 5e064fd87..441ae8b65 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -10,7 +10,6 @@ defmodule Pleroma.Object.Fetcher do
alias Pleroma.Signature
alias Pleroma.Web.ActivityPub.InternalFetchActor
alias Pleroma.Web.ActivityPub.Transmogrifier
- alias Pleroma.Web.OStatus
require Logger
require Pleroma.Constants
@@ -67,7 +66,8 @@ defmodule Pleroma.Object.Fetcher do
{:normalize, nil} <- {:normalize, Object.normalize(data, false)},
params <- prepare_activity_params(data),
{:containment, :ok} <- {:containment, Containment.contain_origin(id, params)},
- {:ok, activity} <- Transmogrifier.handle_incoming(params, options),
+ {:transmogrifier, {:ok, activity}} <-
+ {:transmogrifier, Transmogrifier.handle_incoming(params, options)},
{:object, _data, %Object{} = object} <-
{:object, data, Object.normalize(activity, false)} do
{:ok, object}
@@ -75,9 +75,12 @@ defmodule Pleroma.Object.Fetcher do
{:containment, _} ->
{:error, "Object containment failed."}
- {:error, {:reject, nil}} ->
+ {:transmogrifier, {:error, {:reject, nil}}} ->
{:reject, nil}
+ {:transmogrifier, _} ->
+ {:error, "Transmogrifier failure."}
+
{:object, data, nil} ->
reinject_object(%Object{}, data)
@@ -87,15 +90,11 @@ defmodule Pleroma.Object.Fetcher do
{:fetch_object, %Object{} = object} ->
{:ok, object}
- _e ->
- # Only fallback when receiving a fetch/normalization error with ActivityPub
- Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
+ {:fetch, {:error, error}} ->
+ {:error, error}
- # FIXME: OStatus Object Containment?
- case OStatus.fetch_activity_from_url(id) do
- {:ok, [activity | _]} -> {:ok, Object.normalize(activity, false)}
- e -> e
- end
+ e ->
+ e
end
end
@@ -114,7 +113,11 @@ defmodule Pleroma.Object.Fetcher do
with {:ok, object} <- fetch_object_from_id(id, options) do
object
else
- _e ->
+ {:error, %Tesla.Mock.Error{}} ->
+ nil
+
+ e ->
+ Logger.error("Error while fetching #{id}: #{inspect(e)}")
nil
end
end
@@ -161,7 +164,7 @@ defmodule Pleroma.Object.Fetcher do
Logger.debug("Fetch headers: #{inspect(headers)}")
- with true <- String.starts_with?(id, "http"),
+ with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")},
{:ok, %{body: body, status: code}} when code in 200..299 <- HTTP.get(id, headers),
{:ok, data} <- Jason.decode(body),
:ok <- Containment.contain_origin_from_id(id, data) do
@@ -170,6 +173,12 @@ defmodule Pleroma.Object.Fetcher do
{:ok, %{status: code}} when code in [404, 410] ->
{:error, "Object has been deleted"}
+ {:scheme, _} ->
+ {:error, "Unsupported URI scheme"}
+
+ {:error, e} ->
+ {:error, e}
+
e ->
{:error, e}
end
diff --git a/lib/pleroma/plugs/admin_secret_authentication_plug.ex b/lib/pleroma/plugs/admin_secret_authentication_plug.ex
index 5baf8a691..fdadd476e 100644
--- a/lib/pleroma/plugs/admin_secret_authentication_plug.ex
+++ b/lib/pleroma/plugs/admin_secret_authentication_plug.ex
@@ -19,7 +19,7 @@ defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do
def call(%{params: %{"admin_token" => admin_token}} = conn, _) do
if secret_token() && admin_token == secret_token() do
conn
- |> assign(:user, %User{info: %{is_admin: true}})
+ |> assign(:user, %User{is_admin: true})
else
conn
end
diff --git a/lib/pleroma/plugs/oauth_plug.ex b/lib/pleroma/plugs/oauth_plug.ex
index 86bc4aa3a..fd004fcd2 100644
--- a/lib/pleroma/plugs/oauth_plug.ex
+++ b/lib/pleroma/plugs/oauth_plug.ex
@@ -71,7 +71,7 @@ defmodule Pleroma.Plugs.OAuthPlug do
)
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
- with %Token{user: %{info: %{deactivated: false} = _} = user} = token_record <- Repo.one(query) do
+ with %Token{user: %{deactivated: false} = user} = token_record <- Repo.one(query) do
{:ok, user, token_record}
end
end
diff --git a/lib/pleroma/plugs/user_enabled_plug.ex b/lib/pleroma/plugs/user_enabled_plug.ex
index da892c28b..fbb4bf115 100644
--- a/lib/pleroma/plugs/user_enabled_plug.ex
+++ b/lib/pleroma/plugs/user_enabled_plug.ex
@@ -10,7 +10,7 @@ defmodule Pleroma.Plugs.UserEnabledPlug do
options
end
- def call(%{assigns: %{user: %User{info: %{deactivated: true}}}} = conn, _) do
+ def call(%{assigns: %{user: %User{deactivated: true}}} = conn, _) do
conn
|> assign(:user, nil)
end
diff --git a/lib/pleroma/plugs/user_is_admin_plug.ex b/lib/pleroma/plugs/user_is_admin_plug.ex
index 4c4b3d610..ee808f31f 100644
--- a/lib/pleroma/plugs/user_is_admin_plug.ex
+++ b/lib/pleroma/plugs/user_is_admin_plug.ex
@@ -11,7 +11,7 @@ defmodule Pleroma.Plugs.UserIsAdminPlug do
options
end
- def call(%{assigns: %{user: %User{info: %{is_admin: true}}}} = conn, _) do
+ def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _) do
conn
end
diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy/reverse_proxy.ex
index 78144cae3..2ed719315 100644
--- a/lib/pleroma/reverse_proxy/reverse_proxy.ex
+++ b/lib/pleroma/reverse_proxy/reverse_proxy.ex
@@ -401,11 +401,9 @@ defmodule Pleroma.ReverseProxy do
defp client, do: Pleroma.ReverseProxy.Client
- defp track_failed_url(url, code, opts) do
- code = to_string(code)
-
+ defp track_failed_url(url, error, opts) do
ttl =
- if code in ["403", "404"] or String.starts_with?(code, "5") do
+ unless error in [:body_too_large, 400, 204] do
Keyword.get(opts, :failed_request_ttl, @failed_request_ttl)
else
nil
diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex
index df80fbaa4..8154a09b7 100644
--- a/lib/pleroma/stats.ex
+++ b/lib/pleroma/stats.ex
@@ -68,12 +68,7 @@ defmodule Pleroma.Stats do
domain_count = Enum.count(peers)
- status_query =
- from(u in User.Query.build(%{local: true}),
- select: fragment("sum((?->>'note_count')::int)", u.info)
- )
-
- status_count = Repo.one(status_query)
+ status_count = Repo.aggregate(User.Query.build(%{local: true}), :sum, :note_count)
user_count = Repo.aggregate(User.Query.build(%{local: true, active: true}), :count, :id)
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index 9f0adde5b..2e0986197 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -105,7 +105,7 @@ defmodule Pleroma.Upload do
{Pleroma.Config.get!([:instance, :upload_limit]), "Document"}
end
- opts = %{
+ %{
activity_type: Keyword.get(opts, :activity_type, activity_type),
size_limit: Keyword.get(opts, :size_limit, size_limit),
uploader: Keyword.get(opts, :uploader, Pleroma.Config.get([__MODULE__, :uploader])),
@@ -118,37 +118,6 @@ defmodule Pleroma.Upload do
Pleroma.Config.get([__MODULE__, :base_url], Pleroma.Web.base_url())
)
}
-
- # TODO: 1.0+ : remove old config compatibility
- opts =
- if Pleroma.Config.get([__MODULE__, :strip_exif]) == true &&
- !Enum.member?(opts.filters, Pleroma.Upload.Filter.Mogrify) do
- Logger.warn("""
- Pleroma: configuration `:instance, :strip_exif` is deprecated, please instead set:
-
- :pleroma, Pleroma.Upload, [filters: [Pleroma.Upload.Filter.Mogrify]]
-
- :pleroma, Pleroma.Upload.Filter.Mogrify, args: ["strip", "auto-orient"]
- """)
-
- Pleroma.Config.put([Pleroma.Upload.Filter.Mogrify], args: ["strip", "auto-orient"])
- Map.put(opts, :filters, opts.filters ++ [Pleroma.Upload.Filter.Mogrify])
- else
- opts
- end
-
- if Pleroma.Config.get([:instance, :dedupe_media]) == true &&
- !Enum.member?(opts.filters, Pleroma.Upload.Filter.Dedupe) do
- Logger.warn("""
- Pleroma: configuration `:instance, :dedupe_media` is deprecated, please instead set:
-
- :pleroma, Pleroma.Upload, [filters: [Pleroma.Upload.Filter.Dedupe]]
- """)
-
- Map.put(opts, :filters, opts.filters ++ [Pleroma.Upload.Filter.Dedupe])
- else
- opts
- end
end
defp prepare_upload(%Plug.Upload{} = file, opts) do
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 2cfb13a8c..f8c2db1e1 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -13,6 +13,7 @@ defmodule Pleroma.User do
alias Pleroma.Activity
alias Pleroma.Conversation.Participation
alias Pleroma.Delivery
+ alias Pleroma.FollowingRelationship
alias Pleroma.Keys
alias Pleroma.Notification
alias Pleroma.Object
@@ -26,9 +27,7 @@ defmodule Pleroma.User do
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
alias Pleroma.Web.OAuth
- alias Pleroma.Web.OStatus
alias Pleroma.Web.RelMe
- alias Pleroma.Web.Websub
alias Pleroma.Workers.BackgroundWorker
require Logger
@@ -52,7 +51,6 @@ defmodule Pleroma.User do
field(:password, :string, virtual: true)
field(:password_confirmation, :string, virtual: true)
field(:keys, :string)
- field(:following, {:array, :string}, default: [])
field(:ap_id, :string)
field(:avatar, :map)
field(:local, :boolean, default: true)
@@ -63,15 +61,70 @@ defmodule Pleroma.User do
field(:tags, {:array, :string}, default: [])
field(:last_refreshed_at, :naive_datetime_usec)
field(:last_digest_emailed_at, :naive_datetime)
+
+ field(:banner, :map, default: %{})
+ field(:background, :map, default: %{})
+ field(:source_data, :map, default: %{})
+ field(:note_count, :integer, default: 0)
+ field(:follower_count, :integer, default: 0)
+ # Should be filled in only for remote users
+ field(:following_count, :integer, default: nil)
+ field(:locked, :boolean, default: false)
+ field(:confirmation_pending, :boolean, default: false)
+ field(:password_reset_pending, :boolean, default: false)
+ field(:confirmation_token, :string, default: nil)
+ field(:default_scope, :string, default: "public")
+ field(:blocks, {:array, :string}, default: [])
+ field(:domain_blocks, {:array, :string}, default: [])
+ field(:mutes, {:array, :string}, default: [])
+ field(:muted_reblogs, {:array, :string}, default: [])
+ field(:muted_notifications, {:array, :string}, default: [])
+ field(:subscribers, {:array, :string}, default: [])
+ field(:deactivated, :boolean, default: false)
+ field(:no_rich_text, :boolean, default: false)
+ field(:ap_enabled, :boolean, default: false)
+ field(:is_moderator, :boolean, default: false)
+ field(:is_admin, :boolean, default: false)
+ field(:show_role, :boolean, default: true)
+ field(:settings, :map, default: nil)
+ field(:magic_key, :string, default: nil)
+ field(:uri, :string, default: nil)
+ field(:hide_followers_count, :boolean, default: false)
+ field(:hide_follows_count, :boolean, default: false)
+ field(:hide_followers, :boolean, default: false)
+ field(:hide_follows, :boolean, default: false)
+ field(:hide_favorites, :boolean, default: true)
+ field(:unread_conversation_count, :integer, default: 0)
+ field(:pinned_activities, {:array, :string}, default: [])
+ field(:email_notifications, :map, default: %{"digest" => false})
+ field(:mascot, :map, default: nil)
+ field(:emoji, {:array, :map}, default: [])
+ field(:pleroma_settings_store, :map, default: %{})
+ field(:fields, {:array, :map}, default: [])
+ field(:raw_fields, {:array, :map}, default: [])
+ field(:discoverable, :boolean, default: false)
+ field(:invisible, :boolean, default: false)
+ field(:skip_thread_containment, :boolean, default: false)
+
+ field(:notification_settings, :map,
+ default: %{
+ "followers" => true,
+ "follows" => true,
+ "non_follows" => true,
+ "non_followers" => true
+ }
+ )
+
has_many(:notifications, Notification)
has_many(:registrations, Registration)
has_many(:deliveries, Delivery)
- embeds_one(:info, User.Info)
+
+ field(:info, :map, default: %{})
timestamps()
end
- def auth_active?(%User{info: %User.Info{confirmation_pending: true}}),
+ def auth_active?(%User{confirmation_pending: true}),
do: !Pleroma.Config.get([:instance, :account_activation_required])
def auth_active?(%User{}), do: true
@@ -86,10 +139,13 @@ defmodule Pleroma.User do
def visible_for?(_, _), do: false
- def superuser?(%User{local: true, info: %User.Info{is_admin: true}}), do: true
- def superuser?(%User{local: true, info: %User.Info{is_moderator: true}}), do: true
+ def superuser?(%User{local: true, is_admin: true}), do: true
+ def superuser?(%User{local: true, is_moderator: true}), do: true
def superuser?(_), do: false
+ def invisible?(%User{invisible: true}), do: true
+ def invisible?(_), do: false
+
def avatar_url(user, options \\ []) do
case user.avatar do
%{"url" => [%{"href" => href} | _]} -> href
@@ -98,13 +154,13 @@ defmodule Pleroma.User do
end
def banner_url(user, options \\ []) do
- case user.info.banner do
+ case user.banner do
%{"url" => [%{"href" => href} | _]} -> href
_ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
end
end
- def profile_url(%User{info: %{source_data: %{"url" => url}}}), do: url
+ def profile_url(%User{source_data: %{"url" => url}}), do: url
def profile_url(%User{ap_id: ap_id}), do: ap_id
def profile_url(_), do: nil
@@ -119,15 +175,15 @@ defmodule Pleroma.User do
def user_info(%User{} = user, args \\ %{}) do
following_count =
- Map.get(args, :following_count, user.info.following_count || following_count(user))
+ Map.get(args, :following_count, user.following_count || following_count(user))
- follower_count = Map.get(args, :follower_count, user.info.follower_count)
+ follower_count = Map.get(args, :follower_count, user.follower_count)
%{
- note_count: user.info.note_count,
- locked: user.info.locked,
- confirmation_pending: user.info.confirmation_pending,
- default_scope: user.info.default_scope
+ note_count: user.note_count,
+ locked: user.locked,
+ confirmation_pending: user.confirmation_pending,
+ default_scope: user.default_scope
}
|> Map.put(:following_count, following_count)
|> Map.put(:follower_count, follower_count)
@@ -157,17 +213,17 @@ defmodule Pleroma.User do
@spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
def restrict_deactivated(query) do
- from(u in query,
- where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info)
- )
+ from(u in query, where: u.deactivated != ^true)
end
- def following_count(%User{following: []}), do: 0
+ defdelegate following_count(user), to: FollowingRelationship
- def following_count(%User{} = user) do
- user
- |> get_friends_query()
- |> Repo.aggregate(:count, :id)
+ defp truncate_fields_param(params) do
+ if Map.has_key?(params, :fields) do
+ Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
+ else
+ params
+ end
end
defp truncate_if_exists(params, key, max_length) do
@@ -188,18 +244,43 @@ defmodule Pleroma.User do
|> Map.put(:info, params[:info] || %{})
|> truncate_if_exists(:name, name_limit)
|> truncate_if_exists(:bio, bio_limit)
+ |> truncate_fields_param()
changeset =
%User{local: false}
- |> cast(params, [:bio, :name, :ap_id, :nickname, :avatar])
+ |> cast(
+ params,
+ [
+ :bio,
+ :name,
+ :ap_id,
+ :nickname,
+ :avatar,
+ :ap_enabled,
+ :source_data,
+ :banner,
+ :locked,
+ :magic_key,
+ :uri,
+ :hide_followers,
+ :hide_follows,
+ :hide_followers_count,
+ :hide_follows_count,
+ :follower_count,
+ :fields,
+ :following_count,
+ :discoverable,
+ :invisible
+ ]
+ )
|> validate_required([:name, :ap_id])
|> unique_constraint(:nickname)
|> validate_format(:nickname, @email_regex)
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, max: name_limit)
- |> change_info(&User.Info.remote_user_creation(&1, params[:info]))
+ |> validate_fields(true)
- case params[:info][:source_data] do
+ case params[:source_data] do
%{"followers" => followers, "following" => following} ->
changeset
|> put_change(:follower_address, followers)
@@ -216,11 +297,35 @@ defmodule Pleroma.User do
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
struct
- |> cast(params, [:bio, :name, :avatar, :following])
+ |> cast(
+ params,
+ [
+ :bio,
+ :name,
+ :avatar,
+ :locked,
+ :no_rich_text,
+ :default_scope,
+ :banner,
+ :hide_follows,
+ :hide_followers,
+ :hide_followers_count,
+ :hide_follows_count,
+ :hide_favorites,
+ :background,
+ :show_role,
+ :skip_thread_containment,
+ :fields,
+ :raw_fields,
+ :pleroma_settings_store,
+ :discoverable
+ ]
+ )
|> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit)
+ |> validate_fields(false)
end
def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
@@ -229,20 +334,38 @@ defmodule Pleroma.User do
params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
+ params = if remote?, do: truncate_fields_param(params), else: params
+
struct
- |> cast(params, [
- :bio,
- :name,
- :follower_address,
- :following_address,
- :avatar,
- :last_refreshed_at
- ])
+ |> cast(
+ params,
+ [
+ :bio,
+ :name,
+ :follower_address,
+ :following_address,
+ :avatar,
+ :last_refreshed_at,
+ :ap_enabled,
+ :source_data,
+ :banner,
+ :locked,
+ :magic_key,
+ :follower_count,
+ :following_count,
+ :hide_follows,
+ :fields,
+ :hide_followers,
+ :discoverable,
+ :hide_followers_count,
+ :hide_follows_count
+ ]
+ )
|> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, max: name_limit)
- |> change_info(&User.Info.user_upgrade(&1, params[:info], remote?))
+ |> validate_fields(remote?)
end
def password_update_changeset(struct, params) do
@@ -250,8 +373,8 @@ defmodule Pleroma.User do
|> cast(params, [:password, :password_confirmation])
|> validate_required([:password, :password_confirmation])
|> validate_confirmation(:password)
- |> put_password_hash
- |> put_embed(:info, User.Info.set_password_reset_pending(struct.info, false))
+ |> put_password_hash()
+ |> put_change(:password_reset_pending, false)
end
@spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
@@ -268,19 +391,19 @@ defmodule Pleroma.User do
end
end
+ def update_password_reset_pending(user, value) do
+ user
+ |> change()
+ |> put_change(:password_reset_pending, value)
+ |> update_and_set_cache()
+ end
+
def force_password_reset_async(user) do
BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
end
@spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
- def force_password_reset(user) do
- info_cng = User.Info.set_password_reset_pending(user.info, true)
-
- user
- |> change()
- |> put_embed(:info, info_cng)
- |> update_and_set_cache()
- end
+ def force_password_reset(user), do: update_password_reset_pending(user, true)
def register_changeset(struct, params \\ %{}, opts \\ []) do
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
@@ -294,6 +417,7 @@ defmodule Pleroma.User do
end
struct
+ |> confirmation_changeset(need_confirmation: need_confirmation?)
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
|> validate_required([:name, :nickname, :password, :password_confirmation])
|> validate_confirmation(:password)
@@ -304,7 +428,6 @@ defmodule Pleroma.User do
|> validate_format(:email, @email_regex)
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit)
- |> change_info(&User.Info.confirmation_changeset(&1, need_confirmation: need_confirmation?))
|> maybe_validate_required_email(opts[:external])
|> put_password_hash
|> put_ap_id()
@@ -324,7 +447,6 @@ defmodule Pleroma.User do
followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
changeset
- |> put_change(:following, [followers])
|> put_change(:follower_address, followers)
end
@@ -355,7 +477,7 @@ defmodule Pleroma.User do
end
def try_send_confirmation_email(%User{} = user) do
- if user.info.confirmation_pending &&
+ if user.confirmation_pending &&
Pleroma.Config.get([:instance, :account_activation_required]) do
user
|> Pleroma.Emails.UserEmail.account_confirmation_email()
@@ -378,8 +500,8 @@ defmodule Pleroma.User do
def needs_update?(_), do: true
@spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
- def maybe_direct_follow(%User{} = follower, %User{local: true, info: %{locked: true}}) do
- {:ok, follower}
+ def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
+ follow(follower, followed, "pending")
end
def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
@@ -397,58 +519,32 @@ defmodule Pleroma.User do
@doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
@spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
def follow_all(follower, followeds) do
- followed_addresses =
- followeds
- |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
- |> Enum.map(fn %{follower_address: fa} -> fa end)
-
- q =
- from(u in User,
- where: u.id == ^follower.id,
- update: [
- set: [
- following:
- fragment(
- "array(select distinct unnest (array_cat(?, ?)))",
- u.following,
- ^followed_addresses
- )
- ]
- ],
- select: u
- )
+ followeds =
+ Enum.reject(followeds, fn followed ->
+ blocks?(follower, followed) || blocks?(followed, follower)
+ end)
- {1, [follower]} = Repo.update_all(q, [])
+ Enum.each(followeds, &follow(follower, &1, "accept"))
Enum.each(followeds, &update_follower_count/1)
set_cache(follower)
end
- def follow(%User{} = follower, %User{info: info} = followed) do
+ defdelegate following(user), to: FollowingRelationship
+
+ def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
- ap_followers = followed.follower_address
cond do
- info.deactivated ->
- {:error, "Could not follow user: You are deactivated."}
+ followed.deactivated ->
+ {:error, "Could not follow user: #{followed.nickname} is deactivated."}
deny_follow_blocked and blocks?(followed, follower) ->
{:error, "Could not follow user: #{followed.nickname} blocked you."}
true ->
- if !followed.local && follower.local && !ap_enabled?(followed) do
- Websub.subscribe(follower, followed)
- end
-
- q =
- from(u in User,
- where: u.id == ^follower.id,
- update: [push: [following: ^ap_followers]],
- select: u
- )
-
- {1, [follower]} = Repo.update_all(q, [])
+ FollowingRelationship.follow(follower, followed, state)
follower = maybe_update_following_count(follower)
@@ -459,17 +555,8 @@ defmodule Pleroma.User do
end
def unfollow(%User{} = follower, %User{} = followed) do
- ap_followers = followed.follower_address
-
if following?(follower, followed) and follower.ap_id != followed.ap_id do
- q =
- from(u in User,
- where: u.id == ^follower.id,
- update: [pull: [following: ^ap_followers]],
- select: u
- )
-
- {1, [follower]} = Repo.update_all(q, [])
+ FollowingRelationship.unfollow(follower, followed)
follower = maybe_update_following_count(follower)
@@ -483,13 +570,10 @@ defmodule Pleroma.User do
end
end
- @spec following?(User.t(), User.t()) :: boolean
- def following?(%User{} = follower, %User{} = followed) do
- Enum.member?(follower.following, followed.follower_address)
- end
+ defdelegate following?(follower, followed), to: FollowingRelationship
def locked?(%User{} = user) do
- user.info.locked || false
+ user.locked || false
end
def get_by_id(id) do
@@ -532,6 +616,12 @@ defmodule Pleroma.User do
{:ok, user}
end
+ def update_and_set_cache(struct, params) do
+ struct
+ |> update_changeset(params)
+ |> update_and_set_cache()
+ end
+
def update_and_set_cache(changeset) do
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
set_cache(user)
@@ -614,12 +704,7 @@ defmodule Pleroma.User do
Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
end
- def fetch_by_nickname(nickname) do
- case ActivityPub.make_user_from_nickname(nickname) do
- {:ok, user} -> {:ok, user}
- _ -> OStatus.make_user(nickname)
- end
- end
+ def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
def get_or_fetch_by_nickname(nickname) do
with %User{} = user <- get_by_nickname(nickname) do
@@ -707,30 +792,12 @@ defmodule Pleroma.User do
|> Repo.all()
end
- @spec get_follow_requests(User.t()) :: {:ok, [User.t()]}
- def get_follow_requests(%User{} = user) do
- user
- |> Activity.follow_requests_for_actor()
- |> join(:inner, [a], u in User, on: a.actor == u.ap_id)
- |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
- |> group_by([a, u], u.id)
- |> select([a, u], u)
- |> Repo.all()
- end
+ defdelegate get_follow_requests(user), to: FollowingRelationship
def increase_note_count(%User{} = user) do
User
|> where(id: ^user.id)
- |> update([u],
- set: [
- info:
- fragment(
- "jsonb_set(?, '{note_count}', ((?->>'note_count')::int + 1)::varchar::jsonb, true)",
- u.info,
- u.info
- )
- ]
- )
+ |> update([u], inc: [note_count: 1])
|> select([u], u)
|> Repo.update_all([])
|> case do
@@ -744,12 +811,7 @@ defmodule Pleroma.User do
|> where(id: ^user.id)
|> update([u],
set: [
- info:
- fragment(
- "jsonb_set(?, '{note_count}', (greatest(0, (?->>'note_count')::int - 1))::varchar::jsonb, true)",
- u.info,
- u.info
- )
+ note_count: fragment("greatest(0, note_count - 1)")
]
)
|> select([u], u)
@@ -760,28 +822,18 @@ defmodule Pleroma.User do
end
end
- def update_note_count(%User{} = user) do
+ def update_note_count(%User{} = user, note_count \\ nil) do
note_count =
- from(
- a in Object,
- where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
- select: count(a.id)
- )
- |> Repo.one()
-
- update_info(user, &User.Info.set_note_count(&1, note_count))
- end
-
- def update_mascot(user, url) do
- info_changeset =
- User.Info.mascot_update(
- user.info,
- url
- )
+ note_count ||
+ from(
+ a in Object,
+ where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
+ select: count(a.id)
+ )
+ |> Repo.one()
user
- |> change()
- |> put_embed(:info, info_changeset)
+ |> cast(%{note_count: note_count}, [:note_count])
|> update_and_set_cache()
end
@@ -799,10 +851,24 @@ defmodule Pleroma.User do
def fetch_follow_information(user) do
with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
- update_info(user, &User.Info.follow_information_update(&1, info))
+ user
+ |> follow_information_changeset(info)
+ |> update_and_set_cache()
end
end
+ defp follow_information_changeset(user, params) do
+ user
+ |> cast(params, [
+ :hide_followers,
+ :hide_follows,
+ :follower_count,
+ :following_count,
+ :hide_followers_count,
+ :hide_follows_count
+ ])
+ end
+
def update_follower_count(%User{} = user) do
if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
follower_count_query =
@@ -813,14 +879,7 @@ defmodule Pleroma.User do
|> where(id: ^user.id)
|> join(:inner, [u], s in subquery(follower_count_query))
|> update([u, s],
- set: [
- info:
- fragment(
- "jsonb_set(?, '{follower_count}', ?::varchar::jsonb, true)",
- u.info,
- s.count
- )
- ]
+ set: [follower_count: s.count]
)
|> select([u], u)
|> Repo.update_all([])
@@ -850,14 +909,7 @@ defmodule Pleroma.User do
User
|> join(:inner, [u], p in subquery(unread_query))
|> update([u, p],
- set: [
- info:
- fragment(
- "jsonb_set(?, '{unread_conversation_count}', ?::varchar::jsonb, true)",
- u.info,
- p.count
- )
- ]
+ set: [unread_conversation_count: p.count]
)
|> where([u], u.id == ^user.id)
|> select([u], u)
@@ -868,7 +920,7 @@ defmodule Pleroma.User do
end
end
- def set_unread_conversation_count(_), do: :noop
+ def set_unread_conversation_count(user), do: {:ok, user}
def increment_unread_conversation_count(conversation, %User{local: true} = user) do
unread_query =
@@ -878,14 +930,7 @@ defmodule Pleroma.User do
User
|> join(:inner, [u], p in subquery(unread_query))
|> update([u, p],
- set: [
- info:
- fragment(
- "jsonb_set(?, '{unread_conversation_count}', (coalesce((?->>'unread_conversation_count')::int, 0) + 1)::varchar::jsonb, true)",
- u.info,
- u.info
- )
- ]
+ inc: [unread_conversation_count: 1]
)
|> where([u], u.id == ^user.id)
|> where([u, p], p.count == 0)
@@ -897,19 +942,7 @@ defmodule Pleroma.User do
end
end
- def increment_unread_conversation_count(_, _), do: :noop
-
- def remove_duplicated_following(%User{following: following} = user) do
- uniq_following = Enum.uniq(following)
-
- if length(following) == length(uniq_following) do
- {:ok, user}
- else
- user
- |> update_changeset(%{following: uniq_following})
- |> update_and_set_cache()
- end
- end
+ def increment_unread_conversation_count(_, user), do: {:ok, user}
@spec get_users_from_set([String.t()], boolean()) :: [User.t()]
def get_users_from_set(ap_ids, local_only \\ true) do
@@ -928,11 +961,11 @@ defmodule Pleroma.User do
@spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
- update_info(muter, &User.Info.add_to_mutes(&1, ap_id, notifications?))
+ add_to_mutes(muter, ap_id, notifications?)
end
def unmute(muter, %{ap_id: ap_id}) do
- update_info(muter, &User.Info.remove_from_mutes(&1, ap_id))
+ remove_from_mutes(muter, ap_id)
end
def subscribe(subscriber, %{ap_id: ap_id}) do
@@ -942,14 +975,14 @@ defmodule Pleroma.User do
if blocks?(subscribed, subscriber) and deny_follow_blocked do
{:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
else
- update_info(subscribed, &User.Info.add_to_subscribers(&1, subscriber.ap_id))
+ User.add_to_subscribers(subscribed, subscriber.ap_id)
end
end
end
def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
with %User{} = user <- get_cached_by_ap_id(ap_id) do
- update_info(user, &User.Info.remove_from_subscribers(&1, unsubscriber.ap_id))
+ User.remove_from_subscribers(user, unsubscriber.ap_id)
end
end
@@ -981,8 +1014,8 @@ defmodule Pleroma.User do
if following?(blocked, blocker), do: unfollow(blocked, blocker)
{:ok, blocker} = update_follower_count(blocker)
-
- update_info(blocker, &User.Info.add_to_block(&1, ap_id))
+ {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
+ add_to_block(blocker, ap_id)
end
# helper to handle the block given only an actor's AP id
@@ -991,17 +1024,17 @@ defmodule Pleroma.User do
end
def unblock(blocker, %{ap_id: ap_id}) do
- update_info(blocker, &User.Info.remove_from_block(&1, ap_id))
+ remove_from_block(blocker, ap_id)
end
def mutes?(nil, _), do: false
- def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
+ def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
@spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
def muted_notifications?(nil, _), do: false
def muted_notifications?(user, %{ap_id: ap_id}),
- do: Enum.member?(user.info.muted_notifications, ap_id)
+ do: Enum.member?(user.muted_notifications, ap_id)
def blocks?(%User{} = user, %User{} = target) do
blocks_ap_id?(user, target) || blocks_domain?(user, target)
@@ -1010,13 +1043,13 @@ defmodule Pleroma.User do
def blocks?(nil, _), do: false
def blocks_ap_id?(%User{} = user, %User{} = target) do
- Enum.member?(user.info.blocks, target.ap_id)
+ Enum.member?(user.blocks, target.ap_id)
end
def blocks_ap_id?(_, _), do: false
def blocks_domain?(%User{} = user, %User{} = target) do
- domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks)
+ domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
%{host: host} = URI.parse(target.ap_id)
Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
end
@@ -1025,51 +1058,75 @@ defmodule Pleroma.User do
def subscribed_to?(user, %{ap_id: ap_id}) do
with %User{} = target <- get_cached_by_ap_id(ap_id) do
- Enum.member?(target.info.subscribers, user.ap_id)
+ Enum.member?(target.subscribers, user.ap_id)
end
end
@spec muted_users(User.t()) :: [User.t()]
def muted_users(user) do
- User.Query.build(%{ap_id: user.info.mutes, deactivated: false})
+ User.Query.build(%{ap_id: user.mutes, deactivated: false})
|> Repo.all()
end
@spec blocked_users(User.t()) :: [User.t()]
def blocked_users(user) do
- User.Query.build(%{ap_id: user.info.blocks, deactivated: false})
+ User.Query.build(%{ap_id: user.blocks, deactivated: false})
|> Repo.all()
end
@spec subscribers(User.t()) :: [User.t()]
def subscribers(user) do
- User.Query.build(%{ap_id: user.info.subscribers, deactivated: false})
+ User.Query.build(%{ap_id: user.subscribers, deactivated: false})
|> Repo.all()
end
- def block_domain(user, domain) do
- update_info(user, &User.Info.add_to_domain_block(&1, domain))
+ def deactivate_async(user, status \\ true) do
+ BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
end
- def unblock_domain(user, domain) do
- update_info(user, &User.Info.remove_from_domain_block(&1, domain))
- end
+ def deactivate(user, status \\ true)
- def deactivate_async(user, status \\ true) do
- BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
+ def deactivate(users, status) when is_list(users) do
+ Repo.transaction(fn ->
+ for user <- users, do: deactivate(user, status)
+ end)
end
- def deactivate(%User{} = user, status \\ true) do
- with {:ok, user} <- update_info(user, &User.Info.set_activation_status(&1, status)) do
+ def deactivate(%User{} = user, status) do
+ with {:ok, user} <- set_activation_status(user, status) do
Enum.each(get_followers(user), &invalidate_cache/1)
- Enum.each(get_friends(user), &update_follower_count/1)
+
+ # Only update local user counts, remote will be update during the next pull.
+ user
+ |> get_friends()
+ |> Enum.filter(& &1.local)
+ |> Enum.each(&update_follower_count/1)
{:ok, user}
end
end
- def update_notification_settings(%User{} = user, settings \\ %{}) do
- update_info(user, &User.Info.update_notification_settings(&1, settings))
+ def update_notification_settings(%User{} = user, settings) do
+ settings =
+ settings
+ |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
+ |> Map.new()
+
+ notification_settings =
+ user.notification_settings
+ |> Map.merge(settings)
+ |> Map.take(["followers", "follows", "non_follows", "non_followers"])
+
+ params = %{notification_settings: notification_settings}
+
+ user
+ |> cast(params, [:notification_settings])
+ |> validate_required([:notification_settings])
+ |> update_and_set_cache()
+ end
+
+ def delete(users) when is_list(users) do
+ for user <- users, do: delete(user)
end
def delete(%User{} = user) do
@@ -1107,7 +1164,7 @@ defmodule Pleroma.User do
pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
# Insert all the posts in reverse order, so they're in the right order on the timeline
- user.info.source_data["outbox"]
+ user.source_data["outbox"]
|> Utils.fetch_ordered_collection(pages)
|> Enum.reverse()
|> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
@@ -1228,24 +1285,13 @@ defmodule Pleroma.User do
defp delete_activity(_activity), do: "Doing nothing"
- def html_filter_policy(%User{info: %{no_rich_text: true}}) do
+ def html_filter_policy(%User{no_rich_text: true}) do
Pleroma.HTML.Scrubber.TwitterText
end
def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
- def fetch_by_ap_id(ap_id) do
- case ActivityPub.make_user_from_ap_id(ap_id) do
- {:ok, user} ->
- {:ok, user}
-
- _ ->
- case OStatus.make_user(ap_id) do
- {:ok, user} -> {:ok, user}
- _ -> {:error, "Could not fetch by AP id"}
- end
- end
- end
+ def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
def get_or_fetch_by_ap_id(ap_id) do
user = get_cached_by_ap_id(ap_id)
@@ -1275,7 +1321,7 @@ defmodule Pleroma.User do
else
_ ->
{:ok, user} =
- %User{info: %User.Info{}}
+ %User{}
|> cast(%{}, [:ap_id, :nickname, :local])
|> put_change(:ap_id, uri)
|> put_change(:nickname, nickname)
@@ -1288,9 +1334,7 @@ defmodule Pleroma.User do
end
# AP style
- def public_key_from_info(%{
- source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}
- }) do
+ def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
key =
public_key_pem
|> :public_key.pem_decode()
@@ -1300,16 +1344,11 @@ defmodule Pleroma.User do
{:ok, key}
end
- # OStatus Magic Key
- def public_key_from_info(%{magic_key: magic_key}) when not is_nil(magic_key) do
- {:ok, Pleroma.Web.Salmon.decode_key(magic_key)}
- end
-
- def public_key_from_info(_), do: {:error, "not found key"}
+ def public_key(_), do: {:error, "not found key"}
def get_public_key_for_ap_id(ap_id) do
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
- {:ok, public_key} <- public_key_from_info(user.info) do
+ {:ok, public_key} <- public_key(user) do
{:ok, public_key}
else
_ -> :error
@@ -1328,7 +1367,7 @@ defmodule Pleroma.User do
end
def ap_enabled?(%User{local: true}), do: true
- def ap_enabled?(%User{info: info}), do: info.ap_enabled
+ def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
def ap_enabled?(_), do: false
@doc "Gets or fetch a user by uri or nickname."
@@ -1441,7 +1480,6 @@ defmodule Pleroma.User do
%User{
name: ap_id,
ap_id: ap_id,
- info: %User.Info{},
nickname: "erroruser@example.com",
inserted_at: NaiveDateTime.utc_now()
}
@@ -1454,7 +1492,7 @@ defmodule Pleroma.User do
end
def showing_reblogs?(%User{} = user, %User{} = target) do
- target.ap_id not in user.info.muted_reblogs
+ target.ap_id not in user.muted_reblogs
end
@doc """
@@ -1486,7 +1524,7 @@ defmodule Pleroma.User do
left_join: a in Pleroma.Activity,
on: u.ap_id == a.actor,
where: not is_nil(u.nickname),
- where: fragment("not (?->'deactivated' @> 'true')", u.info),
+ where: u.deactivated != ^true,
where: u.id not in ^has_read_notifications,
group_by: u.id,
having:
@@ -1500,16 +1538,16 @@ defmodule Pleroma.User do
## Examples
- iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => false}}}, "digest", true)
- Pleroma.User{info: %{email_notifications: %{"digest" => true}}}
+ iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
+ Pleroma.User{email_notifications: %{"digest" => true}}
- iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => true}}}, "digest", false)
- Pleroma.User{info: %{email_notifications: %{"digest" => false}}}
+ iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
+ Pleroma.User{email_notifications: %{"digest" => false}}
"""
@spec switch_email_notifications(t(), String.t(), boolean()) ::
{:ok, t()} | {:error, Ecto.Changeset.t()}
def switch_email_notifications(user, type, status) do
- update_info(user, &User.Info.update_email_notifications(&1, %{type => status}))
+ User.update_email_notifications(user, %{type => status})
end
@doc """
@@ -1529,17 +1567,16 @@ defmodule Pleroma.User do
@spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
def toggle_confirmation(%User{} = user) do
- need_confirmation? = !user.info.confirmation_pending
-
user
- |> update_info(&User.Info.confirmation_changeset(&1, need_confirmation: need_confirmation?))
+ |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
+ |> update_and_set_cache()
end
- def get_mascot(%{info: %{mascot: %{} = mascot}}) when not is_nil(mascot) do
+ def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
mascot
end
- def get_mascot(%{info: %{mascot: mascot}}) when is_nil(mascot) do
+ def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
# use instance-default
config = Pleroma.Config.get([:assets, :mascots])
default_mascot = Pleroma.Config.get([:assets, :default_mascot])
@@ -1609,25 +1646,285 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
- @doc """
- Changes `user.info` and returns the user changeset.
+ # Internal function; public one is `deactivate/2`
+ defp set_activation_status(user, deactivated) do
+ user
+ |> cast(%{deactivated: deactivated}, [:deactivated])
+ |> update_and_set_cache()
+ end
- `fun` is called with the `user.info`.
- """
- def change_info(user, fun) do
- changeset = change(user)
- info = get_field(changeset, :info) || %User.Info{}
- put_embed(changeset, :info, fun.(info))
+ def update_banner(user, banner) do
+ user
+ |> cast(%{banner: banner}, [:banner])
+ |> update_and_set_cache()
end
- @doc """
- Updates `user.info` and sets cache.
+ def update_background(user, background) do
+ user
+ |> cast(%{background: background}, [:background])
+ |> update_and_set_cache()
+ end
+
+ def update_source_data(user, source_data) do
+ user
+ |> cast(%{source_data: source_data}, [:source_data])
+ |> update_and_set_cache()
+ end
+
+ def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
+ %{
+ admin: is_admin,
+ moderator: is_moderator
+ }
+ end
+
+ # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
+ # For example: [{"name": "Pronoun", "value": "she/her"}, …]
+ def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
+ limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
+
+ attachment
+ |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
+ |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
+ |> Enum.take(limit)
+ end
+
+ def fields(%{fields: nil}), do: []
+
+ def fields(%{fields: fields}), do: fields
+
+ def validate_fields(changeset, remote? \\ false) do
+ limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
+ limit = Pleroma.Config.get([:instance, limit_name], 0)
+
+ changeset
+ |> validate_length(:fields, max: limit)
+ |> validate_change(:fields, fn :fields, fields ->
+ if Enum.all?(fields, &valid_field?/1) do
+ []
+ else
+ [fields: "invalid"]
+ end
+ end)
+ end
+
+ defp valid_field?(%{"name" => name, "value" => value}) do
+ name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
+ value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
+
+ is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
+ String.length(value) <= value_limit
+ end
+
+ defp valid_field?(_), do: false
+
+ defp truncate_field(%{"name" => name, "value" => value}) do
+ {name, _chopped} =
+ String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
+
+ {value, _chopped} =
+ String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
+
+ %{"name" => name, "value" => value}
+ end
+
+ def admin_api_update(user, params) do
+ user
+ |> cast(params, [
+ :is_moderator,
+ :is_admin,
+ :show_role
+ ])
+ |> update_and_set_cache()
+ end
+
+ def mascot_update(user, url) do
+ user
+ |> cast(%{mascot: url}, [:mascot])
+ |> validate_required([:mascot])
+ |> update_and_set_cache()
+ end
+
+ def mastodon_settings_update(user, settings) do
+ user
+ |> cast(%{settings: settings}, [:settings])
+ |> validate_required([:settings])
+ |> update_and_set_cache()
+ end
+
+ @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
+ def confirmation_changeset(user, need_confirmation: need_confirmation?) do
+ params =
+ if need_confirmation? do
+ %{
+ confirmation_pending: true,
+ confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
+ }
+ else
+ %{
+ confirmation_pending: false,
+ confirmation_token: nil
+ }
+ end
+
+ cast(user, params, [:confirmation_pending, :confirmation_token])
+ end
+
+ def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
+ if id not in user.pinned_activities do
+ max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
+ params = %{pinned_activities: user.pinned_activities ++ [id]}
+
+ user
+ |> cast(params, [:pinned_activities])
+ |> validate_length(:pinned_activities,
+ max: max_pinned_statuses,
+ message: "You have already pinned the maximum number of statuses"
+ )
+ else
+ change(user)
+ end
+ |> update_and_set_cache()
+ end
+
+ def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
+ params = %{pinned_activities: List.delete(user.pinned_activities, id)}
+
+ user
+ |> cast(params, [:pinned_activities])
+ |> update_and_set_cache()
+ end
+
+ def update_email_notifications(user, settings) do
+ email_notifications =
+ user.email_notifications
+ |> Map.merge(settings)
+ |> Map.take(["digest"])
+
+ params = %{email_notifications: email_notifications}
+ fields = [:email_notifications]
+
+ user
+ |> cast(params, fields)
+ |> validate_required(fields)
+ |> update_and_set_cache()
+ end
+
+ defp set_subscribers(user, subscribers) do
+ params = %{subscribers: subscribers}
+
+ user
+ |> cast(params, [:subscribers])
+ |> validate_required([:subscribers])
+ |> update_and_set_cache()
+ end
+
+ def add_to_subscribers(user, subscribed) do
+ set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
+ end
+
+ def remove_from_subscribers(user, subscribed) do
+ set_subscribers(user, List.delete(user.subscribers, subscribed))
+ end
+
+ defp set_domain_blocks(user, domain_blocks) do
+ params = %{domain_blocks: domain_blocks}
+
+ user
+ |> cast(params, [:domain_blocks])
+ |> validate_required([:domain_blocks])
+ |> update_and_set_cache()
+ end
+
+ def block_domain(user, domain_blocked) do
+ set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
+ end
+
+ def unblock_domain(user, domain_blocked) do
+ set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
+ end
+
+ defp set_blocks(user, blocks) do
+ params = %{blocks: blocks}
+
+ user
+ |> cast(params, [:blocks])
+ |> validate_required([:blocks])
+ |> update_and_set_cache()
+ end
+
+ def add_to_block(user, blocked) do
+ set_blocks(user, Enum.uniq([blocked | user.blocks]))
+ end
+
+ def remove_from_block(user, blocked) do
+ set_blocks(user, List.delete(user.blocks, blocked))
+ end
+
+ defp set_mutes(user, mutes) do
+ params = %{mutes: mutes}
+
+ user
+ |> cast(params, [:mutes])
+ |> validate_required([:mutes])
+ |> update_and_set_cache()
+ end
+
+ def add_to_mutes(user, muted, notifications?) do
+ with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
+ set_notification_mutes(
+ user,
+ Enum.uniq([muted | user.muted_notifications]),
+ notifications?
+ )
+ end
+ end
+
+ def remove_from_mutes(user, muted) do
+ with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
+ set_notification_mutes(
+ user,
+ List.delete(user.muted_notifications, muted),
+ true
+ )
+ end
+ end
+
+ defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
+ {:ok, user}
+ end
+
+ defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
+ params = %{muted_notifications: muted_notifications}
+
+ user
+ |> cast(params, [:muted_notifications])
+ |> validate_required([:muted_notifications])
+ |> update_and_set_cache()
+ end
+
+ def add_reblog_mute(user, ap_id) do
+ params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
+
+ user
+ |> cast(params, [:muted_reblogs])
+ |> update_and_set_cache()
+ end
+
+ def remove_reblog_mute(user, ap_id) do
+ params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
+
+ user
+ |> cast(params, [:muted_reblogs])
+ |> update_and_set_cache()
+ end
+
+ def set_invisible(user, invisible) do
+ params = %{invisible: invisible}
- `fun` is called with the `user.info`.
- """
- def update_info(user, fun) do
user
- |> change_info(fun)
+ |> cast(params, [:invisible])
+ |> validate_required([:invisible])
|> update_and_set_cache()
end
end
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
deleted file mode 100644
index 4b5b43d7f..000000000
--- a/lib/pleroma/user/info.ex
+++ /dev/null
@@ -1,478 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.User.Info do
- use Ecto.Schema
- import Ecto.Changeset
-
- alias Pleroma.User.Info
-
- @type t :: %__MODULE__{}
-
- embedded_schema do
- field(:banner, :map, default: %{})
- field(:background, :map, default: %{})
- field(:source_data, :map, default: %{})
- field(:note_count, :integer, default: 0)
- field(:follower_count, :integer, default: 0)
- # Should be filled in only for remote users
- field(:following_count, :integer, default: nil)
- field(:locked, :boolean, default: false)
- field(:confirmation_pending, :boolean, default: false)
- field(:password_reset_pending, :boolean, default: false)
- field(:confirmation_token, :string, default: nil)
- field(:default_scope, :string, default: "public")
- field(:blocks, {:array, :string}, default: [])
- field(:domain_blocks, {:array, :string}, default: [])
- field(:mutes, {:array, :string}, default: [])
- field(:muted_reblogs, {:array, :string}, default: [])
- field(:muted_notifications, {:array, :string}, default: [])
- field(:subscribers, {:array, :string}, default: [])
- field(:deactivated, :boolean, default: false)
- field(:no_rich_text, :boolean, default: false)
- field(:ap_enabled, :boolean, default: false)
- field(:is_moderator, :boolean, default: false)
- field(:is_admin, :boolean, default: false)
- field(:show_role, :boolean, default: true)
- field(:keys, :string, default: nil)
- field(:settings, :map, default: nil)
- field(:magic_key, :string, default: nil)
- field(:uri, :string, default: nil)
- field(:topic, :string, default: nil)
- field(:hub, :string, default: nil)
- field(:salmon, :string, default: nil)
- field(:hide_followers_count, :boolean, default: false)
- field(:hide_follows_count, :boolean, default: false)
- field(:hide_followers, :boolean, default: false)
- field(:hide_follows, :boolean, default: false)
- field(:hide_favorites, :boolean, default: true)
- field(:unread_conversation_count, :integer, default: 0)
- field(:pinned_activities, {:array, :string}, default: [])
- field(:email_notifications, :map, default: %{"digest" => false})
- field(:mascot, :map, default: nil)
- field(:emoji, {:array, :map}, default: [])
- field(:pleroma_settings_store, :map, default: %{})
- field(:fields, {:array, :map}, default: nil)
- field(:raw_fields, {:array, :map}, default: [])
- field(:discoverable, :boolean, default: false)
-
- field(:notification_settings, :map,
- default: %{
- "followers" => true,
- "follows" => true,
- "non_follows" => true,
- "non_followers" => true
- }
- )
-
- field(:skip_thread_containment, :boolean, default: false)
-
- # Found in the wild
- # ap_id -> Where is this used?
- # bio -> Where is this used?
- # avatar -> Where is this used?
- # fqn -> Where is this used?
- # host -> Where is this used?
- # subject _> Where is this used?
- end
-
- def set_activation_status(info, deactivated) do
- params = %{deactivated: deactivated}
-
- info
- |> cast(params, [:deactivated])
- |> validate_required([:deactivated])
- end
-
- def set_password_reset_pending(info, pending) do
- params = %{password_reset_pending: pending}
-
- info
- |> cast(params, [:password_reset_pending])
- |> validate_required([:password_reset_pending])
- end
-
- def update_notification_settings(info, settings) do
- settings =
- settings
- |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
- |> Map.new()
-
- notification_settings =
- info.notification_settings
- |> Map.merge(settings)
- |> Map.take(["followers", "follows", "non_follows", "non_followers"])
-
- params = %{notification_settings: notification_settings}
-
- info
- |> cast(params, [:notification_settings])
- |> validate_required([:notification_settings])
- end
-
- @doc """
- Update email notifications in the given User.Info struct.
-
- Examples:
-
- iex> update_email_notifications(%Pleroma.User.Info{email_notifications: %{"digest" => false}}, %{"digest" => true})
- %Pleroma.User.Info{email_notifications: %{"digest" => true}}
-
- """
- @spec update_email_notifications(t(), map()) :: Ecto.Changeset.t()
- def update_email_notifications(info, settings) do
- email_notifications =
- info.email_notifications
- |> Map.merge(settings)
- |> Map.take(["digest"])
-
- params = %{email_notifications: email_notifications}
- fields = [:email_notifications]
-
- info
- |> cast(params, fields)
- |> validate_required(fields)
- end
-
- def add_to_note_count(info, number) do
- set_note_count(info, info.note_count + number)
- end
-
- def set_note_count(info, number) do
- params = %{note_count: Enum.max([0, number])}
-
- info
- |> cast(params, [:note_count])
- |> validate_required([:note_count])
- end
-
- def set_follower_count(info, number) do
- params = %{follower_count: Enum.max([0, number])}
-
- info
- |> cast(params, [:follower_count])
- |> validate_required([:follower_count])
- end
-
- def set_mutes(info, mutes) do
- params = %{mutes: mutes}
-
- info
- |> cast(params, [:mutes])
- |> validate_required([:mutes])
- end
-
- @spec set_notification_mutes(Changeset.t(), [String.t()], boolean()) :: Changeset.t()
- def set_notification_mutes(changeset, muted_notifications, notifications?) do
- if notifications? do
- put_change(changeset, :muted_notifications, muted_notifications)
- |> validate_required([:muted_notifications])
- else
- changeset
- end
- end
-
- def set_blocks(info, blocks) do
- params = %{blocks: blocks}
-
- info
- |> cast(params, [:blocks])
- |> validate_required([:blocks])
- end
-
- def set_subscribers(info, subscribers) do
- params = %{subscribers: subscribers}
-
- info
- |> cast(params, [:subscribers])
- |> validate_required([:subscribers])
- end
-
- @spec add_to_mutes(Info.t(), String.t(), boolean()) :: Changeset.t()
- def add_to_mutes(info, muted, notifications?) do
- info
- |> set_mutes(Enum.uniq([muted | info.mutes]))
- |> set_notification_mutes(
- Enum.uniq([muted | info.muted_notifications]),
- notifications?
- )
- end
-
- @spec remove_from_mutes(Info.t(), String.t()) :: Changeset.t()
- def remove_from_mutes(info, muted) do
- info
- |> set_mutes(List.delete(info.mutes, muted))
- |> set_notification_mutes(List.delete(info.muted_notifications, muted), true)
- end
-
- def add_to_block(info, blocked) do
- set_blocks(info, Enum.uniq([blocked | info.blocks]))
- end
-
- def remove_from_block(info, blocked) do
- set_blocks(info, List.delete(info.blocks, blocked))
- end
-
- def add_to_subscribers(info, subscribed) do
- set_subscribers(info, Enum.uniq([subscribed | info.subscribers]))
- end
-
- def remove_from_subscribers(info, subscribed) do
- set_subscribers(info, List.delete(info.subscribers, subscribed))
- end
-
- def set_domain_blocks(info, domain_blocks) do
- params = %{domain_blocks: domain_blocks}
-
- info
- |> cast(params, [:domain_blocks])
- |> validate_required([:domain_blocks])
- end
-
- def add_to_domain_block(info, domain_blocked) do
- set_domain_blocks(info, Enum.uniq([domain_blocked | info.domain_blocks]))
- end
-
- def remove_from_domain_block(info, domain_blocked) do
- set_domain_blocks(info, List.delete(info.domain_blocks, domain_blocked))
- end
-
- def set_keys(info, keys) do
- params = %{keys: keys}
-
- info
- |> cast(params, [:keys])
- |> validate_required([:keys])
- end
-
- def remote_user_creation(info, params) do
- params =
- if Map.has_key?(params, :fields) do
- Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
- else
- params
- end
-
- info
- |> cast(params, [
- :ap_enabled,
- :source_data,
- :banner,
- :locked,
- :magic_key,
- :uri,
- :hub,
- :topic,
- :salmon,
- :hide_followers,
- :hide_follows,
- :hide_followers_count,
- :hide_follows_count,
- :follower_count,
- :fields,
- :following_count,
- :discoverable
- ])
- |> validate_fields(true)
- end
-
- def user_upgrade(info, params, remote? \\ false) do
- info
- |> cast(params, [
- :ap_enabled,
- :source_data,
- :banner,
- :locked,
- :magic_key,
- :follower_count,
- :following_count,
- :hide_follows,
- :fields,
- :hide_followers,
- :discoverable,
- :hide_followers_count,
- :hide_follows_count
- ])
- |> validate_fields(remote?)
- end
-
- def profile_update(info, params) do
- info
- |> cast(params, [
- :locked,
- :no_rich_text,
- :default_scope,
- :banner,
- :hide_follows,
- :hide_followers,
- :hide_followers_count,
- :hide_follows_count,
- :hide_favorites,
- :background,
- :show_role,
- :skip_thread_containment,
- :fields,
- :raw_fields,
- :pleroma_settings_store,
- :discoverable
- ])
- |> validate_fields()
- end
-
- def validate_fields(changeset, remote? \\ false) do
- limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
- limit = Pleroma.Config.get([:instance, limit_name], 0)
-
- changeset
- |> validate_length(:fields, max: limit)
- |> validate_change(:fields, fn :fields, fields ->
- if Enum.all?(fields, &valid_field?/1) do
- []
- else
- [fields: "invalid"]
- end
- end)
- end
-
- defp valid_field?(%{"name" => name, "value" => value}) do
- name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
- value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
-
- is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
- String.length(value) <= value_limit
- end
-
- defp valid_field?(_), do: false
-
- defp truncate_field(%{"name" => name, "value" => value}) do
- {name, _chopped} =
- String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
-
- {value, _chopped} =
- String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
-
- %{"name" => name, "value" => value}
- end
-
- @spec confirmation_changeset(Info.t(), keyword()) :: Changeset.t()
- def confirmation_changeset(info, opts) do
- need_confirmation? = Keyword.get(opts, :need_confirmation)
-
- params =
- if need_confirmation? do
- %{
- confirmation_pending: true,
- confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
- }
- else
- %{
- confirmation_pending: false,
- confirmation_token: nil
- }
- end
-
- cast(info, params, [:confirmation_pending, :confirmation_token])
- end
-
- def mastodon_settings_update(info, settings) do
- params = %{settings: settings}
-
- info
- |> cast(params, [:settings])
- |> validate_required([:settings])
- end
-
- def mascot_update(info, url) do
- params = %{mascot: url}
-
- info
- |> cast(params, [:mascot])
- |> validate_required([:mascot])
- end
-
- def set_source_data(info, source_data) do
- params = %{source_data: source_data}
-
- info
- |> cast(params, [:source_data])
- |> validate_required([:source_data])
- end
-
- def admin_api_update(info, params) do
- info
- |> cast(params, [
- :is_moderator,
- :is_admin,
- :show_role
- ])
- end
-
- def add_pinnned_activity(info, %Pleroma.Activity{id: id}) do
- if id not in info.pinned_activities do
- max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
- params = %{pinned_activities: info.pinned_activities ++ [id]}
-
- info
- |> cast(params, [:pinned_activities])
- |> validate_length(:pinned_activities,
- max: max_pinned_statuses,
- message: "You have already pinned the maximum number of statuses"
- )
- else
- change(info)
- end
- end
-
- def remove_pinnned_activity(info, %Pleroma.Activity{id: id}) do
- params = %{pinned_activities: List.delete(info.pinned_activities, id)}
-
- cast(info, params, [:pinned_activities])
- end
-
- def roles(%Info{is_moderator: is_moderator, is_admin: is_admin}) do
- %{
- admin: is_admin,
- moderator: is_moderator
- }
- end
-
- def add_reblog_mute(info, ap_id) do
- params = %{muted_reblogs: info.muted_reblogs ++ [ap_id]}
-
- cast(info, params, [:muted_reblogs])
- end
-
- def remove_reblog_mute(info, ap_id) do
- params = %{muted_reblogs: List.delete(info.muted_reblogs, ap_id)}
-
- cast(info, params, [:muted_reblogs])
- end
-
- # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
- # For example: [{"name": "Pronoun", "value": "she/her"}, …]
- def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
- limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
-
- attachment
- |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
- |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
- |> Enum.take(limit)
- end
-
- def fields(%{fields: nil}), do: []
-
- def fields(%{fields: fields}), do: fields
-
- def follow_information_update(info, params) do
- info
- |> cast(params, [
- :hide_followers,
- :hide_follows,
- :follower_count,
- :following_count,
- :hide_followers_count,
- :hide_follows_count
- ])
- end
-end
diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex
index 2baf016cf..364bc1c89 100644
--- a/lib/pleroma/user/query.ex
+++ b/lib/pleroma/user/query.ex
@@ -28,6 +28,8 @@ defmodule Pleroma.User.Query do
"""
import Ecto.Query
import Pleroma.Web.AdminAPI.Search, only: [not_empty_string: 1]
+
+ alias Pleroma.FollowingRelationship
alias Pleroma.User
@type criteria ::
@@ -56,7 +58,6 @@ defmodule Pleroma.User.Query do
@ilike_criteria [:nickname, :name, :query]
@equal_criteria [:email]
- @role_criteria [:is_admin, :is_moderator]
@contains_criteria [:ap_id, :nickname]
@spec build(criteria()) :: Query.t()
@@ -100,15 +101,19 @@ defmodule Pleroma.User.Query do
Enum.reduce(tags, query, &prepare_tag_criteria/2)
end
- defp compose_query({key, _}, query) when key in @role_criteria do
- where(query, [u], fragment("(?->? @> 'true')", u.info, ^to_string(key)))
+ defp compose_query({:is_admin, _}, query) do
+ where(query, [u], u.is_admin)
+ end
+
+ defp compose_query({:is_moderator, _}, query) do
+ where(query, [u], u.is_moderator)
end
defp compose_query({:super_users, _}, query) do
where(
query,
[u],
- fragment("?->'is_admin' @> 'true' OR ?->'is_moderator' @> 'true'", u.info, u.info)
+ u.is_admin or u.is_moderator
)
end
@@ -117,7 +122,13 @@ defmodule Pleroma.User.Query do
defp compose_query({:external, _}, query), do: location_query(query, false)
defp compose_query({:active, _}, query) do
- where(query, [u], fragment("not (?->'deactivated' @> 'true')", u.info))
+ User.restrict_deactivated(query)
+ |> where([u], not is_nil(u.nickname))
+ end
+
+ defp compose_query({:legacy_active, _}, query) do
+ query
+ |> where([u], fragment("not (?->'deactivated' @> 'true')", u.info))
|> where([u], not is_nil(u.nickname))
end
@@ -126,22 +137,45 @@ defmodule Pleroma.User.Query do
end
defp compose_query({:deactivated, true}, query) do
- where(query, [u], fragment("?->'deactivated' @> 'true'", u.info))
+ where(query, [u], u.deactivated == ^true)
|> where([u], not is_nil(u.nickname))
end
- defp compose_query({:followers, %User{id: id, follower_address: follower_address}}, query) do
- where(query, [u], fragment("? <@ ?", ^[follower_address], u.following))
+ defp compose_query({:followers, %User{id: id}}, query) do
+ query
|> where([u], u.id != ^id)
+ |> join(:inner, [u], r in FollowingRelationship,
+ as: :relationships,
+ on: r.following_id == ^id and r.follower_id == u.id
+ )
+ |> where([relationships: r], r.state == "accept")
end
- defp compose_query({:friends, %User{id: id, following: following}}, query) do
- where(query, [u], u.follower_address in ^following)
+ defp compose_query({:friends, %User{id: id}}, query) do
+ query
|> where([u], u.id != ^id)
+ |> join(:inner, [u], r in FollowingRelationship,
+ as: :relationships,
+ on: r.following_id == u.id and r.follower_id == ^id
+ )
+ |> where([relationships: r], r.state == "accept")
end
defp compose_query({:recipients_from_activity, to}, query) do
- where(query, [u], u.ap_id in ^to or fragment("? && ?", u.following, ^to))
+ query
+ |> join(:left, [u], r in FollowingRelationship,
+ as: :relationships,
+ on: r.follower_id == u.id
+ )
+ |> join(:left, [relationships: r], f in User,
+ as: :following,
+ on: f.id == r.following_id
+ )
+ |> where(
+ [u, following: f, relationships: r],
+ u.ap_id in ^to or (f.follower_address in ^to and r.state == "accept")
+ )
+ |> distinct(true)
end
defp compose_query({:order_by, key}, query) do
diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex
index 6fb2c2352..09664db76 100644
--- a/lib/pleroma/user/search.ex
+++ b/lib/pleroma/user/search.ex
@@ -4,11 +4,9 @@
defmodule Pleroma.User.Search do
alias Pleroma.Pagination
- alias Pleroma.Repo
alias Pleroma.User
import Ecto.Query
- @similarity_threshold 0.25
@limit 20
def search(query_string, opts \\ []) do
@@ -23,18 +21,10 @@ defmodule Pleroma.User.Search do
maybe_resolve(resolve, for_user, query_string)
- {:ok, results} =
- Repo.transaction(fn ->
- Ecto.Adapters.SQL.query(
- Repo,
- "select set_limit(#{@similarity_threshold})",
- []
- )
-
- query_string
- |> search_query(for_user, following)
- |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset)
- end)
+ results =
+ query_string
+ |> search_query(for_user, following)
+ |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset)
results
end
@@ -56,26 +46,66 @@ defmodule Pleroma.User.Search do
|> base_query(following)
|> filter_blocked_user(for_user)
|> filter_blocked_domains(for_user)
- |> search_subqueries(query_string)
- |> union_subqueries
- |> distinct_query()
- |> boost_search_rank_query(for_user)
+ |> fts_search(query_string)
+ |> trigram_rank(query_string)
+ |> boost_search_rank(for_user)
|> subquery()
|> order_by(desc: :search_rank)
|> maybe_restrict_local(for_user)
end
+ defp fts_search(query, query_string) do
+ query_string = to_tsquery(query_string)
+
+ from(
+ u in query,
+ where:
+ fragment(
+ """
+ (to_tsvector('simple', ?) || to_tsvector('simple', ?)) @@ to_tsquery('simple', ?)
+ """,
+ u.name,
+ u.nickname,
+ ^query_string
+ )
+ )
+ end
+
+ defp to_tsquery(query_string) do
+ String.trim_trailing(query_string, "@" <> local_domain())
+ |> String.replace(~r/[!-\/|@|[-`|{-~|:-?]+/, " ")
+ |> String.trim()
+ |> String.split()
+ |> Enum.map(&(&1 <> ":*"))
+ |> Enum.join(" | ")
+ end
+
+ defp trigram_rank(query, query_string) do
+ from(
+ u in query,
+ select_merge: %{
+ search_rank:
+ fragment(
+ "similarity(?, trim(? || ' ' || coalesce(?, '')))",
+ ^query_string,
+ u.nickname,
+ u.name
+ )
+ }
+ )
+ end
+
defp base_query(_user, false), do: User
defp base_query(user, true), do: User.get_followers_query(user)
- defp filter_blocked_user(query, %User{info: %{blocks: blocks}})
+ defp filter_blocked_user(query, %User{blocks: blocks})
when length(blocks) > 0 do
from(q in query, where: not (q.ap_id in ^blocks))
end
defp filter_blocked_user(query, _), do: query
- defp filter_blocked_domains(query, %User{info: %{domain_blocks: domain_blocks}})
+ defp filter_blocked_domains(query, %User{domain_blocks: domain_blocks})
when length(domain_blocks) > 0 do
domains = Enum.join(domain_blocks, ",")
@@ -87,21 +117,6 @@ defmodule Pleroma.User.Search do
defp filter_blocked_domains(query, _), do: query
- defp union_subqueries({fts_subquery, trigram_subquery}) do
- from(s in trigram_subquery, union_all: ^fts_subquery)
- end
-
- defp search_subqueries(base_query, query_string) do
- {
- fts_search_subquery(base_query, query_string),
- trigram_search_subquery(base_query, query_string)
- }
- end
-
- defp distinct_query(q) do
- from(s in subquery(q), order_by: s.search_type, distinct: s.id)
- end
-
defp maybe_resolve(true, user, query) do
case {limit(), user} do
{:all, _} -> :noop
@@ -126,9 +141,9 @@ defmodule Pleroma.User.Search do
defp restrict_local(q), do: where(q, [u], u.local == true)
- defp boost_search_rank_query(query, nil), do: query
+ defp local_domain, do: Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host])
- defp boost_search_rank_query(query, for_user) do
+ defp boost_search_rank(query, %User{} = for_user) do
friends_ids = User.get_friends_ids(for_user)
followers_ids = User.get_followers_ids(for_user)
@@ -137,8 +152,8 @@ defmodule Pleroma.User.Search do
search_rank:
fragment(
"""
- CASE WHEN (?) THEN 0.5 + (?) * 1.3
- WHEN (?) THEN 0.5 + (?) * 1.2
+ CASE WHEN (?) THEN (?) * 1.5
+ WHEN (?) THEN (?) * 1.3
WHEN (?) THEN (?) * 1.1
ELSE (?) END
""",
@@ -154,70 +169,5 @@ defmodule Pleroma.User.Search do
)
end
- @spec fts_search_subquery(User.t() | Ecto.Query.t(), String.t()) :: Ecto.Query.t()
- defp fts_search_subquery(query, term) do
- processed_query =
- String.trim_trailing(term, "@" <> local_domain())
- |> String.replace(~r/[!-\/|@|[-`|{-~|:-?]+/, " ")
- |> String.trim()
- |> String.split()
- |> Enum.map(&(&1 <> ":*"))
- |> Enum.join(" | ")
-
- from(
- u in query,
- select_merge: %{
- search_type: ^0,
- search_rank:
- fragment(
- """
- ts_rank_cd(
- setweight(to_tsvector('simple', regexp_replace(?, '\\W', ' ', 'g')), 'A') ||
- setweight(to_tsvector('simple', regexp_replace(coalesce(?, ''), '\\W', ' ', 'g')), 'B'),
- to_tsquery('simple', ?),
- 32
- )
- """,
- u.nickname,
- u.name,
- ^processed_query
- )
- },
- where:
- fragment(
- """
- (setweight(to_tsvector('simple', regexp_replace(?, '\\W', ' ', 'g')), 'A') ||
- setweight(to_tsvector('simple', regexp_replace(coalesce(?, ''), '\\W', ' ', 'g')), 'B')) @@ to_tsquery('simple', ?)
- """,
- u.nickname,
- u.name,
- ^processed_query
- )
- )
- |> User.restrict_deactivated()
- end
-
- @spec trigram_search_subquery(User.t() | Ecto.Query.t(), String.t()) :: Ecto.Query.t()
- defp trigram_search_subquery(query, term) do
- term = String.trim_trailing(term, "@" <> local_domain())
-
- from(
- u in query,
- select_merge: %{
- # ^1 gives 'Postgrex expected a binary, got 1' for some weird reason
- search_type: fragment("?", 1),
- search_rank:
- fragment(
- "similarity(?, trim(? || ' ' || coalesce(?, '')))",
- ^term,
- u.nickname,
- u.name
- )
- },
- where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term)
- )
- |> User.restrict_deactivated()
- end
-
- defp local_domain, do: Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host])
+ defp boost_search_rank(query, _for_user), do: query
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 9f29087df..51a9c6169 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Activity.Ir.Topics
alias Pleroma.Config
alias Pleroma.Conversation
+ alias Pleroma.Conversation.Participation
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Object.Containment
@@ -68,7 +69,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp check_actor_is_active(actor) do
if not is_nil(actor) do
with user <- User.get_cached_by_ap_id(actor),
- false <- user.info.deactivated do
+ false <- user.deactivated do
true
else
_e -> false
@@ -131,7 +132,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:ok, map} <- MRF.filter(map),
{recipients, _, _} = get_recipients(map),
{:fake, false, map, recipients} <- {:fake, fake, map, recipients},
- :ok <- Containment.contain_child(map),
+ {:containment, :ok} <- {:containment, Containment.contain_child(map)},
{:ok, map, object} <- insert_full_object(map) do
{:ok, activity} =
Repo.insert(%Activity{
@@ -153,11 +154,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Notification.create_notifications(activity)
- participations =
- activity
- |> Conversation.create_or_bump_for()
- |> get_participations()
-
+ conversation = create_or_bump_conversation(activity, map["actor"])
+ participations = get_participations(conversation)
stream_out(activity)
stream_out_participations(participations)
{:ok, activity}
@@ -182,7 +180,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
- defp get_participations({:ok, %{participations: participations}}), do: participations
+ defp create_or_bump_conversation(activity, actor) do
+ with {:ok, conversation} <- Conversation.create_or_bump_for(activity),
+ %User{} = user <- User.get_cached_by_ap_id(actor),
+ Participation.mark_as_read(user, conversation) do
+ {:ok, conversation}
+ end
+ end
+
+ defp get_participations({:ok, conversation}) do
+ conversation
+ |> Repo.preload(:participations, force: true)
+ |> Map.get(:participations)
+ end
+
defp get_participations(_), do: []
def stream_out_participations(participations) do
@@ -225,6 +236,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
# only accept false as false value
local = !(params[:local] == false)
published = params[:published]
+ quick_insert? = Pleroma.Config.get([:env]) == :benchmark
with create_data <-
make_create_data(
@@ -235,12 +247,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:fake, false, activity} <- {:fake, fake, activity},
_ <- increase_replies_count_if_reply(create_data),
_ <- increase_poll_votes_if_vote(create_data),
- # Changing note count prior to enqueuing federation task in order to avoid
- # race conditions on updating user.info
+ {:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
:ok <- maybe_federate(activity) do
{:ok, activity}
else
+ {:quick_insert, true, activity} ->
+ {:ok, activity}
+
{:fake, true, activity} ->
{:ok, activity}
@@ -269,22 +283,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
- def accept(%{to: to, actor: actor, object: object} = params) do
- # only accept false as false value
- local = !(params[:local] == false)
+ def accept(params) do
+ accept_or_reject("Accept", params)
+ end
- with data <- %{"to" => to, "type" => "Accept", "actor" => actor.ap_id, "object" => object},
- {:ok, activity} <- insert(data, local),
- :ok <- maybe_federate(activity) do
- {:ok, activity}
- end
+ def reject(params) do
+ accept_or_reject("Reject", params)
end
- def reject(%{to: to, actor: actor, object: object} = params) do
- # only accept false as false value
- local = !(params[:local] == false)
+ def accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do
+ local = Map.get(params, :local, true)
+ activity_id = Map.get(params, :activity_id, nil)
- with data <- %{"to" => to, "type" => "Reject", "actor" => actor.ap_id, "object" => object},
+ with data <-
+ %{"to" => to, "type" => type, "actor" => actor.ap_id, "object" => object}
+ |> Utils.maybe_put("id", activity_id),
{:ok, activity} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
@@ -409,23 +422,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
- def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
+ def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, options \\ []) do
+ local = Keyword.get(options, :local, true)
+ activity_id = Keyword.get(options, :activity_id, nil)
+ actor = Keyword.get(options, :actor, actor)
+
user = User.get_cached_by_ap_id(actor)
to = (object.data["to"] || []) ++ (object.data["cc"] || [])
with {:ok, object, activity} <- Object.delete(object),
- data <- %{
- "type" => "Delete",
- "actor" => actor,
- "object" => id,
- "to" => to,
- "deleted_activity_id" => activity && activity.id
- },
+ data <-
+ %{
+ "type" => "Delete",
+ "actor" => actor,
+ "object" => id,
+ "to" => to,
+ "deleted_activity_id" => activity && activity.id
+ }
+ |> maybe_put("id", activity_id),
{:ok, activity} <- insert(data, local, false),
stream_out_participations(object, user),
_ <- decrease_replies_count_if_reply(object),
- # Changing note count prior to enqueuing federation task in order to avoid
- # race conditions on updating user.info
{:ok, _actor} <- decrease_note_count_if_public(user, object),
:ok <- maybe_federate(activity) do
{:ok, activity}
@@ -486,7 +503,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
with flag_data <- make_flag_data(params, additional),
{:ok, activity} <- insert(flag_data, local),
- :ok <- maybe_federate(activity) do
+ {:ok, stripped_activity} <- strip_report_status_data(activity),
+ :ok <- maybe_federate(stripped_activity) do
Enum.each(User.all_superusers(), fn superuser ->
superuser
|> Pleroma.Emails.AdminEmail.report(actor, account, statuses, content)
@@ -501,7 +519,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
public = [Pleroma.Constants.as_public()]
recipients =
- if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
+ if opts["user"],
+ do: [opts["user"].ap_id | User.following(opts["user"])] ++ public,
+ else: public
from(activity in Activity)
|> maybe_preload_objects(opts)
@@ -591,12 +611,55 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_visibility(query, _visibility), do: query
+ defp exclude_visibility(query, %{"exclude_visibilities" => visibility})
+ when is_list(visibility) do
+ if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
+ from(
+ a in query,
+ where:
+ not fragment(
+ "activity_visibility(?, ?, ?) = ANY (?)",
+ a.actor,
+ a.recipients,
+ a.data,
+ ^visibility
+ )
+ )
+ else
+ Logger.error("Could not exclude visibility to #{visibility}")
+ query
+ end
+ end
+
+ defp exclude_visibility(query, %{"exclude_visibilities" => visibility})
+ when visibility in @valid_visibilities do
+ from(
+ a in query,
+ where:
+ not fragment(
+ "activity_visibility(?, ?, ?) = ?",
+ a.actor,
+ a.recipients,
+ a.data,
+ ^visibility
+ )
+ )
+ end
+
+ defp exclude_visibility(query, %{"exclude_visibilities" => visibility})
+ when visibility not in @valid_visibilities do
+ Logger.error("Could not exclude visibility to #{visibility}")
+ query
+ end
+
+ defp exclude_visibility(query, _visibility), do: query
+
defp restrict_thread_visibility(query, _, %{skip_thread_containment: true} = _),
do: query
defp restrict_thread_visibility(
query,
- %{"user" => %User{info: %{skip_thread_containment: true}}},
+ %{"user" => %User{skip_thread_containment: true}},
_
),
do: query
@@ -634,7 +697,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> Map.put("user", reading_user)
|> Map.put("actor_id", user.ap_id)
|> Map.put("whole_db", true)
- |> Map.put("pinned_activity_ids", user.info.pinned_activities)
+ |> Map.put("pinned_activity_ids", user.pinned_activities)
recipients =
user_activities_recipients(%{
@@ -652,7 +715,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp user_activities_recipients(%{"reading_user" => reading_user}) do
if reading_user do
- [Pleroma.Constants.as_public()] ++ [reading_user.ap_id | reading_user.following]
+ [Pleroma.Constants.as_public()] ++ [reading_user.ap_id | User.following(reading_user)]
else
[Pleroma.Constants.as_public()]
end
@@ -795,8 +858,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query
- defp restrict_muted(query, %{"muting_user" => %User{info: info}} = opts) do
- mutes = info.mutes
+ defp restrict_muted(query, %{"muting_user" => %User{} = user} = opts) do
+ mutes = user.mutes
query =
from([activity] in query,
@@ -813,9 +876,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_muted(query, _), do: query
- defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
- blocks = info.blocks || []
- domain_blocks = info.domain_blocks || []
+ defp restrict_blocked(query, %{"blocking_user" => %User{} = user}) do
+ blocks = user.blocks || []
+ domain_blocks = user.domain_blocks || []
query =
if has_named_binding?(query, :object), do: query, else: Activity.with_joined_object(query)
@@ -856,8 +919,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_pinned(query, _), do: query
- defp restrict_muted_reblogs(query, %{"muting_user" => %User{info: info}}) do
- muted_reblogs = info.muted_reblogs || []
+ defp restrict_muted_reblogs(query, %{"muting_user" => %User{} = user}) do
+ muted_reblogs = user.muted_reblogs || []
from(
activity in query,
@@ -955,6 +1018,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_muted_reblogs(opts)
|> Activity.restrict_deactivated_users()
|> exclude_poll_votes(opts)
+ |> exclude_visibility(opts)
end
def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do
@@ -1041,17 +1105,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
locked = data["manuallyApprovesFollowers"] || false
data = Transmogrifier.maybe_fix_user_object(data)
discoverable = data["discoverable"] || false
+ invisible = data["invisible"] || false
user_data = %{
ap_id: data["id"],
- info: %{
- ap_enabled: true,
- source_data: data,
- banner: banner,
- fields: fields,
- locked: locked,
- discoverable: discoverable
- },
+ ap_enabled: true,
+ source_data: data,
+ banner: banner,
+ fields: fields,
+ locked: locked,
+ discoverable: discoverable,
+ invisible: invisible,
avatar: avatar,
name: data["name"],
follower_address: data["followers"],
@@ -1103,7 +1167,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
with {:enabled, true} <-
{:enabled, Pleroma.Config.get([:instance, :external_user_synchronization])},
{:ok, info} <- fetch_follow_information_for_user(data) do
- info = Map.merge(data.info, info)
+ info = Map.merge(data[:info] || %{}, info)
Map.put(data, :info, info)
else
{:enabled, false} ->
@@ -1154,7 +1218,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
data <- maybe_update_follow_information(data) do
{:ok, data}
else
- e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
+ e ->
+ Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
+ {:error, e}
end
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 080030eb5..b2cd965fe 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -137,7 +137,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
with %User{} = user <- User.get_cached_by_nickname(nickname),
{user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
{:show_follows, true} <-
- {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
+ {:show_follows, (for_user && for_user == user) || !user.hide_follows} do
{page, _} = Integer.parse(page)
conn
@@ -174,7 +174,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
with %User{} = user <- User.get_cached_by_nickname(nickname),
{user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
{:show_followers, true} <-
- {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
+ {:show_followers, (for_user && for_user == user) || !user.hide_followers} do
{page, _} = Integer.parse(page)
conn
@@ -319,12 +319,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
when page? in [true, "true"] do
activities =
if params["max_id"] do
- ActivityPub.fetch_activities([user.ap_id | user.following], %{
+ ActivityPub.fetch_activities([user.ap_id | User.following(user)], %{
"max_id" => params["max_id"],
"limit" => 10
})
else
- ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10})
+ ActivityPub.fetch_activities([user.ap_id | User.following(user)], %{"limit" => 10})
end
conn
@@ -387,7 +387,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def handle_user_activity(user, %{"type" => "Delete"} = params) do
with %Object{} = object <- Object.normalize(params["object"]),
- true <- user.info.is_moderator || user.ap_id == object.data["actor"],
+ true <- user.is_moderator || user.ap_id == object.data["actor"],
{:ok, delete} <- ActivityPub.delete(object) do
{:ok, delete}
else
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
index b90193ca0..8abe18e29 100644
--- a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
@@ -11,7 +11,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
# has the user successfully posted before?
defp old_user?(%User{} = u) do
- u.info.note_count > 0 || u.info.follower_count > 0
+ u.note_count > 0 || u.follower_count > 0
end
# does the post contain links?
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index 3866dacee..4ea37fc7b 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -129,7 +129,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
[]
end
- Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers ++ fetchers
+ Pleroma.Web.Federator.Publisher.remote_users(actor, activity) ++ followers ++ fetchers
end
defp get_cc_ap_ids(ap_id, recipients) do
@@ -140,7 +140,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|> Enum.map(& &1.ap_id)
end
- defp maybe_use_sharedinbox(%User{info: %{source_data: data}}),
+ defp maybe_use_sharedinbox(%User{source_data: data}),
do: (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
@doc """
@@ -156,7 +156,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
"""
def determine_inbox(
%Activity{data: activity_data},
- %User{info: %{source_data: data}} = user
+ %User{source_data: data} = user
) do
to = activity_data["to"] || []
cc = activity_data["cc"] || []
@@ -190,12 +190,12 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
recipients
|> Enum.filter(&User.ap_enabled?/1)
- |> Enum.map(fn %{info: %{source_data: data}} -> data["inbox"] end)
+ |> Enum.map(fn %{source_data: data} -> data["inbox"] end)
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|> Instances.filter_reachable()
|> Enum.each(fn {inbox, unreachable_since} ->
%User{ap_id: ap_id} =
- Enum.find(recipients, fn %{info: %{source_data: data}} -> data["inbox"] == inbox end)
+ Enum.find(recipients, fn %{source_data: data} -> data["inbox"] == inbox end)
# Get all the recipients on the same host and add them to cc. Otherwise, a remote
# instance would only accept a first message for the first recipient and ignore the rest.
diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex
index c2ac38907..fc2619680 100644
--- a/lib/pleroma/web/activity_pub/relay.ex
+++ b/lib/pleroma/web/activity_pub/relay.ex
@@ -10,8 +10,16 @@ defmodule Pleroma.Web.ActivityPub.Relay do
require Logger
def get_actor do
+ actor =
+ relay_ap_id()
+ |> User.get_or_create_service_actor_by_ap_id()
+
+ {:ok, actor} = User.set_invisible(actor, true)
+ actor
+ end
+
+ def relay_ap_id do
"#{Pleroma.Web.Endpoint.url()}/relay"
- |> User.get_or_create_service_actor_by_ap_id()
end
@spec follow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
@@ -51,6 +59,21 @@ defmodule Pleroma.Web.ActivityPub.Relay do
def publish(_), do: {:error, "Not implemented"}
+ @spec list() :: {:ok, [String.t()]} | {:error, any()}
+ def list do
+ with %User{} = user <- get_actor() do
+ list =
+ user
+ |> User.following()
+ |> Enum.map(fn entry -> URI.parse(entry).host end)
+ |> Enum.uniq()
+
+ {:ok, list}
+ else
+ error -> format_error(error)
+ end
+ end
+
defp format_error({:error, error}), do: format_error(error)
defp format_error(error) do
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 872ed0eb2..91a164eff 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
A module to handle coding from internal to wire ActivityPub and back.
"""
alias Pleroma.Activity
+ alias Pleroma.FollowingRelationship
alias Pleroma.Object
alias Pleroma.Object.Containment
alias Pleroma.Repo
@@ -474,7 +475,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{_, false} <- {:user_locked, User.locked?(followed)},
{_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
{_, {:ok, _}} <-
- {:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")} do
+ {:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")},
+ {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
ActivityPub.accept(%{
to: [follower.ap_id],
actor: followed,
@@ -484,6 +486,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
else
{:user_blocked, true} ->
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
+ {:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
ActivityPub.reject(%{
to: [follower.ap_id],
@@ -494,6 +497,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{:follow, {:error, _}} ->
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
+ {:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
ActivityPub.reject(%{
to: [follower.ap_id],
@@ -503,6 +507,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
})
{:user_locked, true} ->
+ {:ok, _relationship} = FollowingRelationship.update(follower, followed, "pending")
:noop
end
@@ -514,7 +519,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => _id} = data,
+ %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => id} = data,
_options
) do
with actor <- Containment.get_actor(data),
@@ -522,13 +527,14 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
- {:ok, _follower} = User.follow(follower, followed) do
+ {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
ActivityPub.accept(%{
to: follow_activity.data["to"],
type: "Accept",
actor: followed,
object: follow_activity.data["id"],
- local: false
+ local: false,
+ activity_id: id
})
else
_e -> :error
@@ -536,7 +542,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => _id} = data,
+ %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => id} = data,
_options
) do
with actor <- Containment.get_actor(data),
@@ -544,16 +550,16 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
+ {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
{:ok, activity} <-
ActivityPub.reject(%{
to: follow_activity.data["to"],
type: "Reject",
actor: followed,
object: follow_activity.data["id"],
- local: false
+ local: false,
+ activity_id: id
}) do
- User.unfollow(follower, followed)
-
{:ok, activity}
else
_e -> :error
@@ -594,13 +600,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
data,
_options
)
- when object_type in ["Person", "Application", "Service", "Organization"] do
+ when object_type in [
+ "Person",
+ "Application",
+ "Service",
+ "Organization"
+ ] do
with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
- banner = new_user_data[:info][:banner]
- locked = new_user_data[:info][:locked] || false
- attachment = get_in(new_user_data, [:info, :source_data, "attachment"]) || []
+ locked = new_user_data[:locked] || false
+ attachment = get_in(new_user_data, [:source_data, "attachment"]) || []
+ invisible = new_user_data[:invisible] || false
fields =
attachment
@@ -609,8 +620,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
update_data =
new_user_data
- |> Map.take([:name, :bio, :avatar])
- |> Map.put(:info, %{banner: banner, locked: locked, fields: fields})
+ |> Map.take([:avatar, :banner, :bio, :name])
+ |> Map.put(:fields, fields)
+ |> Map.put(:locked, locked)
+ |> Map.put(:invisible, invisible)
actor
|> User.upgrade_changeset(update_data, true)
@@ -637,7 +650,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
# an error or a tombstone. This would allow us to verify that a deletion actually took
# place.
def handle_incoming(
- %{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = data,
+ %{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => id} = data,
_options
) do
object_id = Utils.get_ap_id(object_id)
@@ -646,7 +659,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
{:ok, object} <- get_obj_helper(object_id),
:ok <- Containment.contain_origin(actor.ap_id, object.data),
- {:ok, activity} <- ActivityPub.delete(object, false) do
+ {:ok, activity} <-
+ ActivityPub.delete(object, local: false, activity_id: id, actor: actor.ap_id) do
{:ok, activity}
else
nil ->
@@ -976,7 +990,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => "Mention", "href" => ap_id, "name" => "@#{nickname}"}
end
- def take_emoji_tags(%User{info: %{emoji: emoji} = _user_info} = _user) do
+ def take_emoji_tags(%User{emoji: emoji}) do
emoji
|> Enum.flat_map(&Map.to_list/1)
|> Enum.map(&build_emoji_tag/1)
@@ -1051,45 +1065,22 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
# we pass a fake user so that the followers collection is stripped away
old_follower_address = User.ap_followers(%User{nickname: user.nickname})
- q =
- from(
- u in User,
- where: ^old_follower_address in u.following,
- update: [
- set: [
- following:
- fragment(
- "array_replace(?,?,?)",
- u.following,
- ^old_follower_address,
- ^user.follower_address
- )
- ]
- ]
- )
-
- Repo.update_all(q, [])
-
- maybe_retire_websub(user.ap_id)
-
- q =
- from(
- a in Activity,
- where: ^old_follower_address in a.recipients,
- update: [
- set: [
- recipients:
- fragment(
- "array_replace(?,?,?)",
- a.recipients,
- ^old_follower_address,
- ^user.follower_address
- )
- ]
+ from(
+ a in Activity,
+ where: ^old_follower_address in a.recipients,
+ update: [
+ set: [
+ recipients:
+ fragment(
+ "array_replace(?,?,?)",
+ a.recipients,
+ ^old_follower_address,
+ ^user.follower_address
+ )
]
- )
-
- Repo.update_all(q, [])
+ ]
+ )
+ |> Repo.update_all([])
end
def upgrade_user_from_ap_id(ap_id) do
@@ -1114,19 +1105,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> User.update_and_set_cache()
end
- def maybe_retire_websub(ap_id) do
- # some sanity checks
- if is_binary(ap_id) && String.length(ap_id) > 8 do
- q =
- from(
- ws in Pleroma.Web.Websub.WebsubClientSubscription,
- where: fragment("? like ?", ws.topic, ^"#{ap_id}%")
- )
-
- Repo.delete_all(q)
- end
- end
-
def maybe_fix_user_url(%{"url" => url} = data) when is_map(url) do
Map.put(data, "url", url["href"])
end
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index f2beb0809..57349e304 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -14,6 +14,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Router.Helpers
@@ -23,6 +24,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
require Pleroma.Constants
@supported_object_types ["Article", "Note", "Video", "Page", "Question", "Answer", "Audio"]
+ @strip_status_report_states ~w(closed resolved)
@supported_report_states ~w(open closed resolved)
@valid_visibilities ~w(public unlisted private direct)
@@ -51,26 +53,28 @@ defmodule Pleroma.Web.ActivityPub.Utils do
def determine_explicit_mentions(_), do: []
- @spec recipient_in_collection(any(), any()) :: boolean()
- defp recipient_in_collection(ap_id, coll) when is_binary(coll), do: ap_id == coll
- defp recipient_in_collection(ap_id, coll) when is_list(coll), do: ap_id in coll
- defp recipient_in_collection(_, _), do: false
+ @spec label_in_collection?(any(), any()) :: boolean()
+ defp label_in_collection?(ap_id, coll) when is_binary(coll), do: ap_id == coll
+ defp label_in_collection?(ap_id, coll) when is_list(coll), do: ap_id in coll
+ defp label_in_collection?(_, _), do: false
+
+ @spec label_in_message?(String.t(), map()) :: boolean()
+ def label_in_message?(label, params),
+ do:
+ [params["to"], params["cc"], params["bto"], params["bcc"]]
+ |> Enum.any?(&label_in_collection?(label, &1))
+
+ @spec unaddressed_message?(map()) :: boolean()
+ def unaddressed_message?(params),
+ do:
+ [params["to"], params["cc"], params["bto"], params["bcc"]]
+ |> Enum.all?(&is_nil(&1))
@spec recipient_in_message(User.t(), User.t(), map()) :: boolean()
- def recipient_in_message(%User{ap_id: ap_id} = recipient, %User{} = actor, params) do
- addresses = [params["to"], params["cc"], params["bto"], params["bcc"]]
-
- cond do
- Enum.any?(addresses, &recipient_in_collection(ap_id, &1)) -> true
- # if the message is unaddressed at all, then assume it is directly addressed
- # to the recipient
- Enum.all?(addresses, &is_nil(&1)) -> true
- # if the message is sent from somebody the user is following, then assume it
- # is addressed to the recipient
- User.following?(recipient, actor) -> true
- true -> false
- end
- end
+ def recipient_in_message(%User{ap_id: ap_id} = recipient, %User{} = actor, params),
+ do:
+ label_in_message?(ap_id, params) || unaddressed_message?(params) ||
+ User.following?(recipient, actor)
defp extract_list(target) when is_binary(target), do: [target]
defp extract_list(lst) when is_list(lst), do: lst
@@ -78,8 +82,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
def maybe_splice_recipient(ap_id, params) do
need_splice? =
- !recipient_in_collection(ap_id, params["to"]) &&
- !recipient_in_collection(ap_id, params["cc"])
+ !label_in_collection?(ap_id, params["to"]) &&
+ !label_in_collection?(ap_id, params["cc"])
if need_splice? do
cc_list = extract_list(params["cc"])
@@ -493,10 +497,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do
%Activity{data: %{"actor" => actor}},
object
) do
- announcements = take_announcements(object)
+ unless actor |> User.get_cached_by_ap_id() |> User.invisible?() do
+ announcements = take_announcements(object)
- with announcements <- Enum.uniq([actor | announcements]) do
- update_element_in_object("announcement", announcements, object)
+ with announcements <- Enum.uniq([actor | announcements]) do
+ update_element_in_object("announcement", announcements, object)
+ end
+ else
+ {:ok, object}
end
end
@@ -610,10 +618,24 @@ defmodule Pleroma.Web.ActivityPub.Utils do
defp build_flag_object(%{account: account, statuses: statuses} = _) do
[account.ap_id] ++
- Enum.map(statuses || [], fn
- %Activity{} = act -> act.data["id"]
- act when is_map(act) -> act["id"]
- act when is_binary(act) -> act
+ Enum.map(statuses || [], fn act ->
+ id =
+ case act do
+ %Activity{} = act -> act.data["id"]
+ act when is_map(act) -> act["id"]
+ act when is_binary(act) -> act
+ end
+
+ activity = Activity.get_by_ap_id_with_object(id)
+ actor = User.get_by_ap_id(activity.object.data["actor"])
+
+ %{
+ "type" => "Note",
+ "id" => activity.data["id"],
+ "content" => activity.object.data["content"],
+ "published" => activity.object.data["published"],
+ "actor" => AccountView.render("show.json", %{user: actor})
+ }
end)
end
@@ -659,7 +681,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
end
#### Report-related helpers
-
def get_reports(params, page, page_size) do
params =
params
@@ -747,6 +768,20 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> Repo.all()
end
+ def update_report_state(%Activity{} = activity, state)
+ when state in @strip_status_report_states do
+ {:ok, stripped_activity} = strip_report_status_data(activity)
+
+ new_data =
+ activity.data
+ |> Map.put("state", state)
+ |> Map.put("object", stripped_activity.data["object"])
+
+ activity
+ |> Changeset.change(data: new_data)
+ |> Repo.update()
+ end
+
def update_report_state(%Activity{} = activity, state) when state in @supported_report_states do
new_data = Map.put(activity.data, "state", state)
@@ -769,6 +804,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do
def update_report_state(_, _), do: {:error, "Unsupported state"}
+ def strip_report_status_data(activity) do
+ [actor | reported_activities] = activity.data["object"]
+ stripped_activities = Enum.map(reported_activities, & &1["id"])
+ new_data = put_in(activity.data, ["object"], [actor | stripped_activities])
+
+ {:ok, %{activity | data: new_data}}
+ end
+
def update_activity_visibility(activity, visibility) when visibility in @valid_visibilities do
[to, cc, recipients] =
activity
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 9b39d1629..cf08045c9 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -55,7 +55,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"owner" => user.ap_id,
"publicKeyPem" => public_key
},
- "endpoints" => endpoints
+ "endpoints" => endpoints,
+ "invisible" => User.invisible?(user)
}
|> Map.merge(Utils.make_json_ld_header())
end
@@ -78,8 +79,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
emoji_tags = Transmogrifier.take_emoji_tags(user)
fields =
- user.info
- |> User.Info.fields()
+ user
+ |> User.fields()
|> Enum.map(fn %{"name" => name, "value" => value} ->
%{
"name" => Pleroma.HTML.strip_tags(name),
@@ -99,7 +100,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"name" => user.name,
"summary" => user.bio,
"url" => user.ap_id,
- "manuallyApprovesFollowers" => user.info.locked,
+ "manuallyApprovesFollowers" => user.locked,
"publicKey" => %{
"id" => "#{user.ap_id}#main-key",
"owner" => user.ap_id,
@@ -107,8 +108,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
},
"endpoints" => endpoints,
"attachment" => fields,
- "tag" => (user.info.source_data["tag"] || []) ++ emoji_tags,
- "discoverable" => user.info.discoverable
+ "tag" => (user.source_data["tag"] || []) ++ emoji_tags,
+ "discoverable" => user.discoverable
}
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
@@ -116,8 +117,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
end
def render("following.json", %{user: user, page: page} = opts) do
- showing_items = (opts[:for] && opts[:for] == user) || !user.info.hide_follows
- showing_count = showing_items || !user.info.hide_follows_count
+ showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows
+ showing_count = showing_items || !user.hide_follows_count
query = User.get_friends_query(user)
query = from(user in query, select: [:ap_id])
@@ -135,8 +136,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
end
def render("following.json", %{user: user} = opts) do
- showing_items = (opts[:for] && opts[:for] == user) || !user.info.hide_follows
- showing_count = showing_items || !user.info.hide_follows_count
+ showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows
+ showing_count = showing_items || !user.hide_follows_count
query = User.get_friends_query(user)
query = from(user in query, select: [:ap_id])
@@ -155,7 +156,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"totalItems" => total,
"first" =>
if showing_items do
- collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
+ collection(following, "#{user.ap_id}/following", 1, !user.hide_follows)
else
"#{user.ap_id}/following?page=1"
end
@@ -164,8 +165,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
end
def render("followers.json", %{user: user, page: page} = opts) do
- showing_items = (opts[:for] && opts[:for] == user) || !user.info.hide_followers
- showing_count = showing_items || !user.info.hide_followers_count
+ showing_items = (opts[:for] && opts[:for] == user) || !user.hide_followers
+ showing_count = showing_items || !user.hide_followers_count
query = User.get_followers_query(user)
query = from(user in query, select: [:ap_id])
@@ -183,8 +184,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
end
def render("followers.json", %{user: user} = opts) do
- showing_items = (opts[:for] && opts[:for] == user) || !user.info.hide_followers
- showing_count = showing_items || !user.info.hide_followers_count
+ showing_items = (opts[:for] && opts[:for] == user) || !user.hide_followers
+ showing_count = showing_items || !user.hide_followers_count
query = User.get_followers_query(user)
query = from(user in query, select: [:ap_id])
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 270d0fa02..cd4097493 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Utils
require Pleroma.Constants
@@ -15,7 +16,7 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
def is_public?(%Object{data: data}), do: is_public?(data)
def is_public?(%Activity{data: data}), do: is_public?(data)
def is_public?(%{"directMessage" => true}), do: false
- def is_public?(data), do: Pleroma.Constants.as_public() in (data["to"] ++ (data["cc"] || []))
+ def is_public?(data), do: Utils.label_in_message?(Pleroma.Constants.as_public(), data)
def is_private?(activity) do
with false <- is_public?(activity),
@@ -58,7 +59,7 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
end
def visible_for_user?(activity, user) do
- x = [user.ap_id | user.following]
+ x = [user.ap_id | User.following(user)]
y = [activity.actor] ++ activity.data["to"] ++ (activity.data["cc"] || [])
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
end
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index a556ab050..1f48ce8c1 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -47,11 +47,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
:user_delete,
:users_create,
:user_toggle_activation,
+ :user_activate,
+ :user_deactivate,
:tag_users,
:untag_users,
:right_add,
- :right_delete,
- :set_activation_status
+ :right_delete
]
)
@@ -99,7 +100,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
ModerationLog.insert_log(%{
actor: admin,
- subject: user,
+ subject: [user],
action: "delete"
})
@@ -107,6 +108,20 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|> json(nickname)
end
+ def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
+ User.delete(users)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "delete"
+ })
+
+ conn
+ |> json(nicknames)
+ end
+
def user_follow(%{assigns: %{user: admin}} = conn, %{
"follower" => follower_nick,
"followed" => followed_nick
@@ -235,13 +250,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
user = User.get_cached_by_nickname(nickname)
- {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
+ {:ok, updated_user} = User.deactivate(user, !user.deactivated)
- action = if user.info.deactivated, do: "activate", else: "deactivate"
+ action = if user.deactivated, do: "activate", else: "deactivate"
ModerationLog.insert_log(%{
actor: admin,
- subject: user,
+ subject: [user],
action: action
})
@@ -250,6 +265,36 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|> render("show.json", %{user: updated_user})
end
+ def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+ {:ok, updated_users} = User.deactivate(users, false)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "activate"
+ })
+
+ conn
+ |> put_view(AccountView)
+ |> render("index.json", %{users: Keyword.values(updated_users)})
+ end
+
+ def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+ {:ok, updated_users} = User.deactivate(users, true)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "deactivate"
+ })
+
+ conn
+ |> put_view(AccountView)
+ |> render("index.json", %{users: Keyword.values(updated_users)})
+ end
+
def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
with {:ok, _} <- User.tag(nicknames, tags) do
ModerationLog.insert_log(%{
@@ -290,6 +335,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
}
with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
+ {:ok, users, count} <- filter_relay_user(users, count),
do:
conn
|> json(
@@ -301,6 +347,17 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
)
end
+ defp filter_relay_user(users, count) do
+ filtered_users = Enum.reject(users, &relay_user?/1)
+ count = if Enum.any?(users, &relay_user?/1), do: length(filtered_users), else: count
+
+ {:ok, filtered_users, count}
+ end
+
+ defp relay_user?(user) do
+ user.ap_id == Relay.relay_ap_id()
+ end
+
@filters ~w(local external active deactivated is_admin is_moderator)
@spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
@@ -314,26 +371,51 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|> Enum.into(%{}, &{&1, true})
end
+ def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
+ "permission_group" => permission_group,
+ "nicknames" => nicknames
+ })
+ when permission_group in ["moderator", "admin"] do
+ update = %{:"is_#{permission_group}" => true}
+
+ users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
+
+ for u <- users, do: User.admin_api_update(u, update)
+
+ ModerationLog.insert_log(%{
+ action: "grant",
+ actor: admin,
+ subject: users,
+ permission: permission_group
+ })
+
+ json(conn, update)
+ end
+
+ def right_add_multiple(conn, _) do
+ render_error(conn, :not_found, "No such permission_group")
+ end
+
def right_add(%{assigns: %{user: admin}} = conn, %{
"permission_group" => permission_group,
"nickname" => nickname
})
when permission_group in ["moderator", "admin"] do
- info = Map.put(%{}, "is_" <> permission_group, true)
+ fields = %{:"is_#{permission_group}" => true}
{:ok, user} =
nickname
|> User.get_cached_by_nickname()
- |> User.update_info(&User.Info.admin_api_update(&1, info))
+ |> User.admin_api_update(fields)
ModerationLog.insert_log(%{
action: "grant",
actor: admin,
- subject: user,
+ subject: [user],
permission: permission_group
})
- json(conn, info)
+ json(conn, fields)
end
def right_add(conn, _) do
@@ -345,13 +427,41 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
conn
|> json(%{
- is_moderator: user.info.is_moderator,
- is_admin: user.info.is_admin
+ is_moderator: user.is_moderator,
+ is_admin: user.is_admin
})
end
- def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
- render_error(conn, :forbidden, "You can't revoke your own admin status.")
+ def right_delete_multiple(
+ %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
+ %{
+ "permission_group" => permission_group,
+ "nicknames" => nicknames
+ }
+ )
+ when permission_group in ["moderator", "admin"] do
+ with false <- Enum.member?(nicknames, admin_nickname) do
+ update = %{:"is_#{permission_group}" => false}
+
+ users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
+
+ for u <- users, do: User.admin_api_update(u, update)
+
+ ModerationLog.insert_log(%{
+ action: "revoke",
+ actor: admin,
+ subject: users,
+ permission: permission_group
+ })
+
+ json(conn, update)
+ else
+ _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
+ end
+ end
+
+ def right_delete_multiple(conn, _) do
+ render_error(conn, :not_found, "No such permission_group")
end
def right_delete(
@@ -362,43 +472,34 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
}
)
when permission_group in ["moderator", "admin"] do
- info = Map.put(%{}, "is_" <> permission_group, false)
+ fields = %{:"is_#{permission_group}" => false}
{:ok, user} =
nickname
|> User.get_cached_by_nickname()
- |> User.update_info(&User.Info.admin_api_update(&1, info))
+ |> User.admin_api_update(fields)
ModerationLog.insert_log(%{
action: "revoke",
actor: admin,
- subject: user,
+ subject: [user],
permission: permission_group
})
- json(conn, info)
+ json(conn, fields)
end
- def right_delete(conn, _) do
- render_error(conn, :not_found, "No such permission_group")
+ def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
+ render_error(conn, :forbidden, "You can't revoke your own admin status.")
end
- def set_activation_status(%{assigns: %{user: admin}} = conn, %{
- "nickname" => nickname,
- "status" => status
- }) do
- with {:ok, status} <- Ecto.Type.cast(:boolean, status),
- %User{} = user <- User.get_cached_by_nickname(nickname),
- {:ok, _} <- User.deactivate(user, !status) do
- action = if(user.info.deactivated, do: "activate", else: "deactivate")
-
- ModerationLog.insert_log(%{
- actor: admin,
- subject: user,
- action: action
- })
-
- json_response(conn, :no_content, "")
+ def relay_list(conn, _params) do
+ with {:ok, list} <- Relay.list() do
+ json(conn, %{relays: list})
+ else
+ _ ->
+ conn
+ |> put_status(500)
end
end
diff --git a/lib/pleroma/web/admin_api/report.ex b/lib/pleroma/web/admin_api/report.ex
index c751dc2be..9c3468570 100644
--- a/lib/pleroma/web/admin_api/report.ex
+++ b/lib/pleroma/web/admin_api/report.ex
@@ -13,8 +13,9 @@ defmodule Pleroma.Web.AdminAPI.Report do
account = User.get_cached_by_ap_id(account_ap_id)
statuses =
- Enum.map(status_ap_ids, fn ap_id ->
- Activity.get_by_ap_id_with_object(ap_id)
+ Enum.map(status_ap_ids, fn
+ act when is_map(act) -> Activity.get_by_ap_id_with_object(act["id"])
+ act when is_binary(act) -> Activity.get_by_ap_id_with_object(act)
end)
%{report: report, user: user, account: account, statuses: statuses}
diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex
index a96affd40..6aa7257ce 100644
--- a/lib/pleroma/web/admin_api/views/account_view.ex
+++ b/lib/pleroma/web/admin_api/views/account_view.ex
@@ -7,7 +7,6 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
alias Pleroma.HTML
alias Pleroma.User
- alias Pleroma.User.Info
alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.MediaProxy
@@ -19,6 +18,12 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
}
end
+ def render("index.json", %{users: users}) do
+ %{
+ users: render_many(users, AccountView, "show.json", as: :user)
+ }
+ end
+
def render("show.json", %{user: user}) do
avatar = User.avatar_url(user) |> MediaProxy.url()
display_name = HTML.strip_tags(user.name || user.nickname)
@@ -28,9 +33,9 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
"avatar" => avatar,
"nickname" => user.nickname,
"display_name" => display_name,
- "deactivated" => user.info.deactivated,
+ "deactivated" => user.deactivated,
"local" => user.local,
- "roles" => Info.roles(user.info),
+ "roles" => User.roles(user),
"tags" => user.tags || []
}
end
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index e5d399d02..4bf39baa9 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Activity
alias Pleroma.ActivityExpiration
alias Pleroma.Conversation.Participation
+ alias Pleroma.FollowingRelationship
alias Pleroma.Object
alias Pleroma.ThreadMute
alias Pleroma.User
@@ -40,6 +41,7 @@ defmodule Pleroma.Web.CommonAPI do
with {:ok, follower} <- User.follow(follower, followed),
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
+ {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept"),
{:ok, _activity} <-
ActivityPub.accept(%{
to: [follower.ap_id],
@@ -54,6 +56,7 @@ defmodule Pleroma.Web.CommonAPI do
def reject_follow_request(follower, followed) do
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
+ {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
{:ok, _activity} <-
ActivityPub.reject(%{
to: [follower.ap_id],
@@ -263,10 +266,10 @@ defmodule Pleroma.Web.CommonAPI do
# Updates the emojis for a user based on their profile
def update(user) do
emoji = emoji_from_profile(user)
- source_data = user.info |> Map.get(:source_data, %{}) |> Map.put("tag", emoji)
+ source_data = Map.put(user.source_data, "tag", emoji)
user =
- case User.update_info(user, &User.Info.set_source_data(&1, source_data)) do
+ case User.update_source_data(user, source_data) do
{:ok, user} -> user
_ -> user
end
@@ -287,20 +290,20 @@ defmodule Pleroma.Web.CommonAPI do
object: %Object{data: %{"type" => "Note"}}
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
true <- Visibility.is_public?(activity),
- {:ok, _user} <- User.update_info(user, &User.Info.add_pinnned_activity(&1, activity)) do
+ {:ok, _user} <- User.add_pinnned_activity(user, activity) do
{:ok, activity}
else
- {:error, %{changes: %{info: %{errors: [pinned_activities: {err, _}]}}}} -> {:error, err}
+ {:error, %{errors: [pinned_activities: {err, _}]}} -> {:error, err}
_ -> {:error, dgettext("errors", "Could not pin")}
end
end
def unpin(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
- {:ok, _user} <- User.update_info(user, &User.Info.remove_pinnned_activity(&1, activity)) do
+ {:ok, _user} <- User.remove_pinnned_activity(user, activity) do
{:ok, activity}
else
- %{errors: [pinned_activities: {err, _}]} -> {:error, err}
+ {:error, %{errors: [pinned_activities: {err, _}]}} -> {:error, err}
_ -> {:error, dgettext("errors", "Could not unpin")}
end
end
@@ -399,14 +402,14 @@ defmodule Pleroma.Web.CommonAPI do
defp set_visibility(activity, _), do: {:ok, activity}
def hide_reblogs(user, %{ap_id: ap_id} = _muted) do
- if ap_id not in user.info.muted_reblogs do
- User.update_info(user, &User.Info.add_reblog_mute(&1, ap_id))
+ if ap_id not in user.muted_reblogs do
+ User.add_reblog_mute(user, ap_id)
end
end
def show_reblogs(user, %{ap_id: ap_id} = _muted) do
- if ap_id in user.info.muted_reblogs do
- User.update_info(user, &User.Info.remove_reblog_mute(&1, ap_id))
+ if ap_id in user.muted_reblogs do
+ User.remove_reblog_mute(user, ap_id)
end
end
end
diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex
index 1a2da014a..e8a56ebd7 100644
--- a/lib/pleroma/web/federator/federator.ex
+++ b/lib/pleroma/web/federator/federator.ex
@@ -10,19 +10,11 @@ defmodule Pleroma.Web.Federator do
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Federator.Publisher
- alias Pleroma.Web.OStatus
- alias Pleroma.Web.Websub
alias Pleroma.Workers.PublisherWorker
alias Pleroma.Workers.ReceiverWorker
- alias Pleroma.Workers.SubscriberWorker
require Logger
- def init do
- # To do: consider removing this call in favor of scheduled execution (`quantum`-based)
- refresh_subscriptions(schedule_in: 60)
- end
-
@doc "Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161)"
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
def allowed_incoming_reply_depth?(depth) do
@@ -37,10 +29,6 @@ defmodule Pleroma.Web.Federator do
# Client API
- def incoming_doc(doc) do
- ReceiverWorker.enqueue("incoming_doc", %{"body" => doc})
- end
-
def incoming_ap_doc(params) do
ReceiverWorker.enqueue("incoming_ap_doc", %{"params" => params})
end
@@ -53,18 +41,6 @@ defmodule Pleroma.Web.Federator do
PublisherWorker.enqueue("publish", %{"activity_id" => activity.id})
end
- def verify_websub(websub) do
- SubscriberWorker.enqueue("verify_websub", %{"websub_id" => websub.id})
- end
-
- def request_subscription(websub) do
- SubscriberWorker.enqueue("request_subscription", %{"websub_id" => websub.id})
- end
-
- def refresh_subscriptions(worker_args \\ []) do
- SubscriberWorker.enqueue("refresh_subscriptions", %{}, worker_args ++ [max_attempts: 1])
- end
-
# Job Worker Callbacks
@spec perform(atom(), module(), any()) :: {:ok, any()} | {:error, any()}
@@ -81,11 +57,6 @@ defmodule Pleroma.Web.Federator do
end
end
- def perform(:incoming_doc, doc) do
- Logger.info("Got document, trying to parse")
- OStatus.handle_incoming(doc)
- end
-
def perform(:incoming_ap_doc, params) do
Logger.info("Handling incoming AP activity")
@@ -111,29 +82,6 @@ defmodule Pleroma.Web.Federator do
end
end
- def perform(:request_subscription, websub) do
- Logger.debug("Refreshing #{websub.topic}")
-
- with {:ok, websub} <- Websub.request_subscription(websub) do
- Logger.debug("Successfully refreshed #{websub.topic}")
- else
- _e -> Logger.debug("Couldn't refresh #{websub.topic}")
- end
- end
-
- def perform(:verify_websub, websub) do
- Logger.debug(fn ->
- "Running WebSub verification for #{websub.id} (#{websub.topic}, #{websub.callback})"
- end)
-
- Websub.verify(websub)
- end
-
- def perform(:refresh_subscriptions) do
- Logger.debug("Federator running refresh subscriptions")
- Websub.refresh_subscriptions()
- end
-
def ap_enabled_actor(id) do
user = User.get_cached_by_ap_id(id)
diff --git a/lib/pleroma/web/federator/publisher.ex b/lib/pleroma/web/federator/publisher.ex
index 937064638..fb9b26649 100644
--- a/lib/pleroma/web/federator/publisher.ex
+++ b/lib/pleroma/web/federator/publisher.ex
@@ -80,4 +80,30 @@ defmodule Pleroma.Web.Federator.Publisher do
links ++ module.gather_nodeinfo_protocol_names()
end)
end
+
+ @doc """
+ Gathers a set of remote users given an IR envelope.
+ """
+ def remote_users(%User{id: user_id}, %{data: %{"to" => to} = data}) do
+ cc = Map.get(data, "cc", [])
+
+ bcc =
+ data
+ |> Map.get("bcc", [])
+ |> Enum.reduce([], fn ap_id, bcc ->
+ case Pleroma.List.get_by_ap_id(ap_id) do
+ %Pleroma.List{user_id: ^user_id} = list ->
+ {:ok, following} = Pleroma.List.get_following(list)
+ bcc ++ Enum.map(following, & &1.ap_id)
+
+ _ ->
+ bcc
+ end
+ end)
+
+ [to, cc, bcc]
+ |> Enum.concat()
+ |> Enum.map(&User.get_cached_by_ap_id/1)
+ |> Enum.filter(fn user -> user && !user.local end)
+ end
end
diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex
index 87860f1d5..ca261ad6e 100644
--- a/lib/pleroma/web/masto_fe_controller.ex
+++ b/lib/pleroma/web/masto_fe_controller.ex
@@ -34,9 +34,15 @@ defmodule Pleroma.Web.MastoFEController do
end
end
+ @doc "GET /web/manifest.json"
+ def manifest(conn, _params) do
+ conn
+ |> render("manifest.json")
+ end
+
@doc "PUT /api/web/settings"
def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
- with {:ok, _} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do
+ with {:ok, _} <- User.mastodon_settings_update(user, settings) do
json(conn, %{})
else
e ->
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 9ef7fd48d..73fad519e 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -130,25 +130,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
def update_credentials(%{assigns: %{user: original_user}} = conn, params) do
user = original_user
- user_params =
- %{}
- |> add_if_present(params, "display_name", :name)
- |> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value, user)} end)
- |> add_if_present(params, "avatar", :avatar, fn value ->
- with %Plug.Upload{} <- value,
- {:ok, object} <- ActivityPub.upload(value, type: :avatar) do
- {:ok, object.data}
- end
- end)
-
- emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "")
-
- user_info_emojis =
- user.info
- |> Map.get(:emoji, [])
- |> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text))
- |> Enum.dedup()
-
params =
if Map.has_key?(params, "fields_attributes") do
Map.update!(params, "fields_attributes", fn fields ->
@@ -160,7 +141,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
params
end
- info_params =
+ user_params =
[
:no_rich_text,
:locked,
@@ -176,15 +157,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|> Enum.reduce(%{}, fn key, acc ->
add_if_present(acc, params, to_string(key), key, &{:ok, truthy_param?(&1)})
end)
- |> add_if_present(params, "default_scope", :default_scope)
- |> add_if_present(params, "fields_attributes", :fields, fn fields ->
- fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
-
- {:ok, fields}
- end)
- |> add_if_present(params, "fields_attributes", :raw_fields)
- |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value ->
- {:ok, Map.merge(user.info.pleroma_settings_store, value)}
+ |> add_if_present(params, "display_name", :name)
+ |> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value, user)} end)
+ |> add_if_present(params, "avatar", :avatar, fn value ->
+ with %Plug.Upload{} <- value,
+ {:ok, object} <- ActivityPub.upload(value, type: :avatar) do
+ {:ok, object.data}
+ end
end)
|> add_if_present(params, "header", :banner, fn value ->
with %Plug.Upload{} <- value,
@@ -198,12 +177,27 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
{:ok, object.data}
end
end)
- |> Map.put(:emoji, user_info_emojis)
+ |> add_if_present(params, "fields_attributes", :fields, fn fields ->
+ fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
- changeset =
+ {:ok, fields}
+ end)
+ |> add_if_present(params, "fields_attributes", :raw_fields)
+ |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value ->
+ {:ok, Map.merge(user.pleroma_settings_store, value)}
+ end)
+ |> add_if_present(params, "default_scope", :default_scope)
+
+ emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "")
+
+ user_emojis =
user
- |> User.update_changeset(user_params)
- |> User.change_info(&User.Info.profile_update(&1, info_params))
+ |> Map.get(:emoji, [])
+ |> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text))
+ |> Enum.dedup()
+
+ user_params = Map.put(user_params, :emoji, user_emojis)
+ changeset = User.update_changeset(user, user_params)
with {:ok, user} <- User.update_and_set_cache(changeset) do
if original_user != user, do: CommonAPI.update(user)
@@ -269,7 +263,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
followers =
cond do
for_user && user.id == for_user.id -> MastodonAPI.get_followers(user, params)
- user.info.hide_followers -> []
+ user.hide_followers -> []
true -> MastodonAPI.get_followers(user, params)
end
@@ -283,7 +277,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
followers =
cond do
for_user && user.id == for_user.id -> MastodonAPI.get_friends(user, params)
- user.info.hide_follows -> []
+ user.hide_follows -> []
true -> MastodonAPI.get_friends(user, params)
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
index c7606246b..456fe7ab2 100644
--- a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
@@ -21,8 +21,8 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/domain_blocks"
- def index(%{assigns: %{user: %{info: info}}} = conn, _) do
- json(conn, Map.get(info, :domain_blocks, []))
+ def index(%{assigns: %{user: user}} = conn, _) do
+ json(conn, Map.get(user, :domain_blocks, []))
end
@doc "POST /api/v1/domain_blocks"
diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex
new file mode 100644
index 000000000..ce025624d
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex
@@ -0,0 +1,32 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.MarkerController do
+ use Pleroma.Web, :controller
+ alias Pleroma.Plugs.OAuthScopesPlug
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["read:statuses"]}
+ when action == :index
+ )
+
+ plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :upsert)
+ plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ # GET /api/v1/markers
+ def index(%{assigns: %{user: user}} = conn, params) do
+ markers = Pleroma.Marker.get_markers(user, params["timeline"])
+ render(conn, "markers.json", %{markers: markers})
+ end
+
+ # POST /api/v1/markers
+ def upsert(%{assigns: %{user: user}} = conn, params) do
+ with {:ok, result} <- Pleroma.Marker.upsert(user, params),
+ markers <- Map.values(result) do
+ render(conn, "markers.json", %{markers: markers})
+ end
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
index 9f086a8c2..f2d2d3ccb 100644
--- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
alias Pleroma.Pagination
alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct])
@@ -28,7 +29,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|> Map.put("muting_user", user)
|> Map.put("user", user)
- recipients = [user.ap_id | user.following]
+ recipients = [user.ap_id | User.following(user)]
activities =
recipients
@@ -128,9 +129,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
# we must filter the following list for the user to avoid leaking statuses the user
# does not actually have permission to see (for more info, peruse security issue #270).
+
+ user_following = User.following(user)
+
activities =
following
- |> Enum.filter(fn x -> x in user.following end)
+ |> Enum.filter(fn x -> x in user_following end)
|> ActivityPub.fetch_activities_bounded(following, params)
|> Enum.reverse()
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex
index ac01d1ff3..d875a5788 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex
@@ -71,6 +71,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
defp cast_params(params) do
param_types = %{
exclude_types: {:array, :string},
+ exclude_visibilities: {:array, :string},
reblogs: :boolean,
with_muted: :boolean
}
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 2d4976891..e30fed610 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -74,23 +74,23 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
user_info = User.get_cached_user_info(user)
following_count =
- if !user.info.hide_follows_count or !user.info.hide_follows or opts[:for] == user do
+ if !user.hide_follows_count or !user.hide_follows or opts[:for] == user do
user_info.following_count
else
0
end
followers_count =
- if !user.info.hide_followers_count or !user.info.hide_followers or opts[:for] == user do
+ if !user.hide_followers_count or !user.hide_followers or opts[:for] == user do
user_info.follower_count
else
0
end
- bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"]
+ bot = (user.source_data["type"] || "Person") in ["Application", "Service"]
emojis =
- (user.info.source_data["tag"] || [])
+ (user.source_data["tag"] || [])
|> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
|> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
%{
@@ -102,8 +102,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
end)
fields =
- user.info
- |> User.Info.fields()
+ user
+ |> User.fields()
|> Enum.map(fn %{"name" => name, "value" => value} ->
%{
"name" => Pleroma.HTML.strip_tags(name),
@@ -111,23 +111,19 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
}
end)
- raw_fields = Map.get(user.info, :raw_fields, [])
-
bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for]))
relationship = render("relationship.json", %{user: opts[:for], target: user})
- discoverable = user.info.discoverable
-
%{
id: to_string(user.id),
username: username_from_nickname(user.nickname),
acct: user.nickname,
display_name: display_name,
- locked: user_info.locked,
+ locked: user.locked,
created_at: Utils.to_masto_date(user.inserted_at),
followers_count: followers_count,
following_count: following_count,
- statuses_count: user_info.note_count,
+ statuses_count: user.note_count,
note: bio || "",
url: User.profile_url(user),
avatar: image,
@@ -140,9 +136,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
source: %{
note: HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
sensitive: false,
- fields: raw_fields,
+ fields: user.raw_fields,
pleroma: %{
- discoverable: discoverable
+ discoverable: user.discoverable
}
},
@@ -150,14 +146,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
pleroma: %{
confirmation_pending: user_info.confirmation_pending,
tags: user.tags,
- hide_followers_count: user.info.hide_followers_count,
- hide_follows_count: user.info.hide_follows_count,
- hide_followers: user.info.hide_followers,
- hide_follows: user.info.hide_follows,
- hide_favorites: user.info.hide_favorites,
+ hide_followers_count: user.hide_followers_count,
+ hide_follows_count: user.hide_follows_count,
+ hide_followers: user.hide_followers,
+ hide_follows: user.hide_follows,
+ hide_favorites: user.hide_favorites,
relationship: relationship,
- skip_thread_containment: user.info.skip_thread_containment,
- background_image: image_url(user.info.background) |> MediaProxy.url()
+ skip_thread_containment: user.skip_thread_containment,
+ background_image: image_url(user.background) |> MediaProxy.url()
}
}
|> maybe_put_role(user, opts[:for])
@@ -195,21 +191,21 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
data,
%User{id: user_id} = user,
%User{id: user_id},
- user_info
+ _user_info
) do
data
- |> Kernel.put_in([:source, :privacy], user_info.default_scope)
- |> Kernel.put_in([:source, :pleroma, :show_role], user.info.show_role)
- |> Kernel.put_in([:source, :pleroma, :no_rich_text], user.info.no_rich_text)
+ |> Kernel.put_in([:source, :privacy], user.default_scope)
+ |> Kernel.put_in([:source, :pleroma, :show_role], user.show_role)
+ |> Kernel.put_in([:source, :pleroma, :no_rich_text], user.no_rich_text)
end
defp maybe_put_settings(data, _, _, _), do: data
- defp maybe_put_settings_store(data, %User{info: info, id: id}, %User{id: id}, %{
+ defp maybe_put_settings_store(data, %User{} = user, %User{}, %{
with_pleroma_settings: true
}) do
data
- |> Kernel.put_in([:pleroma, :settings_store], info.pleroma_settings_store)
+ |> Kernel.put_in([:pleroma, :settings_store], user.pleroma_settings_store)
end
defp maybe_put_settings_store(data, _, _, _), do: data
@@ -223,28 +219,28 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
defp maybe_put_chat_token(data, _, _, _), do: data
- defp maybe_put_role(data, %User{info: %{show_role: true}} = user, _) do
+ defp maybe_put_role(data, %User{show_role: true} = user, _) do
data
- |> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin)
- |> Kernel.put_in([:pleroma, :is_moderator], user.info.is_moderator)
+ |> Kernel.put_in([:pleroma, :is_admin], user.is_admin)
+ |> Kernel.put_in([:pleroma, :is_moderator], user.is_moderator)
end
defp maybe_put_role(data, %User{id: user_id} = user, %User{id: user_id}) do
data
- |> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin)
- |> Kernel.put_in([:pleroma, :is_moderator], user.info.is_moderator)
+ |> Kernel.put_in([:pleroma, :is_admin], user.is_admin)
+ |> Kernel.put_in([:pleroma, :is_moderator], user.is_moderator)
end
defp maybe_put_role(data, _, _), do: data
defp maybe_put_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
- Kernel.put_in(data, [:pleroma, :notification_settings], user.info.notification_settings)
+ Kernel.put_in(data, [:pleroma, :notification_settings], user.notification_settings)
end
defp maybe_put_notification_settings(data, _, _), do: data
- defp maybe_put_activation_status(data, user, %User{info: %{is_admin: true}}) do
- Kernel.put_in(data, [:pleroma, :deactivated], user.info.deactivated)
+ defp maybe_put_activation_status(data, user, %User{is_admin: true}) do
+ Kernel.put_in(data, [:pleroma, :deactivated], user.deactivated)
end
defp maybe_put_activation_status(data, _, _), do: data
@@ -253,7 +249,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
data
|> Kernel.put_in(
[:pleroma, :unread_conversation_count],
- user.info.unread_conversation_count
+ user.unread_conversation_count
)
end
diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
index e9d2735b3..c5998e661 100644
--- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
@@ -34,7 +34,11 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do
id: participation.id |> to_string(),
accounts: render(AccountView, "index.json", users: users, as: :user),
unread: !participation.read,
- last_status: render(StatusView, "show.json", activity: activity, for: user)
+ last_status:
+ render(StatusView, "show.json",
+ activity: activity,
+ direct_conversation_id: participation.id
+ )
}
end
end
diff --git a/lib/pleroma/web/mastodon_api/views/marker_view.ex b/lib/pleroma/web/mastodon_api/views/marker_view.ex
new file mode 100644
index 000000000..38fbeed5f
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/marker_view.ex
@@ -0,0 +1,17 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.MarkerView do
+ use Pleroma.Web, :view
+
+ def render("markers.json", %{markers: markers}) do
+ Enum.reduce(markers, %{}, fn m, acc ->
+ Map.put_new(acc, m.timeline, %{
+ last_read_id: m.last_read_id,
+ version: m.lock_version,
+ updated_at: NaiveDateTime.to_iso8601(m.updated_at)
+ })
+ end)
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 9b8dd3086..baff54151 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -243,7 +243,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
direct_conversation_id =
- with {_, true} <- {:include_id, opts[:with_direct_conversation_id]},
+ with {_, nil} <- {:direct_conversation_id, opts[:direct_conversation_id]},
+ {_, true} <- {:include_id, opts[:with_direct_conversation_id]},
{_, %User{} = for_user} <- {:for_user, opts[:for]},
%{data: %{"context" => context}} when is_binary(context) <- activity,
%Conversation{} = conversation <- Conversation.get_for_ap_id(context),
@@ -251,6 +252,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
Participation.for_user_and_conversation(for_user, conversation) do
participation_id
else
+ {:direct_conversation_id, participation_id} when is_integer(participation_id) ->
+ participation_id
+
_e ->
nil
end
@@ -498,6 +502,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
defp present?(false), do: false
defp present?(_), do: true
- defp pinned?(%Activity{id: id}, %User{info: %{pinned_activities: pinned_activities}}),
+ defp pinned?(%Activity{id: id}, %User{pinned_activities: pinned_activities}),
do: id in pinned_activities
end
diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex
index 3c26eb406..a400d1c8d 100644
--- a/lib/pleroma/web/mastodon_api/websocket_handler.ex
+++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex
@@ -35,6 +35,13 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
{_, stream} <- List.keyfind(params, "stream", 0),
{:ok, user} <- allow_request(stream, [access_token, sec_websocket]),
topic when is_binary(topic) <- expand_topic(stream, params) do
+ req =
+ if sec_websocket do
+ :cowboy_req.set_resp_header("sec-websocket-protocol", sec_websocket, req)
+ else
+ req
+ end
+
{:cowboy_websocket, req, %{user: user, topic: topic}, %{idle_timeout: @timeout}}
else
{:error, code} ->
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 03c9a5027..fe71aca8c 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -202,9 +202,9 @@ defmodule Pleroma.Web.OAuth.OAuthController do
with {:ok, %User{} = user} <- Authenticator.get_user(conn),
{:ok, app} <- Token.Utils.fetch_app(conn),
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
- {:user_active, true} <- {:user_active, !user.info.deactivated},
+ {:user_active, true} <- {:user_active, !user.deactivated},
{:password_reset_pending, false} <-
- {:password_reset_pending, user.info.password_reset_pending},
+ {:password_reset_pending, user.password_reset_pending},
{:ok, scopes} <- validate_scopes(app, params),
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
{:ok, token} <- Token.exchange_token(app, auth) do
diff --git a/lib/pleroma/web/ostatus/activity_representer.ex b/lib/pleroma/web/ostatus/activity_representer.ex
deleted file mode 100644
index 8e55b9f0b..000000000
--- a/lib/pleroma/web/ostatus/activity_representer.ex
+++ /dev/null
@@ -1,313 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OStatus.ActivityRepresenter do
- alias Pleroma.Activity
- alias Pleroma.Object
- alias Pleroma.User
- alias Pleroma.Web.OStatus.UserRepresenter
-
- require Logger
- require Pleroma.Constants
-
- defp get_href(id) do
- with %Object{data: %{"external_url" => external_url}} <- Object.get_cached_by_ap_id(id) do
- external_url
- else
- _e -> id
- end
- end
-
- defp get_in_reply_to(activity) do
- with %Object{data: %{"inReplyTo" => in_reply_to}} <- Object.normalize(activity) do
- [
- {:"thr:in-reply-to",
- [ref: to_charlist(in_reply_to), href: to_charlist(get_href(in_reply_to))], []}
- ]
- else
- _ ->
- []
- end
- end
-
- defp get_mentions(to) do
- Enum.map(to, fn id ->
- cond do
- # Special handling for the AP/Ostatus public collections
- Pleroma.Constants.as_public() == id ->
- {:link,
- [
- rel: "mentioned",
- "ostatus:object-type": "http://activitystrea.ms/schema/1.0/collection",
- href: "http://activityschema.org/collection/public"
- ], []}
-
- # Ostatus doesn't handle follower collections, ignore these.
- Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) ->
- []
-
- true ->
- {:link,
- [
- rel: "mentioned",
- "ostatus:object-type": "http://activitystrea.ms/schema/1.0/person",
- href: id
- ], []}
- end
- end)
- end
-
- defp get_links(%{local: true}, %{"id" => object_id}) do
- h = fn str -> [to_charlist(str)] end
-
- [
- {:link, [type: ['application/atom+xml'], href: h.(object_id), rel: 'self'], []},
- {:link, [type: ['text/html'], href: h.(object_id), rel: 'alternate'], []}
- ]
- end
-
- defp get_links(%{local: false}, %{"external_url" => external_url}) do
- h = fn str -> [to_charlist(str)] end
-
- [
- {:link, [type: ['text/html'], href: h.(external_url), rel: 'alternate'], []}
- ]
- end
-
- defp get_links(_activity, _object_data), do: []
-
- defp get_emoji_links(emojis) do
- Enum.map(emojis, fn {emoji, file} ->
- {:link, [name: to_charlist(emoji), rel: 'emoji', href: to_charlist(file)], []}
- end)
- end
-
- def to_simple_form(activity, user, with_author \\ false)
-
- def to_simple_form(%{data: %{"type" => "Create"}} = activity, user, with_author) do
- h = fn str -> [to_charlist(str)] end
-
- object = Object.normalize(activity)
-
- updated_at = object.data["published"]
- inserted_at = object.data["published"]
-
- attachments =
- Enum.map(object.data["attachment"] || [], fn attachment ->
- url = hd(attachment["url"])
-
- {:link,
- [rel: 'enclosure', href: to_charlist(url["href"]), type: to_charlist(url["mediaType"])],
- []}
- end)
-
- in_reply_to = get_in_reply_to(activity)
- author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
- mentions = activity.recipients |> get_mentions
-
- categories =
- (object.data["tag"] || [])
- |> Enum.map(fn tag ->
- if is_binary(tag) do
- {:category, [term: to_charlist(tag)], []}
- else
- nil
- end
- end)
- |> Enum.filter(& &1)
-
- emoji_links = get_emoji_links(object.data["emoji"] || %{})
-
- summary =
- if object.data["summary"] do
- [{:summary, [], h.(object.data["summary"])}]
- else
- []
- end
-
- [
- {:"activity:object-type", ['http://activitystrea.ms/schema/1.0/note']},
- {:"activity:verb", ['http://activitystrea.ms/schema/1.0/post']},
- # For notes, federate the object id.
- {:id, h.(object.data["id"])},
- {:title, ['New note by #{user.nickname}']},
- {:content, [type: 'html'], h.(object.data["content"] |> String.replace(~r/[\n\r]/, ""))},
- {:published, h.(inserted_at)},
- {:updated, h.(updated_at)},
- {:"ostatus:conversation", [ref: h.(activity.data["context"])],
- h.(activity.data["context"])},
- {:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []}
- ] ++
- summary ++
- get_links(activity, object.data) ++
- categories ++ attachments ++ in_reply_to ++ author ++ mentions ++ emoji_links
- end
-
- def to_simple_form(%{data: %{"type" => "Like"}} = activity, user, with_author) do
- h = fn str -> [to_charlist(str)] end
-
- updated_at = activity.data["published"]
- inserted_at = activity.data["published"]
-
- author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
- mentions = activity.recipients |> get_mentions
-
- [
- {:"activity:verb", ['http://activitystrea.ms/schema/1.0/favorite']},
- {:id, h.(activity.data["id"])},
- {:title, ['New favorite by #{user.nickname}']},
- {:content, [type: 'html'], ['#{user.nickname} favorited something']},
- {:published, h.(inserted_at)},
- {:updated, h.(updated_at)},
- {:"activity:object",
- [
- {:"activity:object-type", ['http://activitystrea.ms/schema/1.0/note']},
- # For notes, federate the object id.
- {:id, h.(activity.data["object"])}
- ]},
- {:"ostatus:conversation", [ref: h.(activity.data["context"])],
- h.(activity.data["context"])},
- {:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []},
- {:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []},
- {:"thr:in-reply-to", [ref: to_charlist(activity.data["object"])], []}
- ] ++ author ++ mentions
- end
-
- def to_simple_form(%{data: %{"type" => "Announce"}} = activity, user, with_author) do
- h = fn str -> [to_charlist(str)] end
-
- updated_at = activity.data["published"]
- inserted_at = activity.data["published"]
-
- author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
-
- retweeted_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
- retweeted_object = Object.normalize(retweeted_activity)
- retweeted_user = User.get_cached_by_ap_id(retweeted_activity.data["actor"])
-
- retweeted_xml = to_simple_form(retweeted_activity, retweeted_user, true)
-
- mentions =
- ([retweeted_user.ap_id] ++ activity.recipients)
- |> Enum.uniq()
- |> get_mentions()
-
- [
- {:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
- {:"activity:verb", ['http://activitystrea.ms/schema/1.0/share']},
- {:id, h.(activity.data["id"])},
- {:title, ['#{user.nickname} repeated a notice']},
- {:content, [type: 'html'], ['RT #{retweeted_object.data["content"]}']},
- {:published, h.(inserted_at)},
- {:updated, h.(updated_at)},
- {:"ostatus:conversation", [ref: h.(activity.data["context"])],
- h.(activity.data["context"])},
- {:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []},
- {:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []},
- {:"activity:object", retweeted_xml}
- ] ++ mentions ++ author
- end
-
- def to_simple_form(%{data: %{"type" => "Follow"}} = activity, user, with_author) do
- h = fn str -> [to_charlist(str)] end
-
- updated_at = activity.data["published"]
- inserted_at = activity.data["published"]
-
- author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
-
- mentions = (activity.recipients || []) |> get_mentions
-
- [
- {:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
- {:"activity:verb", ['http://activitystrea.ms/schema/1.0/follow']},
- {:id, h.(activity.data["id"])},
- {:title, ['#{user.nickname} started following #{activity.data["object"]}']},
- {:content, [type: 'html'],
- ['#{user.nickname} started following #{activity.data["object"]}']},
- {:published, h.(inserted_at)},
- {:updated, h.(updated_at)},
- {:"activity:object",
- [
- {:"activity:object-type", ['http://activitystrea.ms/schema/1.0/person']},
- {:id, h.(activity.data["object"])},
- {:uri, h.(activity.data["object"])}
- ]},
- {:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []}
- ] ++ mentions ++ author
- end
-
- # Only undos of follow for now. Will need to get redone once there are more
- def to_simple_form(
- %{data: %{"type" => "Undo", "object" => %{"type" => "Follow"} = follow_activity}} =
- activity,
- user,
- with_author
- ) do
- h = fn str -> [to_charlist(str)] end
-
- updated_at = activity.data["published"]
- inserted_at = activity.data["published"]
-
- author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
-
- mentions = (activity.recipients || []) |> get_mentions
- follow_activity = Activity.normalize(follow_activity)
-
- [
- {:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
- {:"activity:verb", ['http://activitystrea.ms/schema/1.0/unfollow']},
- {:id, h.(activity.data["id"])},
- {:title, ['#{user.nickname} stopped following #{follow_activity.data["object"]}']},
- {:content, [type: 'html'],
- ['#{user.nickname} stopped following #{follow_activity.data["object"]}']},
- {:published, h.(inserted_at)},
- {:updated, h.(updated_at)},
- {:"activity:object",
- [
- {:"activity:object-type", ['http://activitystrea.ms/schema/1.0/person']},
- {:id, h.(follow_activity.data["object"])},
- {:uri, h.(follow_activity.data["object"])}
- ]},
- {:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []}
- ] ++ mentions ++ author
- end
-
- def to_simple_form(%{data: %{"type" => "Delete"}} = activity, user, with_author) do
- h = fn str -> [to_charlist(str)] end
-
- updated_at = activity.data["published"]
- inserted_at = activity.data["published"]
-
- author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
-
- [
- {:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
- {:"activity:verb", ['http://activitystrea.ms/schema/1.0/delete']},
- {:id, h.(activity.data["object"])},
- {:title, ['An object was deleted']},
- {:content, [type: 'html'], ['An object was deleted']},
- {:published, h.(inserted_at)},
- {:updated, h.(updated_at)}
- ] ++ author
- end
-
- def to_simple_form(_, _, _), do: nil
-
- def wrap_with_entry(simple_form) do
- [
- {
- :entry,
- [
- xmlns: 'http://www.w3.org/2005/Atom',
- "xmlns:thr": 'http://purl.org/syndication/thread/1.0',
- "xmlns:activity": 'http://activitystrea.ms/spec/1.0/',
- "xmlns:poco": 'http://portablecontacts.net/spec/1.0',
- "xmlns:ostatus": 'http://ostatus.org/schema/1.0'
- ],
- simple_form
- }
- ]
- end
-end
diff --git a/lib/pleroma/web/ostatus/feed_representer.ex b/lib/pleroma/web/ostatus/feed_representer.ex
deleted file mode 100644
index b7b97e505..000000000
--- a/lib/pleroma/web/ostatus/feed_representer.ex
+++ /dev/null
@@ -1,66 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OStatus.FeedRepresenter do
- alias Pleroma.User
- alias Pleroma.Web.MediaProxy
- alias Pleroma.Web.OStatus
- alias Pleroma.Web.OStatus.ActivityRepresenter
- alias Pleroma.Web.OStatus.UserRepresenter
-
- def to_simple_form(user, activities, _users) do
- most_recent_update =
- (List.first(activities) || user).updated_at
- |> NaiveDateTime.to_iso8601()
-
- h = fn str -> [to_charlist(str)] end
-
- last_activity = List.last(activities)
-
- entries =
- activities
- |> Enum.map(fn activity ->
- {:entry, ActivityRepresenter.to_simple_form(activity, user)}
- end)
- |> Enum.filter(fn {_, form} -> form end)
-
- [
- {
- :feed,
- [
- xmlns: 'http://www.w3.org/2005/Atom',
- "xmlns:thr": 'http://purl.org/syndication/thread/1.0',
- "xmlns:activity": 'http://activitystrea.ms/spec/1.0/',
- "xmlns:poco": 'http://portablecontacts.net/spec/1.0',
- "xmlns:ostatus": 'http://ostatus.org/schema/1.0'
- ],
- [
- {:id, h.(OStatus.feed_path(user))},
- {:title, ['#{user.nickname}\'s timeline']},
- {:updated, h.(most_recent_update)},
- {:logo, [to_charlist(User.avatar_url(user) |> MediaProxy.url())]},
- {:link, [rel: 'hub', href: h.(OStatus.pubsub_path(user))], []},
- {:link, [rel: 'salmon', href: h.(OStatus.salmon_path(user))], []},
- {:link, [rel: 'self', href: h.(OStatus.feed_path(user)), type: 'application/atom+xml'],
- []},
- {:author, UserRepresenter.to_simple_form(user)}
- ] ++
- if last_activity do
- [
- {:link,
- [
- rel: 'next',
- href:
- to_charlist(OStatus.feed_path(user)) ++
- '?max_id=' ++ to_charlist(last_activity.id),
- type: 'application/atom+xml'
- ], []}
- ]
- else
- []
- end ++ entries
- }
- ]
- end
-end
diff --git a/lib/pleroma/web/ostatus/handlers/delete_handler.ex b/lib/pleroma/web/ostatus/handlers/delete_handler.ex
deleted file mode 100644
index b2f9f3946..000000000
--- a/lib/pleroma/web/ostatus/handlers/delete_handler.ex
+++ /dev/null
@@ -1,18 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OStatus.DeleteHandler do
- require Logger
- alias Pleroma.Object
- alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.XML
-
- def handle_delete(entry, _doc \\ nil) do
- with id <- XML.string_from_xpath("//id", entry),
- %Object{} = object <- Object.normalize(id),
- {:ok, delete} <- ActivityPub.delete(object, false) do
- delete
- end
- end
-end
diff --git a/lib/pleroma/web/ostatus/handlers/follow_handler.ex b/lib/pleroma/web/ostatus/handlers/follow_handler.ex
deleted file mode 100644
index 24513972e..000000000
--- a/lib/pleroma/web/ostatus/handlers/follow_handler.ex
+++ /dev/null
@@ -1,26 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OStatus.FollowHandler do
- alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.OStatus
- alias Pleroma.Web.XML
-
- def handle(entry, doc) do
- with {:ok, actor} <- OStatus.find_make_or_update_actor(doc),
- id when not is_nil(id) <- XML.string_from_xpath("/entry/id", entry),
- followed_uri when not is_nil(followed_uri) <-
- XML.string_from_xpath("/entry/activity:object/id", entry),
- {:ok, followed} <- OStatus.find_or_make_user(followed_uri),
- {:locked, false} <- {:locked, followed.info.locked},
- {:ok, activity} <- ActivityPub.follow(actor, followed, id, false) do
- User.follow(actor, followed)
- {:ok, activity}
- else
- {:locked, true} ->
- {:error, "It's not possible to follow locked accounts over OStatus"}
- end
- end
-end
diff --git a/lib/pleroma/web/ostatus/handlers/note_handler.ex b/lib/pleroma/web/ostatus/handlers/note_handler.ex
deleted file mode 100644
index 7fae14f7b..000000000
--- a/lib/pleroma/web/ostatus/handlers/note_handler.ex
+++ /dev/null
@@ -1,168 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OStatus.NoteHandler do
- require Logger
- require Pleroma.Constants
-
- alias Pleroma.Activity
- alias Pleroma.Object
- alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.ActivityPub.Utils
- alias Pleroma.Web.CommonAPI
- alias Pleroma.Web.Federator
- alias Pleroma.Web.OStatus
- alias Pleroma.Web.XML
-
- @doc """
- Get the context for this note. Uses this:
- 1. The context of the parent activity
- 2. The conversation reference in the ostatus xml
- 3. A newly generated context id.
- """
- def get_context(entry, in_reply_to) do
- context =
- (XML.string_from_xpath("//ostatus:conversation[1]", entry) ||
- XML.string_from_xpath("//ostatus:conversation[1]/@ref", entry) || "")
- |> String.trim()
-
- with %{data: %{"context" => context}} <- Object.get_cached_by_ap_id(in_reply_to) do
- context
- else
- _e ->
- if String.length(context) > 0 do
- context
- else
- Utils.generate_context_id()
- end
- end
- end
-
- def get_people_mentions(entry) do
- :xmerl_xpath.string(
- '//link[@rel="mentioned" and @ostatus:object-type="http://activitystrea.ms/schema/1.0/person"]',
- entry
- )
- |> Enum.map(fn person -> XML.string_from_xpath("@href", person) end)
- end
-
- def get_collection_mentions(entry) do
- transmogrify = fn
- "http://activityschema.org/collection/public" ->
- Pleroma.Constants.as_public()
-
- group ->
- group
- end
-
- :xmerl_xpath.string(
- '//link[@rel="mentioned" and @ostatus:object-type="http://activitystrea.ms/schema/1.0/collection"]',
- entry
- )
- |> Enum.map(fn collection -> XML.string_from_xpath("@href", collection) |> transmogrify.() end)
- end
-
- def get_mentions(entry) do
- (get_people_mentions(entry) ++ get_collection_mentions(entry))
- |> Enum.filter(& &1)
- end
-
- def get_emoji(entry) do
- try do
- :xmerl_xpath.string('//link[@rel="emoji"]', entry)
- |> Enum.reduce(%{}, fn emoji, acc ->
- Map.put(acc, XML.string_from_xpath("@name", emoji), XML.string_from_xpath("@href", emoji))
- end)
- rescue
- _e -> nil
- end
- end
-
- def make_to_list(actor, mentions) do
- [
- actor.follower_address
- ] ++ mentions
- end
-
- def add_external_url(note, entry) do
- url = XML.string_from_xpath("//link[@rel='alternate' and @type='text/html']/@href", entry)
- Map.put(note, "external_url", url)
- end
-
- def fetch_replied_to_activity(entry, in_reply_to, options \\ []) do
- with %Activity{} = activity <- Activity.get_create_by_object_ap_id(in_reply_to) do
- activity
- else
- _e ->
- with true <- Federator.allowed_incoming_reply_depth?(options[:depth]),
- in_reply_to_href when not is_nil(in_reply_to_href) <-
- XML.string_from_xpath("//thr:in-reply-to[1]/@href", entry),
- {:ok, [activity | _]} <- OStatus.fetch_activity_from_url(in_reply_to_href, options) do
- activity
- else
- _e -> nil
- end
- end
- end
-
- # TODO: Clean this up a bit.
- def handle_note(entry, doc \\ nil, options \\ []) do
- with id <- XML.string_from_xpath("//id", entry),
- activity when is_nil(activity) <- Activity.get_create_by_object_ap_id_with_object(id),
- [author] <- :xmerl_xpath.string('//author[1]', doc),
- {:ok, actor} <- OStatus.find_make_or_update_actor(author),
- content_html <- OStatus.get_content(entry),
- cw <- OStatus.get_cw(entry),
- in_reply_to <- XML.string_from_xpath("//thr:in-reply-to[1]/@ref", entry),
- options <- Keyword.put(options, :depth, (options[:depth] || 0) + 1),
- in_reply_to_activity <- fetch_replied_to_activity(entry, in_reply_to, options),
- in_reply_to_object <-
- (in_reply_to_activity && Object.normalize(in_reply_to_activity)) || nil,
- in_reply_to <- (in_reply_to_object && in_reply_to_object.data["id"]) || in_reply_to,
- attachments <- OStatus.get_attachments(entry),
- context <- get_context(entry, in_reply_to),
- tags <- OStatus.get_tags(entry),
- mentions <- get_mentions(entry),
- to <- make_to_list(actor, mentions),
- date <- XML.string_from_xpath("//published", entry),
- unlisted <- XML.string_from_xpath("//mastodon:scope", entry) == "unlisted",
- cc <- if(unlisted, do: [Pleroma.Constants.as_public()], else: []),
- note <-
- CommonAPI.Utils.make_note_data(
- actor.ap_id,
- to,
- context,
- content_html,
- attachments,
- in_reply_to_activity,
- [],
- cw
- ),
- note <- note |> Map.put("id", id) |> Map.put("tag", tags),
- note <- note |> Map.put("published", date),
- note <- note |> Map.put("emoji", get_emoji(entry)),
- note <- add_external_url(note, entry),
- note <- note |> Map.put("cc", cc),
- # TODO: Handle this case in make_note_data
- note <-
- if(
- in_reply_to && !in_reply_to_activity,
- do: note |> Map.put("inReplyTo", in_reply_to),
- else: note
- ) do
- ActivityPub.create(%{
- to: to,
- actor: actor,
- context: context,
- object: note,
- published: date,
- local: false,
- additional: %{"cc" => cc}
- })
- else
- %Activity{} = activity -> {:ok, activity}
- e -> {:error, e}
- end
- end
-end
diff --git a/lib/pleroma/web/ostatus/handlers/unfollow_handler.ex b/lib/pleroma/web/ostatus/handlers/unfollow_handler.ex
deleted file mode 100644
index 2062432e3..000000000
--- a/lib/pleroma/web/ostatus/handlers/unfollow_handler.ex
+++ /dev/null
@@ -1,22 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OStatus.UnfollowHandler do
- alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.OStatus
- alias Pleroma.Web.XML
-
- def handle(entry, doc) do
- with {:ok, actor} <- OStatus.find_make_or_update_actor(doc),
- id when not is_nil(id) <- XML.string_from_xpath("/entry/id", entry),
- followed_uri when not is_nil(followed_uri) <-
- XML.string_from_xpath("/entry/activity:object/id", entry),
- {:ok, followed} <- OStatus.find_or_make_user(followed_uri),
- {:ok, activity} <- ActivityPub.unfollow(actor, followed, id, false) do
- User.unfollow(actor, followed)
- {:ok, activity}
- end
- end
-end
diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex
deleted file mode 100644
index 5de1ceef3..000000000
--- a/lib/pleroma/web/ostatus/ostatus.ex
+++ /dev/null
@@ -1,395 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OStatus do
- import Pleroma.Web.XML
- require Logger
-
- alias Pleroma.Activity
- alias Pleroma.HTTP
- alias Pleroma.Object
- alias Pleroma.User
- alias Pleroma.Web
- alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.ActivityPub.Transmogrifier
- alias Pleroma.Web.ActivityPub.Visibility
- alias Pleroma.Web.OStatus.DeleteHandler
- alias Pleroma.Web.OStatus.FollowHandler
- alias Pleroma.Web.OStatus.NoteHandler
- alias Pleroma.Web.OStatus.UnfollowHandler
- alias Pleroma.Web.WebFinger
- alias Pleroma.Web.Websub
-
- def is_representable?(%Activity{} = activity) do
- object = Object.normalize(activity)
-
- cond do
- is_nil(object) ->
- false
-
- Visibility.is_public?(activity) && object.data["type"] == "Note" ->
- true
-
- true ->
- false
- end
- end
-
- def feed_path(user), do: "#{user.ap_id}/feed.atom"
-
- def pubsub_path(user), do: "#{Web.base_url()}/push/hub/#{user.nickname}"
-
- def salmon_path(user), do: "#{user.ap_id}/salmon"
-
- def remote_follow_path, do: "#{Web.base_url()}/ostatus_subscribe?acct={uri}"
-
- def handle_incoming(xml_string, options \\ []) do
- with doc when doc != :error <- parse_document(xml_string) do
- with {:ok, actor_user} <- find_make_or_update_actor(doc),
- do: Pleroma.Instances.set_reachable(actor_user.ap_id)
-
- entries = :xmerl_xpath.string('//entry', doc)
-
- activities =
- Enum.map(entries, fn entry ->
- {:xmlObj, :string, object_type} =
- :xmerl_xpath.string('string(/entry/activity:object-type[1])', entry)
-
- {:xmlObj, :string, verb} = :xmerl_xpath.string('string(/entry/activity:verb[1])', entry)
- Logger.debug("Handling #{verb}")
-
- try do
- case verb do
- 'http://activitystrea.ms/schema/1.0/delete' ->
- with {:ok, activity} <- DeleteHandler.handle_delete(entry, doc), do: activity
-
- 'http://activitystrea.ms/schema/1.0/follow' ->
- with {:ok, activity} <- FollowHandler.handle(entry, doc), do: activity
-
- 'http://activitystrea.ms/schema/1.0/unfollow' ->
- with {:ok, activity} <- UnfollowHandler.handle(entry, doc), do: activity
-
- 'http://activitystrea.ms/schema/1.0/share' ->
- with {:ok, activity, retweeted_activity} <- handle_share(entry, doc),
- do: [activity, retweeted_activity]
-
- 'http://activitystrea.ms/schema/1.0/favorite' ->
- with {:ok, activity, favorited_activity} <- handle_favorite(entry, doc),
- do: [activity, favorited_activity]
-
- _ ->
- case object_type do
- 'http://activitystrea.ms/schema/1.0/note' ->
- with {:ok, activity} <- NoteHandler.handle_note(entry, doc, options),
- do: activity
-
- 'http://activitystrea.ms/schema/1.0/comment' ->
- with {:ok, activity} <- NoteHandler.handle_note(entry, doc, options),
- do: activity
-
- _ ->
- Logger.error("Couldn't parse incoming document")
- nil
- end
- end
- rescue
- e ->
- Logger.error("Error occured while handling activity")
- Logger.error(xml_string)
- Logger.error(inspect(e))
- nil
- end
- end)
- |> Enum.filter(& &1)
-
- {:ok, activities}
- else
- _e -> {:error, []}
- end
- end
-
- def make_share(entry, doc, retweeted_activity) do
- with {:ok, actor} <- find_make_or_update_actor(doc),
- %Object{} = object <- Object.normalize(retweeted_activity),
- id when not is_nil(id) <- string_from_xpath("/entry/id", entry),
- {:ok, activity, _object} = ActivityPub.announce(actor, object, id, false) do
- {:ok, activity}
- end
- end
-
- def handle_share(entry, doc) do
- with {:ok, retweeted_activity} <- get_or_build_object(entry),
- {:ok, activity} <- make_share(entry, doc, retweeted_activity) do
- {:ok, activity, retweeted_activity}
- else
- e -> {:error, e}
- end
- end
-
- def make_favorite(entry, doc, favorited_activity) do
- with {:ok, actor} <- find_make_or_update_actor(doc),
- %Object{} = object <- Object.normalize(favorited_activity),
- id when not is_nil(id) <- string_from_xpath("/entry/id", entry),
- {:ok, activity, _object} = ActivityPub.like(actor, object, id, false) do
- {:ok, activity}
- end
- end
-
- def get_or_build_object(entry) do
- with {:ok, activity} <- get_or_try_fetching(entry) do
- {:ok, activity}
- else
- _e ->
- with [object] <- :xmerl_xpath.string('/entry/activity:object', entry) do
- NoteHandler.handle_note(object, object)
- end
- end
- end
-
- def get_or_try_fetching(entry) do
- Logger.debug("Trying to get entry from db")
-
- with id when not is_nil(id) <- string_from_xpath("//activity:object[1]/id", entry),
- %Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do
- {:ok, activity}
- else
- _ ->
- Logger.debug("Couldn't get, will try to fetch")
-
- with href when not is_nil(href) <-
- string_from_xpath("//activity:object[1]/link[@type=\"text/html\"]/@href", entry),
- {:ok, [favorited_activity]} <- fetch_activity_from_url(href) do
- {:ok, favorited_activity}
- else
- e -> Logger.debug("Couldn't find href: #{inspect(e)}")
- end
- end
- end
-
- def handle_favorite(entry, doc) do
- with {:ok, favorited_activity} <- get_or_try_fetching(entry),
- {:ok, activity} <- make_favorite(entry, doc, favorited_activity) do
- {:ok, activity, favorited_activity}
- else
- e -> {:error, e}
- end
- end
-
- def get_attachments(entry) do
- :xmerl_xpath.string('/entry/link[@rel="enclosure"]', entry)
- |> Enum.map(fn enclosure ->
- with href when not is_nil(href) <- string_from_xpath("/link/@href", enclosure),
- type when not is_nil(type) <- string_from_xpath("/link/@type", enclosure) do
- %{
- "type" => "Attachment",
- "url" => [
- %{
- "type" => "Link",
- "mediaType" => type,
- "href" => href
- }
- ]
- }
- end
- end)
- |> Enum.filter(& &1)
- end
-
- @doc """
- Gets the content from a an entry.
- """
- def get_content(entry) do
- string_from_xpath("//content", entry)
- end
-
- @doc """
- Get the cw that mastodon uses.
- """
- def get_cw(entry) do
- case string_from_xpath("/*/summary", entry) do
- cw when not is_nil(cw) -> cw
- _ -> nil
- end
- end
-
- def get_tags(entry) do
- :xmerl_xpath.string('//category', entry)
- |> Enum.map(fn category -> string_from_xpath("/category/@term", category) end)
- |> Enum.filter(& &1)
- |> Enum.map(&String.downcase/1)
- end
-
- def maybe_update(doc, user) do
- case string_from_xpath("//author[1]/ap_enabled", doc) do
- "true" ->
- Transmogrifier.upgrade_user_from_ap_id(user.ap_id)
-
- _ ->
- maybe_update_ostatus(doc, user)
- end
- end
-
- def maybe_update_ostatus(doc, user) do
- old_data = Map.take(user, [:bio, :avatar, :name])
-
- with false <- user.local,
- avatar <- make_avatar_object(doc),
- bio <- string_from_xpath("//author[1]/summary", doc),
- name <- string_from_xpath("//author[1]/poco:displayName", doc),
- new_data <- %{
- avatar: avatar || old_data.avatar,
- name: name || old_data.name,
- bio: bio || old_data.bio
- },
- false <- new_data == old_data do
- change = Ecto.Changeset.change(user, new_data)
- User.update_and_set_cache(change)
- else
- _ ->
- {:ok, user}
- end
- end
-
- def find_make_or_update_actor(doc) do
- uri = string_from_xpath("//author/uri[1]", doc)
-
- with {:ok, %User{} = user} <- find_or_make_user(uri),
- {:ap_enabled, false} <- {:ap_enabled, User.ap_enabled?(user)} do
- maybe_update(doc, user)
- else
- {:ap_enabled, true} ->
- {:error, :invalid_protocol}
-
- _ ->
- {:error, :unknown_user}
- end
- end
-
- @spec find_or_make_user(String.t()) :: {:ok, User.t()}
- def find_or_make_user(uri) do
- case User.get_by_ap_id(uri) do
- %User{} = user -> {:ok, user}
- _ -> make_user(uri)
- end
- end
-
- @spec make_user(String.t(), boolean()) :: {:ok, User.t()} | {:error, any()}
- def make_user(uri, update \\ false) do
- with {:ok, info} <- gather_user_info(uri) do
- with false <- update,
- %User{} = user <- User.get_cached_by_ap_id(info["uri"]) do
- {:ok, user}
- else
- _e -> User.insert_or_update_user(build_user_data(info))
- end
- end
- end
-
- defp build_user_data(info) do
- %{
- name: info["name"],
- nickname: info["nickname"] <> "@" <> info["host"],
- ap_id: info["uri"],
- info: info,
- avatar: info["avatar"],
- bio: info["bio"]
- }
- end
-
- # TODO: Just takes the first one for now.
- def make_avatar_object(author_doc, rel \\ "avatar") do
- href = string_from_xpath("//author[1]/link[@rel=\"#{rel}\"]/@href", author_doc)
- type = string_from_xpath("//author[1]/link[@rel=\"#{rel}\"]/@type", author_doc)
-
- if href do
- %{
- "type" => "Image",
- "url" => [%{"type" => "Link", "mediaType" => type, "href" => href}]
- }
- else
- nil
- end
- end
-
- @spec gather_user_info(String.t()) :: {:ok, map()} | {:error, any()}
- def gather_user_info(username) do
- with {:ok, webfinger_data} <- WebFinger.finger(username),
- {:ok, feed_data} <- Websub.gather_feed_data(webfinger_data["topic"]) do
- data =
- webfinger_data
- |> Map.merge(feed_data)
- |> Map.put("fqn", username)
-
- {:ok, data}
- else
- e ->
- Logger.debug(fn -> "Couldn't gather info for #{username}" end)
- {:error, e}
- end
- end
-
- # Regex-based 'parsing' so we don't have to pull in a full html parser
- # It's a hack anyway. Maybe revisit this in the future
- @mastodon_regex ~r/<link href='(.*)' rel='alternate' type='application\/atom\+xml'>/
- @gs_regex ~r/<link title=.* href="(.*)" type="application\/atom\+xml" rel="alternate">/
- @gs_classic_regex ~r/<link rel="alternate" href="(.*)" type="application\/atom\+xml" title=.*>/
- def get_atom_url(body) do
- cond do
- Regex.match?(@mastodon_regex, body) ->
- [[_, match]] = Regex.scan(@mastodon_regex, body)
- {:ok, match}
-
- Regex.match?(@gs_regex, body) ->
- [[_, match]] = Regex.scan(@gs_regex, body)
- {:ok, match}
-
- Regex.match?(@gs_classic_regex, body) ->
- [[_, match]] = Regex.scan(@gs_classic_regex, body)
- {:ok, match}
-
- true ->
- Logger.debug(fn -> "Couldn't find Atom link in #{inspect(body)}" end)
- {:error, "Couldn't find the Atom link"}
- end
- end
-
- def fetch_activity_from_atom_url(url, options \\ []) do
- with true <- String.starts_with?(url, "http"),
- {:ok, %{body: body, status: code}} when code in 200..299 <-
- HTTP.get(url, [{:Accept, "application/atom+xml"}]) do
- Logger.debug("Got document from #{url}, handling...")
- handle_incoming(body, options)
- else
- e ->
- Logger.debug("Couldn't get #{url}: #{inspect(e)}")
- e
- end
- end
-
- def fetch_activity_from_html_url(url, options \\ []) do
- Logger.debug("Trying to fetch #{url}")
-
- with true <- String.starts_with?(url, "http"),
- {:ok, %{body: body}} <- HTTP.get(url, []),
- {:ok, atom_url} <- get_atom_url(body) do
- fetch_activity_from_atom_url(atom_url, options)
- else
- e ->
- Logger.debug("Couldn't get #{url}: #{inspect(e)}")
- e
- end
- end
-
- def fetch_activity_from_url(url, options \\ []) do
- with {:ok, [_ | _] = activities} <- fetch_activity_from_atom_url(url, options) do
- {:ok, activities}
- else
- _e -> fetch_activity_from_html_url(url, options)
- end
- rescue
- e ->
- Logger.debug("Couldn't get #{url}: #{inspect(e)}")
- {:error, "Couldn't get #{url}: #{inspect(e)}"}
- end
-end
diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex
index 20f2d9ddc..6958519de 100644
--- a/lib/pleroma/web/ostatus/ostatus_controller.ex
+++ b/lib/pleroma/web/ostatus/ostatus_controller.ex
@@ -13,19 +13,14 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Endpoint
- alias Pleroma.Web.Federator
alias Pleroma.Web.Metadata.PlayerView
- alias Pleroma.Web.OStatus.ActivityRepresenter
alias Pleroma.Web.Router
- alias Pleroma.Web.XML
plug(
Pleroma.Plugs.RateLimiter,
{:ap_routes, params: ["uuid"]} when action in [:object, :activity]
)
- plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])
-
plug(
Pleroma.Plugs.SetFormatPlug
when action in [:object, :activity, :notice]
@@ -33,32 +28,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do
action_fallback(:errors)
- defp decode_or_retry(body) do
- with {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
- {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
- {:ok, doc}
- else
- _e ->
- with [decoded | _] <- Pleroma.Web.Salmon.decode(body),
- doc <- XML.parse_document(decoded),
- uri when not is_nil(uri) <- XML.string_from_xpath("/entry/author[1]/uri", doc),
- {:ok, _} <- Pleroma.Web.OStatus.make_user(uri, true),
- {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
- {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
- {:ok, doc}
- end
- end
- end
-
- def salmon_incoming(conn, _) do
- {:ok, body, _conn} = read_body(conn)
- {:ok, doc} = decode_or_retry(body)
-
- Federator.incoming_doc(doc)
-
- send_resp(conn, 200, "")
- end
-
def object(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
when format in ["json", "activity+json"] do
ActivityPubController.call(conn, :object)
@@ -179,23 +148,10 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|> render("object.json", %{object: object})
end
- defp represent_activity(_conn, "activity+json", _, _) do
+ defp represent_activity(_conn, _, _, _) do
{:error, :not_found}
end
- defp represent_activity(conn, _, activity, user) do
- response =
- activity
- |> ActivityRepresenter.to_simple_form(user, true)
- |> ActivityRepresenter.wrap_with_entry()
- |> :xmerl.export_simple(:xmerl_xml)
- |> to_string
-
- conn
- |> put_resp_content_type("application/atom+xml")
- |> send_resp(200, response)
- end
-
def errors(conn, {:error, :not_found}) do
render_error(conn, :not_found, "Not found")
end
diff --git a/lib/pleroma/web/ostatus/user_representer.ex b/lib/pleroma/web/ostatus/user_representer.ex
deleted file mode 100644
index 852be6eb4..000000000
--- a/lib/pleroma/web/ostatus/user_representer.ex
+++ /dev/null
@@ -1,41 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OStatus.UserRepresenter do
- alias Pleroma.User
-
- def to_simple_form(user) do
- ap_id = to_charlist(user.ap_id)
- nickname = to_charlist(user.nickname)
- name = to_charlist(user.name)
- bio = to_charlist(user.bio)
- avatar_url = to_charlist(User.avatar_url(user))
-
- banner =
- if banner_url = User.banner_url(user) do
- [{:link, [rel: 'header', href: banner_url], []}]
- else
- []
- end
-
- ap_enabled =
- if user.local do
- [{:ap_enabled, ['true']}]
- else
- []
- end
-
- [
- {:id, [ap_id]},
- {:"activity:object", ['http://activitystrea.ms/schema/1.0/person']},
- {:uri, [ap_id]},
- {:"poco:preferredUsername", [nickname]},
- {:"poco:displayName", [name]},
- {:"poco:note", [bio]},
- {:summary, [bio]},
- {:name, [nickname]},
- {:link, [rel: 'avatar', href: avatar_url], []}
- ] ++ banner ++ ap_enabled
- end
-end
diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
index 9012e2175..db6faac83 100644
--- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
@@ -80,9 +80,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
@doc "PATCH /api/v1/pleroma/accounts/update_banner"
def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
- new_info = %{"banner" => %{}}
-
- with {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
+ with {:ok, user} <- User.update_banner(user, %{}) do
CommonAPI.update(user)
json(conn, %{url: nil})
end
@@ -90,8 +88,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
def update_banner(%{assigns: %{user: user}} = conn, params) do
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
- new_info <- %{"banner" => object.data},
- {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
+ {:ok, user} <- User.update_banner(user, object.data) do
CommonAPI.update(user)
%{"url" => [%{"href" => href} | _]} = object.data
@@ -101,17 +98,14 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
@doc "PATCH /api/v1/pleroma/accounts/update_background"
def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
- new_info = %{"background" => %{}}
-
- with {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
+ with {:ok, _user} <- User.update_background(user, %{}) do
json(conn, %{url: nil})
end
end
def update_background(%{assigns: %{user: user}} = conn, params) do
with {:ok, object} <- ActivityPub.upload(params, type: :background),
- new_info <- %{"background" => object.data},
- {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
+ {:ok, _user} <- User.update_background(user, object.data) do
%{"url" => [%{"href" => href} | _]} = object.data
json(conn, %{url: href})
@@ -119,7 +113,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
end
@doc "GET /api/v1/pleroma/accounts/:id/favourites"
- def favourites(%{assigns: %{account: %{info: %{hide_favorites: true}}}} = conn, _params) do
+ def favourites(%{assigns: %{account: %{hide_favorites: true}}} = conn, _params) do
render_error(conn, :forbidden, "Can't get favorites")
end
@@ -132,7 +126,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
recipients =
if for_user do
- [Pleroma.Constants.as_public()] ++ [for_user.ap_id | for_user.following]
+ [Pleroma.Constants.as_public()] ++ [for_user.ap_id | User.following(for_user)]
else
[Pleroma.Constants.as_public()]
end
diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
index d71d72dd5..8cf552b7e 100644
--- a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
@@ -24,9 +24,7 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do
with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)),
# Reject if not an image
%{type: "image"} = attachment <- render_attachment(object) do
- # Sure!
- # Save to the user's info
- {:ok, _user} = User.update_info(user, &User.Info.mascot_update(&1, attachment))
+ {:ok, _user} = User.mascot_update(user, attachment)
json(conn, attachment)
else
diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
index 9d50a7ca9..651a99423 100644
--- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
@@ -79,6 +79,15 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
end
end
+ def read_conversations(%{assigns: %{user: user}} = conn, _params) do
+ with {:ok, _, participations} <- Participation.mark_all_as_read(user) do
+ conn
+ |> add_link_headers(participations)
+ |> put_view(ConversationView)
+ |> render("participations.json", participations: participations, for: user)
+ end
+ end
+
def read_notification(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
with {:ok, notification} <- Notification.read_one(user, notification_id) do
conn
diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex
index 35d3ff07c..dd445e8bf 100644
--- a/lib/pleroma/web/push/impl.ex
+++ b/lib/pleroma/web/push/impl.ex
@@ -125,6 +125,10 @@ defmodule Pleroma.Web.Push.Impl do
end
end
+ def format_title(%{activity: %{data: %{"directMessage" => true}}}) do
+ "New Direct Message"
+ end
+
def format_title(%{activity: %{data: %{"type" => type}}}) do
case type do
"Create" -> "New Mention"
diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex
index d376e2069..16b1a53d2 100644
--- a/lib/pleroma/web/rel_me.ex
+++ b/lib/pleroma/web/rel_me.ex
@@ -25,13 +25,13 @@ defmodule Pleroma.Web.RelMe do
def parse(_), do: {:error, "No URL provided"}
defp parse_url(url) do
- {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options)
-
- data =
- Floki.attribute(html, "link[rel~=me]", "href") ++
- Floki.attribute(html, "a[rel~=me]", "href")
-
- {:ok, data}
+ with {:ok, %Tesla.Env{body: html, status: status}} when status in 200..299 <-
+ Pleroma.HTTP.get(url, [], adapter: @hackney_options),
+ data <-
+ Floki.attribute(html, "link[rel~=me]", "href") ++
+ Floki.attribute(html, "a[rel~=me]", "href") do
+ {:ok, data}
+ end
rescue
e -> {:error, "Parsing error: #{inspect(e)}"}
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index b974579fb..7b9e9b1b7 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -137,11 +137,14 @@ defmodule Pleroma.Web.Router do
delete("/users", AdminAPIController, :user_delete)
post("/users", AdminAPIController, :users_create)
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
+ patch("/users/activate", AdminAPIController, :user_activate)
+ patch("/users/deactivate", AdminAPIController, :user_deactivate)
put("/users/tag", AdminAPIController, :tag_users)
delete("/users/tag", AdminAPIController, :untag_users)
get("/users/:nickname/permission_group", AdminAPIController, :right_get)
get("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_get)
+
post("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_add)
delete(
@@ -150,8 +153,15 @@ defmodule Pleroma.Web.Router do
:right_delete
)
- put("/users/:nickname/activation_status", AdminAPIController, :set_activation_status)
+ post("/users/permission_group/:permission_group", AdminAPIController, :right_add_multiple)
+
+ delete(
+ "/users/permission_group/:permission_group",
+ AdminAPIController,
+ :right_delete_multiple
+ )
+ get("/relay", AdminAPIController, :relay_list)
post("/relay", AdminAPIController, :relay_follow)
delete("/relay", AdminAPIController, :relay_unfollow)
@@ -257,6 +267,7 @@ defmodule Pleroma.Web.Router do
get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses)
get("/conversations/:id", PleromaAPIController, :conversation)
+ post("/conversations/read", PleromaAPIController, :read_conversations)
end
scope [] do
@@ -395,6 +406,9 @@ defmodule Pleroma.Web.Router do
get("/push/subscription", SubscriptionController, :get)
put("/push/subscription", SubscriptionController, :update)
delete("/push/subscription", SubscriptionController, :delete)
+
+ get("/markers", MarkerController, :index)
+ post("/markers", MarkerController, :upsert)
end
scope "/api/web", Pleroma.Web do
@@ -500,11 +514,6 @@ defmodule Pleroma.Web.Router do
get("/users/:nickname/feed", Feed.FeedController, :feed)
get("/users/:nickname", Feed.FeedController, :feed_redirect)
- post("/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming)
- post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request)
- get("/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation)
- post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)
-
get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe)
end
@@ -588,6 +597,12 @@ defmodule Pleroma.Web.Router do
end
scope "/", Pleroma.Web do
+ pipe_through(:api)
+
+ get("/web/manifest.json", MastoFEController, :manifest)
+ end
+
+ scope "/", Pleroma.Web do
pipe_through(:mastodon_html)
get("/web/login", MastodonAPI.AuthController, :login)
diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex
deleted file mode 100644
index 0ffe903cd..000000000
--- a/lib/pleroma/web/salmon/salmon.ex
+++ /dev/null
@@ -1,254 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Salmon do
- @behaviour Pleroma.Web.Federator.Publisher
-
- use Bitwise
-
- alias Pleroma.Activity
- alias Pleroma.HTTP
- alias Pleroma.Instances
- alias Pleroma.Keys
- alias Pleroma.User
- alias Pleroma.Web.ActivityPub.Visibility
- alias Pleroma.Web.Federator.Publisher
- alias Pleroma.Web.OStatus
- alias Pleroma.Web.OStatus.ActivityRepresenter
- alias Pleroma.Web.XML
-
- require Logger
-
- def decode(salmon) do
- doc = XML.parse_document(salmon)
-
- {:xmlObj, :string, data} = :xmerl_xpath.string('string(//me:data[1])', doc)
- {:xmlObj, :string, sig} = :xmerl_xpath.string('string(//me:sig[1])', doc)
- {:xmlObj, :string, alg} = :xmerl_xpath.string('string(//me:alg[1])', doc)
- {:xmlObj, :string, encoding} = :xmerl_xpath.string('string(//me:encoding[1])', doc)
- {:xmlObj, :string, type} = :xmerl_xpath.string('string(//me:data[1]/@type)', doc)
-
- {:ok, data} = Base.url_decode64(to_string(data), ignore: :whitespace)
- {:ok, sig} = Base.url_decode64(to_string(sig), ignore: :whitespace)
- alg = to_string(alg)
- encoding = to_string(encoding)
- type = to_string(type)
-
- [data, type, encoding, alg, sig]
- end
-
- def fetch_magic_key(salmon) do
- with [data, _, _, _, _] <- decode(salmon),
- doc <- XML.parse_document(data),
- uri when not is_nil(uri) <- XML.string_from_xpath("/entry/author[1]/uri", doc),
- {:ok, public_key} <- User.get_public_key_for_ap_id(uri),
- magic_key <- encode_key(public_key) do
- {:ok, magic_key}
- end
- end
-
- def decode_and_validate(magickey, salmon) do
- [data, type, encoding, alg, sig] = decode(salmon)
-
- signed_text =
- [data, type, encoding, alg]
- |> Enum.map(&Base.url_encode64/1)
- |> Enum.join(".")
-
- key = decode_key(magickey)
-
- verify = :public_key.verify(signed_text, :sha256, sig, key)
-
- if verify do
- {:ok, data}
- else
- :error
- end
- end
-
- def decode_key("RSA." <> magickey) do
- make_integer = fn bin ->
- list = :erlang.binary_to_list(bin)
- Enum.reduce(list, 0, fn el, acc -> acc <<< 8 ||| el end)
- end
-
- [modulus, exponent] =
- magickey
- |> String.split(".")
- |> Enum.map(fn n -> Base.url_decode64!(n, padding: false) end)
- |> Enum.map(make_integer)
-
- {:RSAPublicKey, modulus, exponent}
- end
-
- def encode_key({:RSAPublicKey, modulus, exponent}) do
- modulus_enc = :binary.encode_unsigned(modulus) |> Base.url_encode64()
- exponent_enc = :binary.encode_unsigned(exponent) |> Base.url_encode64()
-
- "RSA.#{modulus_enc}.#{exponent_enc}"
- end
-
- def encode(private_key, doc) do
- type = "application/atom+xml"
- encoding = "base64url"
- alg = "RSA-SHA256"
-
- signed_text =
- [doc, type, encoding, alg]
- |> Enum.map(&Base.url_encode64/1)
- |> Enum.join(".")
-
- signature =
- signed_text
- |> :public_key.sign(:sha256, private_key)
- |> to_string
- |> Base.url_encode64()
-
- doc_base64 =
- doc
- |> Base.url_encode64()
-
- # Don't need proper xml building, these strings are safe to leave unescaped
- salmon = """
- <?xml version="1.0" encoding="UTF-8"?>
- <me:env xmlns:me="http://salmon-protocol.org/ns/magic-env">
- <me:data type="application/atom+xml">#{doc_base64}</me:data>
- <me:encoding>#{encoding}</me:encoding>
- <me:alg>#{alg}</me:alg>
- <me:sig>#{signature}</me:sig>
- </me:env>
- """
-
- {:ok, salmon}
- end
-
- def remote_users(%User{id: user_id}, %{data: %{"to" => to} = data}) do
- cc = Map.get(data, "cc", [])
-
- bcc =
- data
- |> Map.get("bcc", [])
- |> Enum.reduce([], fn ap_id, bcc ->
- case Pleroma.List.get_by_ap_id(ap_id) do
- %Pleroma.List{user_id: ^user_id} = list ->
- {:ok, following} = Pleroma.List.get_following(list)
- bcc ++ Enum.map(following, & &1.ap_id)
-
- _ ->
- bcc
- end
- end)
-
- [to, cc, bcc]
- |> Enum.concat()
- |> Enum.map(&User.get_cached_by_ap_id/1)
- |> Enum.filter(fn user -> user && !user.local end)
- end
-
- @doc "Pushes an activity to remote account."
- def publish_one(%{recipient: %{info: %{salmon: salmon}}} = params),
- do: publish_one(Map.put(params, :recipient, salmon))
-
- def publish_one(%{recipient: url, feed: feed} = params) when is_binary(url) do
- with {:ok, %{status: code}} when code in 200..299 <-
- HTTP.post(
- url,
- feed,
- [{"Content-Type", "application/magic-envelope+xml"}]
- ) do
- if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
- do: Instances.set_reachable(url)
-
- Logger.debug(fn -> "Pushed to #{url}, code #{code}" end)
- {:ok, code}
- else
- e ->
- unless params[:unreachable_since], do: Instances.set_reachable(url)
- Logger.debug(fn -> "Pushing Salmon to #{url} failed, #{inspect(e)}" end)
- {:error, "Unreachable instance"}
- end
- end
-
- def publish_one(%{recipient_id: recipient_id} = params) do
- recipient = User.get_cached_by_id(recipient_id)
-
- params
- |> Map.delete(:recipient_id)
- |> Map.put(:recipient, recipient)
- |> publish_one()
- end
-
- def publish_one(_), do: :noop
-
- @supported_activities [
- "Create",
- "Follow",
- "Like",
- "Announce",
- "Undo",
- "Delete"
- ]
-
- def is_representable?(%Activity{data: %{"type" => type}} = activity)
- when type in @supported_activities,
- do: Visibility.is_public?(activity)
-
- def is_representable?(_), do: false
-
- @doc """
- Publishes an activity to remote accounts
- """
- @spec publish(User.t(), Pleroma.Activity.t()) :: none
- def publish(user, activity)
-
- def publish(%{keys: keys} = user, %{data: %{"type" => type}} = activity)
- when type in @supported_activities do
- feed = ActivityRepresenter.to_simple_form(activity, user, true)
-
- if feed do
- feed =
- ActivityRepresenter.wrap_with_entry(feed)
- |> :xmerl.export_simple(:xmerl_xml)
- |> to_string
-
- {:ok, private, _} = Keys.keys_from_pem(keys)
- {:ok, feed} = encode(private, feed)
-
- remote_users = remote_users(user, activity)
-
- salmon_urls = Enum.map(remote_users, & &1.info.salmon)
- reachable_urls_metadata = Instances.filter_reachable(salmon_urls)
- reachable_urls = Map.keys(reachable_urls_metadata)
-
- remote_users
- |> Enum.filter(&(&1.info.salmon in reachable_urls))
- |> Enum.each(fn remote_user ->
- Logger.debug(fn -> "Sending Salmon to #{remote_user.ap_id}" end)
-
- Publisher.enqueue_one(__MODULE__, %{
- recipient_id: remote_user.id,
- feed: feed,
- unreachable_since: reachable_urls_metadata[remote_user.info.salmon]
- })
- end)
- end
- end
-
- def publish(%{id: id}, _), do: Logger.debug(fn -> "Keys missing for user #{id}" end)
-
- def gather_webfinger_links(%User{} = user) do
- {:ok, _private, public} = Keys.keys_from_pem(user.keys)
- magic_key = encode_key(public)
-
- [
- %{"rel" => "salmon", "href" => OStatus.salmon_path(user)},
- %{
- "rel" => "magic-public-key",
- "href" => "data:application/magic-public-key,#{magic_key}"
- }
- ]
- end
-
- def gather_nodeinfo_protocol_names, do: []
-end
diff --git a/lib/pleroma/web/streamer/streamer.ex b/lib/pleroma/web/streamer/streamer.ex
index 8cf719277..2fc7ac8cf 100644
--- a/lib/pleroma/web/streamer/streamer.ex
+++ b/lib/pleroma/web/streamer/streamer.ex
@@ -49,7 +49,7 @@ defmodule Pleroma.Web.Streamer do
end
end
- defp handle_should_send(_) do
- true
- end
+ defp handle_should_send(:benchmark), do: false
+
+ defp handle_should_send(_), do: true
end
diff --git a/lib/pleroma/web/streamer/worker.ex b/lib/pleroma/web/streamer/worker.ex
index 0ea224874..33b24840d 100644
--- a/lib/pleroma/web/streamer/worker.ex
+++ b/lib/pleroma/web/streamer/worker.ex
@@ -129,14 +129,14 @@ defmodule Pleroma.Web.Streamer.Worker do
end
defp should_send?(%User{} = user, %Activity{} = item) do
- blocks = user.info.blocks || []
- mutes = user.info.mutes || []
- reblog_mutes = user.info.muted_reblogs || []
+ blocks = user.blocks || []
+ mutes = user.mutes || []
+ reblog_mutes = user.muted_reblogs || []
recipient_blocks = MapSet.new(blocks ++ mutes)
recipients = MapSet.new(item.recipients)
- domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks)
+ domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
- with parent when not is_nil(parent) <- Object.normalize(item),
+ with parent <- Object.normalize(item) || item,
true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)),
true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)),
true <- MapSet.disjoint?(recipients, recipient_blocks),
@@ -212,7 +212,7 @@ defmodule Pleroma.Web.Streamer.Worker do
end
@spec thread_containment(Activity.t(), User.t()) :: boolean()
- defp thread_containment(_activity, %User{info: %{skip_thread_containment: true}}), do: true
+ defp thread_containment(_activity, %User{skip_thread_containment: true}), do: true
defp thread_containment(activity, user) do
if Config.get([:instance, :skip_thread_containment]) do
diff --git a/lib/pleroma/web/templates/feed/feed/feed.xml.eex b/lib/pleroma/web/templates/feed/feed/feed.xml.eex
index fbfdc46b5..45df9dc09 100644
--- a/lib/pleroma/web/templates/feed/feed/feed.xml.eex
+++ b/lib/pleroma/web/templates/feed/feed/feed.xml.eex
@@ -10,8 +10,6 @@
<title><%= @user.nickname <> "'s timeline" %></title>
<updated><%= most_recent_update(@activities, @user) %></updated>
<logo><%= logo(@user) %></logo>
- <link rel="hub" href="<%= websub_url(@conn, :websub_subscription_request, @user.nickname) %>"/>
- <link rel="salmon" href="<%= o_status_url(@conn, :salmon_incoming, @user.nickname) %>"/>
<link rel="self" href="<%= '#{feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/>
<%= render @view_module, "_author.xml", assigns %>
diff --git a/lib/pleroma/web/templates/masto_fe/index.html.eex b/lib/pleroma/web/templates/masto_fe/index.html.eex
index feff36fae..c330960fa 100644
--- a/lib/pleroma/web/templates/masto_fe/index.html.eex
+++ b/lib/pleroma/web/templates/masto_fe/index.html.eex
@@ -4,9 +4,13 @@
<meta charset='utf-8'>
<meta content='width=device-width, initial-scale=1' name='viewport'>
<title>
-<%= Pleroma.Config.get([:instance, :name]) %>
+<%= Config.get([:instance, :name]) %>
</title>
<link rel="icon" type="image/png" href="/favicon.png"/>
+<link rel="manifest" type="applicaton/manifest+json" href="<%= masto_fe_path(Pleroma.Web.Endpoint, :manifest) %>" />
+
+<meta name="theme-color" content="<%= Config.get([:manifest, :theme_color]) %>" />
+
<script crossorigin='anonymous' src="/packs/locales.js"></script>
<script crossorigin='anonymous' src="/packs/locales/glitch/en.js"></script>
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
index bf5a6ae42..39f10c49f 100644
--- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
@@ -20,11 +20,12 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
action_fallback(:errors)
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
- new_info = [need_confirmation: false]
-
- with %User{info: info} = user <- User.get_cached_by_id(uid),
- true <- user.local and info.confirmation_pending and info.confirmation_token == token,
- {:ok, _} <- User.update_info(user, &User.Info.confirmation_changeset(&1, new_info)) do
+ with %User{} = user <- User.get_cached_by_id(uid),
+ true <- user.local and user.confirmation_pending and user.confirmation_token == token,
+ {:ok, _} <-
+ user
+ |> User.confirmation_changeset(need_confirmation: false)
+ |> User.update_and_set_cache() do
redirect(conn, to: "/")
end
end
diff --git a/lib/pleroma/web/views/masto_fe_view.ex b/lib/pleroma/web/views/masto_fe_view.ex
index 21b086d4c..c39b7f095 100644
--- a/lib/pleroma/web/views/masto_fe_view.ex
+++ b/lib/pleroma/web/views/masto_fe_view.ex
@@ -61,12 +61,12 @@ defmodule Pleroma.Web.MastoFEView do
},
poll_limits: Config.get([:instance, :poll_limits]),
rights: %{
- delete_others_notice: present?(user.info.is_moderator),
- admin: present?(user.info.is_admin)
+ delete_others_notice: present?(user.is_moderator),
+ admin: present?(user.is_admin)
},
compose: %{
me: "#{user.id}",
- default_privacy: user.info.default_scope,
+ default_privacy: user.default_scope,
default_sensitive: false,
allow_content_types: Config.get([:instance, :allowed_post_formats])
},
@@ -86,7 +86,7 @@ defmodule Pleroma.Web.MastoFEView do
"video\/mp4"
]
},
- settings: user.info.settings || @default_settings,
+ settings: user.settings || @default_settings,
push_subscription: nil,
accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},
custom_emojis: render(CustomEmojiView, "index.json", custom_emojis: custom_emojis),
@@ -99,4 +99,23 @@ defmodule Pleroma.Web.MastoFEView do
defp present?(nil), do: false
defp present?(false), do: false
defp present?(_), do: true
+
+ def render("manifest.json", _params) do
+ %{
+ name: Config.get([:instance, :name]),
+ description: Config.get([:instance, :description]),
+ icons: Config.get([:manifest, :icons]),
+ theme_color: Config.get([:manifest, :theme_color]),
+ background_color: Config.get([:manifest, :background_color]),
+ display: "standalone",
+ scope: Pleroma.Web.base_url(),
+ start_url: masto_fe_path(Pleroma.Web.Endpoint, :index, ["getting-started"]),
+ categories: [
+ "social"
+ ],
+ serviceworker: %{
+ src: "/sw.js"
+ }
+ }
+ end
end
diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex
index ecb39ee50..b4cc80179 100644
--- a/lib/pleroma/web/web_finger/web_finger.ex
+++ b/lib/pleroma/web/web_finger/web_finger.ex
@@ -108,7 +108,6 @@ defmodule Pleroma.Web.WebFinger do
doc
),
subject <- XML.string_from_xpath("//Subject", doc),
- salmon <- XML.string_from_xpath(~s{//Link[@rel="salmon"]/@href}, doc),
subscribe_address <-
XML.string_from_xpath(
~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template},
@@ -123,7 +122,6 @@ defmodule Pleroma.Web.WebFinger do
"magic_key" => magic_key,
"topic" => topic,
"subject" => subject,
- "salmon" => salmon,
"subscribe_address" => subscribe_address,
"ap_id" => ap_id
}
@@ -148,16 +146,6 @@ defmodule Pleroma.Web.WebFinger do
{"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "self"} ->
Map.put(data, "ap_id", link["href"])
- {_, "magic-public-key"} ->
- "data:application/magic-public-key," <> magic_key = link["href"]
- Map.put(data, "magic_key", magic_key)
-
- {"application/atom+xml", "http://schemas.google.com/g/2010#updates-from"} ->
- Map.put(data, "topic", link["href"])
-
- {_, "salmon"} ->
- Map.put(data, "salmon", link["href"])
-
{_, "http://ostatus.org/schema/1.0/subscribe"} ->
Map.put(data, "subscribe_address", link["template"])
diff --git a/lib/pleroma/web/websub/websub.ex b/lib/pleroma/web/websub/websub.ex
deleted file mode 100644
index b61f388b8..000000000
--- a/lib/pleroma/web/websub/websub.ex
+++ /dev/null
@@ -1,332 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Websub do
- alias Ecto.Changeset
- alias Pleroma.Activity
- alias Pleroma.HTTP
- alias Pleroma.Instances
- alias Pleroma.Repo
- alias Pleroma.User
- alias Pleroma.Web.ActivityPub.Visibility
- alias Pleroma.Web.Endpoint
- alias Pleroma.Web.Federator
- alias Pleroma.Web.Federator.Publisher
- alias Pleroma.Web.OStatus
- alias Pleroma.Web.OStatus.FeedRepresenter
- alias Pleroma.Web.Router.Helpers
- alias Pleroma.Web.Websub.WebsubClientSubscription
- alias Pleroma.Web.Websub.WebsubServerSubscription
- alias Pleroma.Web.XML
- require Logger
-
- import Ecto.Query
-
- @behaviour Pleroma.Web.Federator.Publisher
-
- def verify(subscription, getter \\ &HTTP.get/3) do
- challenge = Base.encode16(:crypto.strong_rand_bytes(8))
- lease_seconds = NaiveDateTime.diff(subscription.valid_until, subscription.updated_at)
- lease_seconds = lease_seconds |> to_string
-
- params = %{
- "hub.challenge": challenge,
- "hub.lease_seconds": lease_seconds,
- "hub.topic": subscription.topic,
- "hub.mode": "subscribe"
- }
-
- url = hd(String.split(subscription.callback, "?"))
- query = URI.parse(subscription.callback).query || ""
- params = Map.merge(params, URI.decode_query(query))
-
- with {:ok, response} <- getter.(url, [], params: params),
- ^challenge <- response.body do
- changeset = Changeset.change(subscription, %{state: "active"})
- Repo.update(changeset)
- else
- e ->
- Logger.debug("Couldn't verify subscription")
- Logger.debug(inspect(e))
- {:error, subscription}
- end
- end
-
- @supported_activities [
- "Create",
- "Follow",
- "Like",
- "Announce",
- "Undo",
- "Delete"
- ]
-
- def is_representable?(%Activity{data: %{"type" => type}} = activity)
- when type in @supported_activities,
- do: Visibility.is_public?(activity)
-
- def is_representable?(_), do: false
-
- def publish(topic, user, %{data: %{"type" => type}} = activity)
- when type in @supported_activities do
- response =
- user
- |> FeedRepresenter.to_simple_form([activity], [user])
- |> :xmerl.export_simple(:xmerl_xml)
- |> to_string
-
- query =
- from(
- sub in WebsubServerSubscription,
- where: sub.topic == ^topic and sub.state == "active",
- where: fragment("? > (NOW() at time zone 'UTC')", sub.valid_until)
- )
-
- subscriptions = Repo.all(query)
-
- callbacks = Enum.map(subscriptions, & &1.callback)
- reachable_callbacks_metadata = Instances.filter_reachable(callbacks)
- reachable_callbacks = Map.keys(reachable_callbacks_metadata)
-
- subscriptions
- |> Enum.filter(&(&1.callback in reachable_callbacks))
- |> Enum.each(fn sub ->
- data = %{
- xml: response,
- topic: topic,
- callback: sub.callback,
- secret: sub.secret,
- unreachable_since: reachable_callbacks_metadata[sub.callback]
- }
-
- Publisher.enqueue_one(__MODULE__, data)
- end)
- end
-
- def publish(_, _, _), do: ""
-
- def publish(actor, activity), do: publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
-
- def sign(secret, doc) do
- :crypto.hmac(:sha, secret, to_string(doc)) |> Base.encode16() |> String.downcase()
- end
-
- def incoming_subscription_request(user, %{"hub.mode" => "subscribe"} = params) do
- with {:ok, topic} <- valid_topic(params, user),
- {:ok, lease_time} <- lease_time(params),
- secret <- params["hub.secret"],
- callback <- params["hub.callback"] do
- subscription = get_subscription(topic, callback)
-
- data = %{
- state: subscription.state || "requested",
- topic: topic,
- secret: secret,
- callback: callback
- }
-
- change = Changeset.change(subscription, data)
- websub = Repo.insert_or_update!(change)
-
- change =
- Changeset.change(websub, %{valid_until: NaiveDateTime.add(websub.updated_at, lease_time)})
-
- websub = Repo.update!(change)
-
- Federator.verify_websub(websub)
-
- {:ok, websub}
- else
- {:error, reason} ->
- Logger.debug("Couldn't create subscription")
- Logger.debug(inspect(reason))
-
- {:error, reason}
- end
- end
-
- def incoming_subscription_request(user, params) do
- Logger.info("Unhandled WebSub request for #{user.nickname}: #{inspect(params)}")
-
- {:error, "Invalid WebSub request"}
- end
-
- defp get_subscription(topic, callback) do
- Repo.get_by(WebsubServerSubscription, topic: topic, callback: callback) ||
- %WebsubServerSubscription{}
- end
-
- # Temp hack for mastodon.
- defp lease_time(%{"hub.lease_seconds" => ""}) do
- # three days
- {:ok, 60 * 60 * 24 * 3}
- end
-
- defp lease_time(%{"hub.lease_seconds" => lease_seconds}) do
- {:ok, String.to_integer(lease_seconds)}
- end
-
- defp lease_time(_) do
- # three days
- {:ok, 60 * 60 * 24 * 3}
- end
-
- defp valid_topic(%{"hub.topic" => topic}, user) do
- if topic == OStatus.feed_path(user) do
- {:ok, OStatus.feed_path(user)}
- else
- {:error, "Wrong topic requested, expected #{OStatus.feed_path(user)}, got #{topic}"}
- end
- end
-
- def subscribe(subscriber, subscribed, requester \\ &request_subscription/1) do
- topic = subscribed.info.topic
- # FIXME: Race condition, use transactions
- {:ok, subscription} =
- with subscription when not is_nil(subscription) <-
- Repo.get_by(WebsubClientSubscription, topic: topic) do
- subscribers = [subscriber.ap_id | subscription.subscribers] |> Enum.uniq()
- change = Ecto.Changeset.change(subscription, %{subscribers: subscribers})
- Repo.update(change)
- else
- _e ->
- subscription = %WebsubClientSubscription{
- topic: topic,
- hub: subscribed.info.hub,
- subscribers: [subscriber.ap_id],
- state: "requested",
- secret: :crypto.strong_rand_bytes(8) |> Base.url_encode64(),
- user: subscribed
- }
-
- Repo.insert(subscription)
- end
-
- requester.(subscription)
- end
-
- def gather_feed_data(topic, getter \\ &HTTP.get/1) do
- with {:ok, response} <- getter.(topic),
- status when status in 200..299 <- response.status,
- body <- response.body,
- doc <- XML.parse_document(body),
- uri when not is_nil(uri) <- XML.string_from_xpath("/feed/author[1]/uri", doc),
- hub when not is_nil(hub) <- XML.string_from_xpath(~S{/feed/link[@rel="hub"]/@href}, doc) do
- name = XML.string_from_xpath("/feed/author[1]/name", doc)
- preferred_username = XML.string_from_xpath("/feed/author[1]/poco:preferredUsername", doc)
- display_name = XML.string_from_xpath("/feed/author[1]/poco:displayName", doc)
- avatar = OStatus.make_avatar_object(doc)
- bio = XML.string_from_xpath("/feed/author[1]/summary", doc)
-
- {:ok,
- %{
- "uri" => uri,
- "hub" => hub,
- "nickname" => preferred_username || name,
- "name" => display_name || name,
- "host" => URI.parse(uri).host,
- "avatar" => avatar,
- "bio" => bio
- }}
- else
- e ->
- {:error, e}
- end
- end
-
- def request_subscription(websub, poster \\ &HTTP.post/3, timeout \\ 10_000) do
- data = [
- "hub.mode": "subscribe",
- "hub.topic": websub.topic,
- "hub.secret": websub.secret,
- "hub.callback": Helpers.websub_url(Endpoint, :websub_subscription_confirmation, websub.id)
- ]
-
- # This checks once a second if we are confirmed yet
- websub_checker = fn ->
- helper = fn helper ->
- :timer.sleep(1000)
- websub = Repo.get_by(WebsubClientSubscription, id: websub.id, state: "accepted")
- if websub, do: websub, else: helper.(helper)
- end
-
- helper.(helper)
- end
-
- task = Task.async(websub_checker)
-
- with {:ok, %{status: 202}} <-
- poster.(websub.hub, {:form, data}, "Content-type": "application/x-www-form-urlencoded"),
- {:ok, websub} <- Task.yield(task, timeout) do
- {:ok, websub}
- else
- e ->
- Task.shutdown(task)
-
- change = Ecto.Changeset.change(websub, %{state: "rejected"})
- {:ok, websub} = Repo.update(change)
-
- Logger.debug(fn -> "Couldn't confirm subscription: #{inspect(websub)}" end)
- Logger.debug(fn -> "error: #{inspect(e)}" end)
-
- {:error, websub}
- end
- end
-
- def refresh_subscriptions(delta \\ 60 * 60 * 24) do
- Logger.debug("Refreshing subscriptions")
-
- cut_off = NaiveDateTime.add(NaiveDateTime.utc_now(), delta)
-
- query = from(sub in WebsubClientSubscription, where: sub.valid_until < ^cut_off)
-
- subs = Repo.all(query)
-
- Enum.each(subs, fn sub ->
- Federator.request_subscription(sub)
- end)
- end
-
- def publish_one(%{xml: xml, topic: topic, callback: callback, secret: secret} = params) do
- signature = sign(secret || "", xml)
- Logger.info(fn -> "Pushing #{topic} to #{callback}" end)
-
- with {:ok, %{status: code}} when code in 200..299 <-
- HTTP.post(
- callback,
- xml,
- [
- {"Content-Type", "application/atom+xml"},
- {"X-Hub-Signature", "sha1=#{signature}"}
- ]
- ) do
- if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
- do: Instances.set_reachable(callback)
-
- Logger.info(fn -> "Pushed to #{callback}, code #{code}" end)
- {:ok, code}
- else
- {_post_result, response} ->
- unless params[:unreachable_since], do: Instances.set_reachable(callback)
- Logger.debug(fn -> "Couldn't push to #{callback}, #{inspect(response)}" end)
- {:error, response}
- end
- end
-
- def gather_webfinger_links(%User{} = user) do
- [
- %{
- "rel" => "http://schemas.google.com/g/2010#updates-from",
- "type" => "application/atom+xml",
- "href" => OStatus.feed_path(user)
- },
- %{
- "rel" => "http://ostatus.org/schema/1.0/subscribe",
- "template" => OStatus.remote_follow_path()
- }
- ]
- end
-
- def gather_nodeinfo_protocol_names, do: ["ostatus"]
-end
diff --git a/lib/pleroma/web/websub/websub_client_subscription.ex b/lib/pleroma/web/websub/websub_client_subscription.ex
deleted file mode 100644
index 23a04b87d..000000000
--- a/lib/pleroma/web/websub/websub_client_subscription.ex
+++ /dev/null
@@ -1,20 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Websub.WebsubClientSubscription do
- use Ecto.Schema
- alias Pleroma.User
-
- schema "websub_client_subscriptions" do
- field(:topic, :string)
- field(:secret, :string)
- field(:valid_until, :naive_datetime_usec)
- field(:state, :string)
- field(:subscribers, {:array, :string}, default: [])
- field(:hub, :string)
- belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
-
- timestamps()
- end
-end
diff --git a/lib/pleroma/web/websub/websub_controller.ex b/lib/pleroma/web/websub/websub_controller.ex
deleted file mode 100644
index 9e8b48b80..000000000
--- a/lib/pleroma/web/websub/websub_controller.ex
+++ /dev/null
@@ -1,99 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Websub.WebsubController do
- use Pleroma.Web, :controller
-
- alias Pleroma.Repo
- alias Pleroma.User
- alias Pleroma.Web.Federator
- alias Pleroma.Web.Websub
- alias Pleroma.Web.Websub.WebsubClientSubscription
-
- require Logger
-
- plug(
- Pleroma.Web.FederatingPlug
- when action in [
- :websub_subscription_request,
- :websub_subscription_confirmation,
- :websub_incoming
- ]
- )
-
- def websub_subscription_request(conn, %{"nickname" => nickname} = params) do
- user = User.get_cached_by_nickname(nickname)
-
- with {:ok, _websub} <- Websub.incoming_subscription_request(user, params) do
- conn
- |> send_resp(202, "Accepted")
- else
- {:error, reason} ->
- conn
- |> send_resp(500, reason)
- end
- end
-
- # TODO: Extract this into the Websub module
- def websub_subscription_confirmation(
- conn,
- %{
- "id" => id,
- "hub.mode" => "subscribe",
- "hub.challenge" => challenge,
- "hub.topic" => topic
- } = params
- ) do
- Logger.debug("Got WebSub confirmation")
- Logger.debug(inspect(params))
-
- lease_seconds =
- if params["hub.lease_seconds"] do
- String.to_integer(params["hub.lease_seconds"])
- else
- # Guess 3 days
- 60 * 60 * 24 * 3
- end
-
- with %WebsubClientSubscription{} = websub <-
- Repo.get_by(WebsubClientSubscription, id: id, topic: topic) do
- valid_until = NaiveDateTime.add(NaiveDateTime.utc_now(), lease_seconds)
- change = Ecto.Changeset.change(websub, %{state: "accepted", valid_until: valid_until})
- {:ok, _websub} = Repo.update(change)
-
- conn
- |> send_resp(200, challenge)
- else
- _e ->
- conn
- |> send_resp(500, "Error")
- end
- end
-
- def websub_subscription_confirmation(conn, params) do
- Logger.info("Invalid WebSub confirmation request: #{inspect(params)}")
-
- conn
- |> send_resp(500, "Invalid parameters")
- end
-
- def websub_incoming(conn, %{"id" => id}) do
- with "sha1=" <> signature <- hd(get_req_header(conn, "x-hub-signature")),
- signature <- String.downcase(signature),
- %WebsubClientSubscription{} = websub <- Repo.get(WebsubClientSubscription, id),
- {:ok, body, _conn} = read_body(conn),
- ^signature <- Websub.sign(websub.secret, body) do
- Federator.incoming_doc(body)
-
- conn
- |> send_resp(200, "OK")
- else
- _e ->
- Logger.debug("Can't handle incoming subscription post")
-
- conn
- |> send_resp(500, "Error")
- end
- end
-end
diff --git a/lib/pleroma/web/websub/websub_server_subscription.ex b/lib/pleroma/web/websub/websub_server_subscription.ex
deleted file mode 100644
index d0ef548da..000000000
--- a/lib/pleroma/web/websub/websub_server_subscription.ex
+++ /dev/null
@@ -1,17 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Websub.WebsubServerSubscription do
- use Ecto.Schema
-
- schema "websub_server_subscriptions" do
- field(:topic, :string)
- field(:callback, :string)
- field(:secret, :string)
- field(:valid_until, :naive_datetime)
- field(:state, :string)
-
- timestamps()
- end
-end
diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex
index 83d528a66..8ad756b62 100644
--- a/lib/pleroma/workers/receiver_worker.ex
+++ b/lib/pleroma/workers/receiver_worker.ex
@@ -8,10 +8,6 @@ defmodule Pleroma.Workers.ReceiverWorker do
use Pleroma.Workers.WorkerHelper, queue: "federator_incoming"
@impl Oban.Worker
- def perform(%{"op" => "incoming_doc", "body" => doc}, _job) do
- Federator.perform(:incoming_doc, doc)
- end
-
def perform(%{"op" => "incoming_ap_doc", "params" => params}, _job) do
Federator.perform(:incoming_ap_doc, params)
end
diff --git a/lib/pleroma/workers/subscriber_worker.ex b/lib/pleroma/workers/subscriber_worker.ex
deleted file mode 100644
index fc490e300..000000000
--- a/lib/pleroma/workers/subscriber_worker.ex
+++ /dev/null
@@ -1,26 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Workers.SubscriberWorker do
- alias Pleroma.Repo
- alias Pleroma.Web.Federator
- alias Pleroma.Web.Websub
-
- use Pleroma.Workers.WorkerHelper, queue: "federator_outgoing"
-
- @impl Oban.Worker
- def perform(%{"op" => "refresh_subscriptions"}, _job) do
- Federator.perform(:refresh_subscriptions)
- end
-
- def perform(%{"op" => "request_subscription", "websub_id" => websub_id}, _job) do
- websub = Repo.get(Websub.WebsubClientSubscription, websub_id)
- Federator.perform(:request_subscription, websub)
- end
-
- def perform(%{"op" => "verify_websub", "websub_id" => websub_id}, _job) do
- websub = Repo.get(Websub.WebsubServerSubscription, websub_id)
- Federator.perform(:verify_websub, websub)
- end
-end