aboutsummaryrefslogtreecommitdiff
path: root/lib/pleroma/user.ex
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pleroma/user.ex')
-rw-r--r--lib/pleroma/user.ex589
1 files changed, 360 insertions, 229 deletions
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 120cb2641..911dde6e2 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User do
@@ -7,13 +7,16 @@ defmodule Pleroma.User do
import Ecto.Changeset
import Ecto.Query
+ import Ecto, only: [assoc: 2]
alias Comeonin.Pbkdf2
alias Ecto.Multi
alias Pleroma.Activity
+ alias Pleroma.Config
alias Pleroma.Conversation.Participation
alias Pleroma.Delivery
alias Pleroma.FollowingRelationship
+ alias Pleroma.HTML
alias Pleroma.Keys
alias Pleroma.Notification
alias Pleroma.Object
@@ -21,6 +24,7 @@ defmodule Pleroma.User do
alias Pleroma.Repo
alias Pleroma.RepoStreamer
alias Pleroma.User
+ alias Pleroma.UserRelationship
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
@@ -33,7 +37,7 @@ defmodule Pleroma.User do
require Logger
@type t :: %__MODULE__{}
-
+ @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
@@ -42,6 +46,32 @@ defmodule Pleroma.User do
@strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
@extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
+ # AP ID user relationships (blocks, mutes etc.)
+ # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
+ @user_relationships_config [
+ block: [
+ blocker_blocks: :blocked_users,
+ blockee_blocks: :blocker_users
+ ],
+ mute: [
+ muter_mutes: :muted_users,
+ mutee_mutes: :muter_users
+ ],
+ reblog_mute: [
+ reblog_muter_mutes: :reblog_muted_users,
+ reblog_mutee_mutes: :reblog_muter_users
+ ],
+ notification_mute: [
+ notification_muter_mutes: :notification_muted_users,
+ notification_mutee_mutes: :notification_muter_users
+ ],
+ # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
+ inverse_subscription: [
+ subscribee_subscriptions: :subscriber_users,
+ subscriber_subscriptions: :subscribee_users
+ ]
+ ]
+
schema "users" do
field(:bio, :string)
field(:email, :string)
@@ -61,7 +91,6 @@ 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: %{})
@@ -73,12 +102,7 @@ defmodule Pleroma.User do
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)
@@ -105,32 +129,110 @@ defmodule Pleroma.User do
field(:invisible, :boolean, default: false)
field(:allow_following_move, :boolean, default: true)
field(:skip_thread_containment, :boolean, default: false)
+ field(:actor_type, :string, default: "Person")
field(:also_known_as, {:array, :string}, default: [])
- field(:notification_settings, :map,
- default: %{
- "followers" => true,
- "follows" => true,
- "non_follows" => true,
- "non_followers" => true
- }
+ embeds_one(
+ :notification_settings,
+ Pleroma.User.NotificationSetting,
+ on_replace: :update
)
has_many(:notifications, Notification)
has_many(:registrations, Registration)
has_many(:deliveries, Delivery)
+ has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
+ has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
+
+ for {relationship_type,
+ [
+ {outgoing_relation, outgoing_relation_target},
+ {incoming_relation, incoming_relation_source}
+ ]} <- @user_relationships_config do
+ # Definitions of `has_many :blocker_blocks`, `has_many :muter_mutes` etc.
+ has_many(outgoing_relation, UserRelationship,
+ foreign_key: :source_id,
+ where: [relationship_type: relationship_type]
+ )
+
+ # Definitions of `has_many :blockee_blocks`, `has_many :mutee_mutes` etc.
+ has_many(incoming_relation, UserRelationship,
+ foreign_key: :target_id,
+ where: [relationship_type: relationship_type]
+ )
+
+ # Definitions of `has_many :blocked_users`, `has_many :muted_users` etc.
+ has_many(outgoing_relation_target, through: [outgoing_relation, :target])
+
+ # Definitions of `has_many :blocker_users`, `has_many :muter_users` etc.
+ has_many(incoming_relation_source, through: [incoming_relation, :source])
+ end
+
+ # `:blocks` is deprecated (replaced with `blocked_users` relation)
+ field(:blocks, {:array, :string}, default: [])
+ # `:mutes` is deprecated (replaced with `muted_users` relation)
+ field(:mutes, {:array, :string}, default: [])
+ # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
+ field(:muted_reblogs, {:array, :string}, default: [])
+ # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
+ field(:muted_notifications, {:array, :string}, default: [])
+ # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
+ field(:subscribers, {:array, :string}, default: [])
+
timestamps()
end
- @doc "Returns if the user should be allowed to authenticate"
- def auth_active?(%User{deactivated: true}), do: false
+ for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
+ @user_relationships_config do
+ # Definitions of `blocked_users_relation/1`, `muted_users_relation/1`, etc.
+ def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
+ target_users_query = assoc(user, unquote(outgoing_relation_target))
- def auth_active?(%User{confirmation_pending: true}),
- do: !Pleroma.Config.get([:instance, :account_activation_required])
+ if restrict_deactivated? do
+ restrict_deactivated(target_users_query)
+ else
+ target_users_query
+ end
+ end
+
+ # Definitions of `blocked_users/1`, `muted_users/1`, etc.
+ def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
+ __MODULE__
+ |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
+ user,
+ restrict_deactivated?
+ ])
+ |> Repo.all()
+ end
+
+ # Definitions of `blocked_users_ap_ids/1`, `muted_users_ap_ids/1`, etc.
+ def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
+ __MODULE__
+ |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
+ user,
+ restrict_deactivated?
+ ])
+ |> select([u], u.ap_id)
+ |> Repo.all()
+ end
+ end
- def auth_active?(%User{}), do: true
+ @doc "Returns status account"
+ @spec account_status(User.t()) :: account_status()
+ def account_status(%User{deactivated: true}), do: :deactivated
+ def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
+ def account_status(%User{confirmation_pending: true}) do
+ case Config.get([:instance, :account_activation_required]) do
+ true -> :confirmation_pending
+ _ -> :active
+ end
+ end
+
+ def account_status(%User{}), do: :active
+
+ @spec visible_for?(User.t(), User.t() | nil) :: boolean()
def visible_for?(user, for_user \\ nil)
def visible_for?(%User{invisible: true}, _), do: false
@@ -138,15 +240,17 @@ defmodule Pleroma.User do
def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
def visible_for?(%User{} = user, for_user) do
- auth_active?(user) || superuser?(for_user)
+ account_status(user) == :active || superuser?(for_user)
end
def visible_for?(_, _), do: false
+ @spec superuser?(User.t()) :: boolean()
def superuser?(%User{local: true, is_admin: true}), do: true
def superuser?(%User{local: true, is_moderator: true}), do: true
def superuser?(_), do: false
+ @spec invisible?(User.t()) :: boolean()
def invisible?(%User{invisible: true}), do: true
def invisible?(_), do: false
@@ -254,6 +358,7 @@ defmodule Pleroma.User do
:following_count,
:discoverable,
:invisible,
+ :actor_type,
:also_known_as
]
)
@@ -304,6 +409,7 @@ defmodule Pleroma.User do
:raw_fields,
:pleroma_settings_store,
:discoverable,
+ :actor_type,
:also_known_as
]
)
@@ -346,6 +452,7 @@ defmodule Pleroma.User do
:discoverable,
:hide_followers_count,
:hide_follows_count,
+ :actor_type,
:also_known_as
]
)
@@ -424,7 +531,14 @@ defmodule Pleroma.User do
end
def maybe_validate_required_email(changeset, true), do: changeset
- def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
+
+ def maybe_validate_required_email(changeset, _) do
+ if Pleroma.Config.get([:instance, :account_activation_required]) do
+ validate_required(changeset, [:email])
+ else
+ changeset
+ end
+ end
defp put_ap_id(changeset) do
ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
@@ -541,25 +655,48 @@ defmodule Pleroma.User do
end
end
+ def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
+ {:error, "Not subscribed!"}
+ end
+
def unfollow(%User{} = follower, %User{} = followed) do
- if following?(follower, followed) and follower.ap_id != followed.ap_id do
- FollowingRelationship.unfollow(follower, followed)
+ case get_follow_state(follower, followed) do
+ state when state in ["accept", "pending"] ->
+ FollowingRelationship.unfollow(follower, followed)
+ {:ok, followed} = update_follower_count(followed)
- {:ok, followed} = update_follower_count(followed)
+ {:ok, follower} =
+ follower
+ |> update_following_count()
+ |> set_cache()
- {:ok, follower} =
- follower
- |> update_following_count()
- |> set_cache()
+ {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
- {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
- else
- {:error, "Not subscribed!"}
+ nil ->
+ {:error, "Not subscribed!"}
end
end
defdelegate following?(follower, followed), to: FollowingRelationship
+ def get_follow_state(%User{} = follower, %User{} = following) do
+ following_relationship = FollowingRelationship.get(follower, following)
+
+ case {following_relationship, following.local} do
+ {nil, false} ->
+ case Utils.fetch_latest_follow(follower, following) do
+ %{data: %{"state" => state}} when state in ["pending", "accept"] -> state
+ _ -> nil
+ end
+
+ {%{state: state}, _} ->
+ state
+
+ {nil, _} ->
+ nil
+ end
+ end
+
def locked?(%User{} = user) do
user.locked || false
end
@@ -620,9 +757,18 @@ defmodule Pleroma.User do
Cachex.del(:user_cache, "nickname:#{user.nickname}")
end
+ @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
def get_cached_by_ap_id(ap_id) do
key = "ap_id:#{ap_id}"
- Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
+
+ with {:ok, nil} <- Cachex.get(:user_cache, key),
+ user when not is_nil(user) <- get_by_ap_id(ap_id),
+ {:ok, true} <- Cachex.put(:user_cache, key, user) do
+ user
+ else
+ {:ok, user} -> user
+ nil -> nil
+ end
end
def get_cached_by_id(id) do
@@ -694,10 +840,6 @@ defmodule Pleroma.User do
_e ->
with [_nick, _domain] <- String.split(nickname, "@"),
{:ok, user} <- fetch_by_nickname(nickname) do
- if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
- fetch_initial_posts(user)
- end
-
{:ok, user}
else
_e -> {:error, "not found " <> nickname}
@@ -705,11 +847,6 @@ defmodule Pleroma.User do
end
end
- @doc "Fetch some posts when the user has just been federated with"
- def fetch_initial_posts(user) do
- BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
- end
-
@spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
def get_followers_query(%User{} = user, nil) do
User.Query.build(%{followers: user, deactivated: false})
@@ -724,14 +861,14 @@ defmodule Pleroma.User do
@spec get_followers_query(User.t()) :: Ecto.Query.t()
def get_followers_query(user), do: get_followers_query(user, nil)
- @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
+ @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
def get_followers(user, page \\ nil) do
user
|> get_followers_query(page)
|> Repo.all()
end
- @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
+ @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
def get_external_followers(user, page \\ nil) do
user
|> get_followers_query(page)
@@ -766,6 +903,13 @@ defmodule Pleroma.User do
|> Repo.all()
end
+ def get_friends_ap_ids(user) do
+ user
+ |> get_friends_query(nil)
+ |> select([u], u.ap_id)
+ |> Repo.all()
+ end
+
def get_friends_ids(user, page \\ nil) do
user
|> get_friends_query(page)
@@ -946,34 +1090,45 @@ defmodule Pleroma.User do
|> Repo.all()
end
- @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
- def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
- add_to_mutes(muter, ap_id, notifications?)
+ @spec mute(User.t(), User.t(), boolean()) ::
+ {:ok, list(UserRelationship.t())} | {:error, String.t()}
+ def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
+ add_to_mutes(muter, mutee, notifications?)
end
- def unmute(muter, %{ap_id: ap_id}) do
- remove_from_mutes(muter, ap_id)
+ def unmute(%User{} = muter, %User{} = mutee) do
+ remove_from_mutes(muter, mutee)
end
- def subscribe(subscriber, %{ap_id: ap_id}) do
- with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
- deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
+ def subscribe(%User{} = subscriber, %User{} = target) do
+ deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
+
+ if blocks?(target, subscriber) and deny_follow_blocked do
+ {:error, "Could not subscribe: #{target.nickname} is blocking you"}
+ else
+ # Note: the relationship is inverse: subscriber acts as relationship target
+ UserRelationship.create_inverse_subscription(target, subscriber)
+ end
+ end
- if blocks?(subscribed, subscriber) and deny_follow_blocked do
- {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
- else
- User.add_to_subscribers(subscribed, subscriber.ap_id)
- end
+ def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
+ with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
+ subscribe(subscriber, subscribee)
end
end
- def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
+ def unsubscribe(%User{} = unsubscriber, %User{} = target) do
+ # Note: the relationship is inverse: subscriber acts as relationship target
+ UserRelationship.delete_inverse_subscription(target, unsubscriber)
+ end
+
+ def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
with %User{} = user <- get_cached_by_ap_id(ap_id) do
- User.remove_from_subscribers(user, unsubscriber.ap_id)
+ unsubscribe(unsubscriber, user)
end
end
- def block(blocker, %User{ap_id: ap_id} = blocked) do
+ def block(%User{} = blocker, %User{} = blocked) do
# sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
blocker =
if following?(blocker, blocked) do
@@ -990,50 +1145,54 @@ defmodule Pleroma.User do
nil -> blocked
end
- blocker =
- if subscribed_to?(blocked, blocker) do
- {:ok, blocker} = unsubscribe(blocked, blocker)
- blocker
- else
- blocker
- end
+ unsubscribe(blocked, blocker)
if following?(blocked, blocker), do: unfollow(blocked, blocker)
{:ok, blocker} = update_follower_count(blocker)
{:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
- add_to_block(blocker, ap_id)
+ add_to_block(blocker, blocked)
end
# helper to handle the block given only an actor's AP id
- def block(blocker, %{ap_id: ap_id}) do
+ def block(%User{} = blocker, %{ap_id: ap_id}) do
block(blocker, get_cached_by_ap_id(ap_id))
end
- def unblock(blocker, %{ap_id: ap_id}) do
- remove_from_block(blocker, ap_id)
+ def unblock(%User{} = blocker, %User{} = blocked) do
+ remove_from_block(blocker, blocked)
+ end
+
+ # helper to handle the block given only an actor's AP id
+ def unblock(%User{} = blocker, %{ap_id: ap_id}) do
+ unblock(blocker, get_cached_by_ap_id(ap_id))
end
def mutes?(nil, _), do: false
- def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
+ def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
+
+ def mutes_user?(%User{} = user, %User{} = target) do
+ UserRelationship.mute_exists?(user, target)
+ end
@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.muted_notifications, ap_id)
+ def muted_notifications?(%User{} = user, %User{} = target),
+ do: UserRelationship.notification_mute_exists?(user, target)
+
+ def blocks?(nil, _), do: false
def blocks?(%User{} = user, %User{} = target) do
- blocks_ap_id?(user, target) || blocks_domain?(user, target)
+ blocks_user?(user, target) ||
+ (!User.following?(user, target) && blocks_domain?(user, target))
end
- def blocks?(nil, _), do: false
-
- def blocks_ap_id?(%User{} = user, %User{} = target) do
- Enum.member?(user.blocks, target.ap_id)
+ def blocks_user?(%User{} = user, %User{} = target) do
+ UserRelationship.block_exists?(user, target)
end
- def blocks_ap_id?(_, _), do: false
+ def blocks_user?(_, _), do: false
def blocks_domain?(%User{} = user, %User{} = target) do
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
@@ -1043,28 +1202,41 @@ defmodule Pleroma.User do
def blocks_domain?(_, _), do: false
- def subscribed_to?(user, %{ap_id: ap_id}) do
+ def subscribed_to?(%User{} = user, %User{} = target) do
+ # Note: the relationship is inverse: subscriber acts as relationship target
+ UserRelationship.inverse_subscription_exists?(target, user)
+ end
+
+ def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
with %User{} = target <- get_cached_by_ap_id(ap_id) do
- Enum.member?(target.subscribers, user.ap_id)
+ subscribed_to?(user, target)
end
end
- @spec muted_users(User.t()) :: [User.t()]
- def muted_users(user) do
- User.Query.build(%{ap_id: user.mutes, deactivated: false})
- |> Repo.all()
- end
+ @doc """
+ Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
+ E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
+ """
+ @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
+ def outgoing_relations_ap_ids(_, []), do: %{}
- @spec blocked_users(User.t()) :: [User.t()]
- def blocked_users(user) do
- User.Query.build(%{ap_id: user.blocks, deactivated: false})
- |> Repo.all()
- end
+ def outgoing_relations_ap_ids(%User{} = user, relationship_types)
+ when is_list(relationship_types) do
+ db_result =
+ user
+ |> assoc(:outgoing_relationships)
+ |> join(:inner, [user_rel], u in assoc(user_rel, :target))
+ |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
+ |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
+ |> group_by([user_rel, u], user_rel.relationship_type)
+ |> Repo.all()
+ |> Enum.into(%{}, fn [k, v] -> {k, v} end)
- @spec subscribers(User.t()) :: [User.t()]
- def subscribers(user) do
- User.Query.build(%{ap_id: user.subscribers, deactivated: false})
- |> Repo.all()
+ Enum.into(
+ relationship_types,
+ %{},
+ fn rel_type -> {rel_type, db_result[rel_type] || []} end
+ )
end
def deactivate_async(user, status \\ true) do
@@ -1099,20 +1271,9 @@ defmodule Pleroma.User do
end
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])
+ |> cast(%{notification_settings: settings}, [])
+ |> cast_embed(:notification_settings)
|> validate_required([:notification_settings])
|> update_and_set_cache()
end
@@ -1151,17 +1312,6 @@ defmodule Pleroma.User do
Repo.delete(user)
end
- @spec perform(atom(), User.t()) :: {:ok, User.t()}
- def perform(:fetch_initial_posts, %User{} = 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.source_data["outbox"]
- |> Utils.fetch_ordered_collection(pages)
- |> Enum.reverse()
- |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
- end
-
def perform(:deactivate_async, user, status), do: deactivate(user, status)
@spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
@@ -1171,7 +1321,7 @@ defmodule Pleroma.User do
blocked_identifiers,
fn blocked_identifier ->
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
- {:ok, blocker} <- block(blocker, blocked),
+ {:ok, _user_block} <- block(blocker, blocked),
{:ok, _} <- ActivityPub.block(blocker, blocked) do
blocked
else
@@ -1183,7 +1333,6 @@ defmodule Pleroma.User do
)
end
- @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
def perform(:follow_import, %User{} = follower, followed_identifiers)
when is_list(followed_identifiers) do
Enum.map(
@@ -1291,18 +1440,7 @@ defmodule Pleroma.User do
if !is_nil(user) and !needs_update?(user) do
{:ok, user}
else
- # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
- should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
-
- resp = fetch_by_ap_id(ap_id)
-
- if should_fetch_initial do
- with {:ok, %User{} = user} <- resp do
- fetch_initial_posts(user)
- end
- end
-
- resp
+ fetch_by_ap_id(ap_id)
end
end
@@ -1310,20 +1448,47 @@ defmodule Pleroma.User do
Creates an internal service actor by URI if missing.
Optionally takes nickname for addressing.
"""
- def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
- with user when is_nil(user) <- get_cached_by_ap_id(uri) do
- {:ok, user} =
- %User{
- invisible: true,
- local: true,
- ap_id: uri,
- nickname: nickname,
- follower_address: uri <> "/followers"
- }
- |> Repo.insert()
+ @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
+ def get_or_create_service_actor_by_ap_id(uri, nickname) do
+ {_, user} =
+ case get_cached_by_ap_id(uri) do
+ nil ->
+ with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
+ Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
+ {:error, nil}
+ end
- user
- end
+ %User{invisible: false} = user ->
+ set_invisible(user)
+
+ user ->
+ {:ok, user}
+ end
+
+ user
+ end
+
+ @spec set_invisible(User.t()) :: {:ok, User.t()}
+ defp set_invisible(user) do
+ user
+ |> change(%{invisible: true})
+ |> update_and_set_cache()
+ end
+
+ @spec create_service_actor(String.t(), String.t()) ::
+ {:ok, User.t()} | {:error, Ecto.Changeset.t()}
+ defp create_service_actor(uri, nickname) do
+ %User{
+ invisible: true,
+ local: true,
+ ap_id: uri,
+ nickname: nickname,
+ follower_address: uri <> "/followers"
+ }
+ |> change
+ |> unique_constraint(:nickname)
+ |> Repo.insert()
+ |> set_cache()
end
# AP style
@@ -1355,7 +1520,7 @@ defmodule Pleroma.User do
data
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
|> remote_user_creation()
- |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
+ |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
|> set_cache()
end
@@ -1485,7 +1650,7 @@ defmodule Pleroma.User do
end
def showing_reblogs?(%User{} = user, %User{} = target) do
- target.ap_id not in user.muted_reblogs
+ not UserRelationship.reblog_mute_exists?(user, target)
end
@doc """
@@ -1736,6 +1901,12 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
+ @doc "Signs user out of all applications"
+ def global_sign_out(user) do
+ OAuth.Authorization.delete_user_authorizations(user)
+ OAuth.Token.delete_user_tokens(user)
+ end
+
def mascot_update(user, url) do
user
|> cast(%{mascot: url}, [:mascot])
@@ -1808,23 +1979,6 @@ defmodule Pleroma.User do
|> 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}
@@ -1842,87 +1996,64 @@ defmodule Pleroma.User 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()
+ @spec add_to_block(User.t(), User.t()) ::
+ {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
+ defp add_to_block(%User{} = user, %User{} = blocked) do
+ UserRelationship.create_block(user, blocked)
end
- def add_to_block(user, blocked) do
- set_blocks(user, Enum.uniq([blocked | user.blocks]))
+ @spec add_to_block(User.t(), User.t()) ::
+ {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
+ defp remove_from_block(%User{} = user, %User{} = blocked) do
+ UserRelationship.delete_block(user, blocked)
end
- 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?
- )
+ defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
+ with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
+ {:ok, user_notification_mute} <-
+ (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
+ {:ok, nil} do
+ {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
end
end
- 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
- )
+ defp remove_from_mutes(user, %User{} = muted_user) do
+ with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
+ {:ok, user_notification_mute} <-
+ UserRelationship.delete_notification_mute(user, muted_user) do
+ {:ok, [user_mute, user_notification_mute]}
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}
+ def set_invisible(user, invisible) do
+ params = %{invisible: invisible}
user
- |> cast(params, [:muted_notifications])
- |> validate_required([:muted_notifications])
+ |> cast(params, [:invisible])
+ |> validate_required([:invisible])
|> 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()
+ def sanitize_html(%User{} = user) do
+ sanitize_html(user, nil)
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}
+ # User data that mastodon isn't filtering (treated as plaintext):
+ # - field name
+ # - display name
+ def sanitize_html(%User{} = user, filter) do
+ fields =
+ user
+ |> User.fields()
+ |> Enum.map(fn %{"name" => name, "value" => value} ->
+ %{
+ "name" => name,
+ "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
+ }
+ end)
user
- |> cast(params, [:invisible])
- |> validate_required([:invisible])
- |> update_and_set_cache()
+ |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
+ |> Map.put(:fields, fields)
end
end