1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.User do
10 import Ecto, only: [assoc: 2]
14 alias Pleroma.Activity
15 alias Pleroma.Conversation.Participation
16 alias Pleroma.Delivery
17 alias Pleroma.FollowingRelationship
19 alias Pleroma.Notification
21 alias Pleroma.Registration
23 alias Pleroma.RepoStreamer
25 alias Pleroma.UserRelationship
27 alias Pleroma.Web.ActivityPub.ActivityPub
28 alias Pleroma.Web.ActivityPub.Utils
29 alias Pleroma.Web.CommonAPI
30 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
31 alias Pleroma.Web.OAuth
32 alias Pleroma.Web.RelMe
33 alias Pleroma.Workers.BackgroundWorker
37 @type t :: %__MODULE__{}
39 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
41 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
42 @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
44 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
45 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
47 # AP ID user relationships (blocks, mutes etc.)
48 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
49 @user_relationships_config [
51 blocker_blocks: :blocked_users,
52 blockee_blocks: :blocker_users
55 muter_mutes: :muted_users,
56 mutee_mutes: :muter_users
59 reblog_muter_mutes: :reblog_muted_users,
60 reblog_mutee_mutes: :reblog_muter_users
63 notification_muter_mutes: :notification_muted_users,
64 notification_mutee_mutes: :notification_muter_users
66 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
67 inverse_subscription: [
68 subscribee_subscriptions: :subscriber_users,
69 subscriber_subscriptions: :subscribee_users
75 field(:email, :string)
77 field(:nickname, :string)
78 field(:password_hash, :string)
79 field(:password, :string, virtual: true)
80 field(:password_confirmation, :string, virtual: true)
82 field(:ap_id, :string)
84 field(:local, :boolean, default: true)
85 field(:follower_address, :string)
86 field(:following_address, :string)
87 field(:search_rank, :float, virtual: true)
88 field(:search_type, :integer, virtual: true)
89 field(:tags, {:array, :string}, default: [])
90 field(:last_refreshed_at, :naive_datetime_usec)
91 field(:last_digest_emailed_at, :naive_datetime)
92 field(:banner, :map, default: %{})
93 field(:background, :map, default: %{})
94 field(:source_data, :map, default: %{})
95 field(:note_count, :integer, default: 0)
96 field(:follower_count, :integer, default: 0)
97 field(:following_count, :integer, default: 0)
98 field(:locked, :boolean, default: false)
99 field(:confirmation_pending, :boolean, default: false)
100 field(:password_reset_pending, :boolean, default: false)
101 field(:confirmation_token, :string, default: nil)
102 field(:default_scope, :string, default: "public")
103 field(:domain_blocks, {:array, :string}, default: [])
104 field(:deactivated, :boolean, default: false)
105 field(:no_rich_text, :boolean, default: false)
106 field(:ap_enabled, :boolean, default: false)
107 field(:is_moderator, :boolean, default: false)
108 field(:is_admin, :boolean, default: false)
109 field(:show_role, :boolean, default: true)
110 field(:settings, :map, default: nil)
111 field(:magic_key, :string, default: nil)
112 field(:uri, :string, default: nil)
113 field(:hide_followers_count, :boolean, default: false)
114 field(:hide_follows_count, :boolean, default: false)
115 field(:hide_followers, :boolean, default: false)
116 field(:hide_follows, :boolean, default: false)
117 field(:hide_favorites, :boolean, default: true)
118 field(:unread_conversation_count, :integer, default: 0)
119 field(:pinned_activities, {:array, :string}, default: [])
120 field(:email_notifications, :map, default: %{"digest" => false})
121 field(:mascot, :map, default: nil)
122 field(:emoji, {:array, :map}, default: [])
123 field(:pleroma_settings_store, :map, default: %{})
124 field(:fields, {:array, :map}, default: [])
125 field(:raw_fields, {:array, :map}, default: [])
126 field(:discoverable, :boolean, default: false)
127 field(:invisible, :boolean, default: false)
128 field(:allow_following_move, :boolean, default: true)
129 field(:skip_thread_containment, :boolean, default: false)
130 field(:actor_type, :string, default: "Person")
131 field(:also_known_as, {:array, :string}, default: [])
134 :notification_settings,
135 Pleroma.User.NotificationSetting,
139 has_many(:notifications, Notification)
140 has_many(:registrations, Registration)
141 has_many(:deliveries, Delivery)
143 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
144 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
146 for {relationship_type,
148 {outgoing_relation, outgoing_relation_target},
149 {incoming_relation, incoming_relation_source}
150 ]} <- @user_relationships_config do
151 # Definitions of `has_many :blocker_blocks`, `has_many :muter_mutes` etc.
152 has_many(outgoing_relation, UserRelationship,
153 foreign_key: :source_id,
154 where: [relationship_type: relationship_type]
157 # Definitions of `has_many :blockee_blocks`, `has_many :mutee_mutes` etc.
158 has_many(incoming_relation, UserRelationship,
159 foreign_key: :target_id,
160 where: [relationship_type: relationship_type]
163 # Definitions of `has_many :blocked_users`, `has_many :muted_users` etc.
164 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
166 # Definitions of `has_many :blocker_users`, `has_many :muter_users` etc.
167 has_many(incoming_relation_source, through: [incoming_relation, :source])
170 # `:blocks` is deprecated (replaced with `blocked_users` relation)
171 field(:blocks, {:array, :string}, default: [])
172 # `:mutes` is deprecated (replaced with `muted_users` relation)
173 field(:mutes, {:array, :string}, default: [])
174 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
175 field(:muted_reblogs, {:array, :string}, default: [])
176 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
177 field(:muted_notifications, {:array, :string}, default: [])
178 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
179 field(:subscribers, {:array, :string}, default: [])
184 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
185 @user_relationships_config do
186 # Definitions of `blocked_users_relation/1`, `muted_users_relation/1`, etc.
187 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
188 target_users_query = assoc(user, unquote(outgoing_relation_target))
190 if restrict_deactivated? do
191 restrict_deactivated(target_users_query)
197 # Definitions of `blocked_users/1`, `muted_users/1`, etc.
198 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
200 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
202 restrict_deactivated?
207 # Definitions of `blocked_users_ap_ids/1`, `muted_users_ap_ids/1`, etc.
208 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
210 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
212 restrict_deactivated?
214 |> select([u], u.ap_id)
219 @doc "Returns if the user should be allowed to authenticate"
220 def auth_active?(%User{deactivated: true}), do: false
222 def auth_active?(%User{confirmation_pending: true}),
223 do: !Pleroma.Config.get([:instance, :account_activation_required])
225 def auth_active?(%User{}), do: true
227 def visible_for?(user, for_user \\ nil)
229 def visible_for?(%User{invisible: true}, _), do: false
231 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
233 def visible_for?(%User{} = user, for_user) do
234 auth_active?(user) || superuser?(for_user)
237 def visible_for?(_, _), do: false
239 def superuser?(%User{local: true, is_admin: true}), do: true
240 def superuser?(%User{local: true, is_moderator: true}), do: true
241 def superuser?(_), do: false
243 def invisible?(%User{invisible: true}), do: true
244 def invisible?(_), do: false
246 def avatar_url(user, options \\ []) do
248 %{"url" => [%{"href" => href} | _]} -> href
249 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
253 def banner_url(user, options \\ []) do
255 %{"url" => [%{"href" => href} | _]} -> href
256 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
260 def profile_url(%User{source_data: %{"url" => url}}), do: url
261 def profile_url(%User{ap_id: ap_id}), do: ap_id
262 def profile_url(_), do: nil
264 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
266 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
267 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
269 @spec ap_following(User.t()) :: Sring.t()
270 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
271 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
273 def follow_state(%User{} = user, %User{} = target) do
274 case Utils.fetch_latest_follow(user, target) do
275 %{data: %{"state" => state}} -> state
276 # Ideally this would be nil, but then Cachex does not commit the value
281 def get_cached_follow_state(user, target) do
282 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
283 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
286 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
287 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
288 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
291 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
292 def restrict_deactivated(query) do
293 from(u in query, where: u.deactivated != ^true)
296 defdelegate following_count(user), to: FollowingRelationship
298 defp truncate_fields_param(params) do
299 if Map.has_key?(params, :fields) do
300 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
306 defp truncate_if_exists(params, key, max_length) do
307 if Map.has_key?(params, key) and is_binary(params[key]) do
308 {value, _chopped} = String.split_at(params[key], max_length)
309 Map.put(params, key, value)
315 def remote_user_creation(params) do
316 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
317 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
321 |> truncate_if_exists(:name, name_limit)
322 |> truncate_if_exists(:bio, bio_limit)
323 |> truncate_fields_param()
343 :hide_followers_count,
354 |> validate_required([:name, :ap_id])
355 |> unique_constraint(:nickname)
356 |> validate_format(:nickname, @email_regex)
357 |> validate_length(:bio, max: bio_limit)
358 |> validate_length(:name, max: name_limit)
359 |> validate_fields(true)
361 case params[:source_data] do
362 %{"followers" => followers, "following" => following} ->
364 |> put_change(:follower_address, followers)
365 |> put_change(:following_address, following)
368 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
369 put_change(changeset, :follower_address, followers)
373 def update_changeset(struct, params \\ %{}) do
374 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
375 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
390 :hide_followers_count,
393 :allow_following_move,
396 :skip_thread_containment,
399 :pleroma_settings_store,
405 |> unique_constraint(:nickname)
406 |> validate_format(:nickname, local_nickname_regex())
407 |> validate_length(:bio, max: bio_limit)
408 |> validate_length(:name, min: 1, max: name_limit)
409 |> validate_fields(false)
412 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
413 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
414 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
416 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
418 params = if remote?, do: truncate_fields_param(params), else: params
440 :allow_following_move,
442 :hide_followers_count,
448 |> unique_constraint(:nickname)
449 |> validate_format(:nickname, local_nickname_regex())
450 |> validate_length(:bio, max: bio_limit)
451 |> validate_length(:name, max: name_limit)
452 |> validate_fields(remote?)
455 def password_update_changeset(struct, params) do
457 |> cast(params, [:password, :password_confirmation])
458 |> validate_required([:password, :password_confirmation])
459 |> validate_confirmation(:password)
460 |> put_password_hash()
461 |> put_change(:password_reset_pending, false)
464 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
465 def reset_password(%User{id: user_id} = user, data) do
468 |> Multi.update(:user, password_update_changeset(user, data))
469 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
470 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
472 case Repo.transaction(multi) do
473 {:ok, %{user: user} = _} -> set_cache(user)
474 {:error, _, changeset, _} -> {:error, changeset}
478 def update_password_reset_pending(user, value) do
481 |> put_change(:password_reset_pending, value)
482 |> update_and_set_cache()
485 def force_password_reset_async(user) do
486 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
489 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
490 def force_password_reset(user), do: update_password_reset_pending(user, true)
492 def register_changeset(struct, params \\ %{}, opts \\ []) do
493 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
494 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
497 if is_nil(opts[:need_confirmation]) do
498 Pleroma.Config.get([:instance, :account_activation_required])
500 opts[:need_confirmation]
504 |> confirmation_changeset(need_confirmation: need_confirmation?)
505 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
506 |> validate_required([:name, :nickname, :password, :password_confirmation])
507 |> validate_confirmation(:password)
508 |> unique_constraint(:email)
509 |> unique_constraint(:nickname)
510 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
511 |> validate_format(:nickname, local_nickname_regex())
512 |> validate_format(:email, @email_regex)
513 |> validate_length(:bio, max: bio_limit)
514 |> validate_length(:name, min: 1, max: name_limit)
515 |> maybe_validate_required_email(opts[:external])
518 |> unique_constraint(:ap_id)
519 |> put_following_and_follower_address()
522 def maybe_validate_required_email(changeset, true), do: changeset
523 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
525 defp put_ap_id(changeset) do
526 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
527 put_change(changeset, :ap_id, ap_id)
530 defp put_following_and_follower_address(changeset) do
531 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
534 |> put_change(:follower_address, followers)
537 defp autofollow_users(user) do
538 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
541 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
544 follow_all(user, autofollowed_users)
547 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
548 def register(%Ecto.Changeset{} = changeset) do
549 with {:ok, user} <- Repo.insert(changeset) do
550 post_register_action(user)
554 def post_register_action(%User{} = user) do
555 with {:ok, user} <- autofollow_users(user),
556 {:ok, user} <- set_cache(user),
557 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
558 {:ok, _} <- try_send_confirmation_email(user) do
563 def try_send_confirmation_email(%User{} = user) do
564 if user.confirmation_pending &&
565 Pleroma.Config.get([:instance, :account_activation_required]) do
567 |> Pleroma.Emails.UserEmail.account_confirmation_email()
568 |> Pleroma.Emails.Mailer.deliver_async()
576 def try_send_confirmation_email(users) do
577 Enum.each(users, &try_send_confirmation_email/1)
580 def needs_update?(%User{local: true}), do: false
582 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
584 def needs_update?(%User{local: false} = user) do
585 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
588 def needs_update?(_), do: true
590 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
591 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
592 follow(follower, followed, "pending")
595 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
596 follow(follower, followed)
599 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
600 if not ap_enabled?(followed) do
601 follow(follower, followed)
607 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
608 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
609 def follow_all(follower, followeds) do
611 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
612 |> Enum.each(&follow(follower, &1, "accept"))
617 defdelegate following(user), to: FollowingRelationship
619 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
620 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
623 followed.deactivated ->
624 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
626 deny_follow_blocked and blocks?(followed, follower) ->
627 {:error, "Could not follow user: #{followed.nickname} blocked you."}
630 FollowingRelationship.follow(follower, followed, state)
632 {:ok, _} = update_follower_count(followed)
635 |> update_following_count()
640 def unfollow(%User{} = follower, %User{} = followed) do
641 if following?(follower, followed) and follower.ap_id != followed.ap_id do
642 FollowingRelationship.unfollow(follower, followed)
644 {:ok, followed} = update_follower_count(followed)
648 |> update_following_count()
651 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
653 {:error, "Not subscribed!"}
657 defdelegate following?(follower, followed), to: FollowingRelationship
659 def locked?(%User{} = user) do
664 Repo.get_by(User, id: id)
667 def get_by_ap_id(ap_id) do
668 Repo.get_by(User, ap_id: ap_id)
671 def get_all_by_ap_id(ap_ids) do
672 from(u in __MODULE__,
673 where: u.ap_id in ^ap_ids
678 def get_all_by_ids(ids) do
679 from(u in __MODULE__, where: u.id in ^ids)
683 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
684 # of the ap_id and the domain and tries to get that user
685 def get_by_guessed_nickname(ap_id) do
686 domain = URI.parse(ap_id).host
687 name = List.last(String.split(ap_id, "/"))
688 nickname = "#{name}@#{domain}"
690 get_cached_by_nickname(nickname)
693 def set_cache({:ok, user}), do: set_cache(user)
694 def set_cache({:error, err}), do: {:error, err}
696 def set_cache(%User{} = user) do
697 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
698 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
702 def update_and_set_cache(struct, params) do
704 |> update_changeset(params)
705 |> update_and_set_cache()
708 def update_and_set_cache(changeset) do
709 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
714 def invalidate_cache(user) do
715 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
716 Cachex.del(:user_cache, "nickname:#{user.nickname}")
719 def get_cached_by_ap_id(ap_id) do
720 key = "ap_id:#{ap_id}"
721 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
724 def get_cached_by_id(id) do
728 Cachex.fetch!(:user_cache, key, fn _ ->
732 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
733 {:commit, user.ap_id}
739 get_cached_by_ap_id(ap_id)
742 def get_cached_by_nickname(nickname) do
743 key = "nickname:#{nickname}"
745 Cachex.fetch!(:user_cache, key, fn ->
746 case get_or_fetch_by_nickname(nickname) do
747 {:ok, user} -> {:commit, user}
748 {:error, _error} -> {:ignore, nil}
753 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
754 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
757 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
758 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
760 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
761 get_cached_by_nickname(nickname_or_id)
763 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
764 get_cached_by_nickname(nickname_or_id)
771 def get_by_nickname(nickname) do
772 Repo.get_by(User, nickname: nickname) ||
773 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
774 Repo.get_by(User, nickname: local_nickname(nickname))
778 def get_by_email(email), do: Repo.get_by(User, email: email)
780 def get_by_nickname_or_email(nickname_or_email) do
781 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
784 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
786 def get_or_fetch_by_nickname(nickname) do
787 with %User{} = user <- get_by_nickname(nickname) do
791 with [_nick, _domain] <- String.split(nickname, "@"),
792 {:ok, user} <- fetch_by_nickname(nickname) do
793 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
794 fetch_initial_posts(user)
799 _e -> {:error, "not found " <> nickname}
804 @doc "Fetch some posts when the user has just been federated with"
805 def fetch_initial_posts(user) do
806 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
809 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
810 def get_followers_query(%User{} = user, nil) do
811 User.Query.build(%{followers: user, deactivated: false})
814 def get_followers_query(user, page) do
816 |> get_followers_query(nil)
817 |> User.Query.paginate(page, 20)
820 @spec get_followers_query(User.t()) :: Ecto.Query.t()
821 def get_followers_query(user), do: get_followers_query(user, nil)
823 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
824 def get_followers(user, page \\ nil) do
826 |> get_followers_query(page)
830 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
831 def get_external_followers(user, page \\ nil) do
833 |> get_followers_query(page)
834 |> User.Query.build(%{external: true})
838 def get_followers_ids(user, page \\ nil) do
840 |> get_followers_query(page)
845 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
846 def get_friends_query(%User{} = user, nil) do
847 User.Query.build(%{friends: user, deactivated: false})
850 def get_friends_query(user, page) do
852 |> get_friends_query(nil)
853 |> User.Query.paginate(page, 20)
856 @spec get_friends_query(User.t()) :: Ecto.Query.t()
857 def get_friends_query(user), do: get_friends_query(user, nil)
859 def get_friends(user, page \\ nil) do
861 |> get_friends_query(page)
865 def get_friends_ap_ids(user) do
867 |> get_friends_query(nil)
868 |> select([u], u.ap_id)
872 def get_friends_ids(user, page \\ nil) do
874 |> get_friends_query(page)
879 defdelegate get_follow_requests(user), to: FollowingRelationship
881 def increase_note_count(%User{} = user) do
883 |> where(id: ^user.id)
884 |> update([u], inc: [note_count: 1])
886 |> Repo.update_all([])
888 {1, [user]} -> set_cache(user)
893 def decrease_note_count(%User{} = user) do
895 |> where(id: ^user.id)
898 note_count: fragment("greatest(0, note_count - 1)")
902 |> Repo.update_all([])
904 {1, [user]} -> set_cache(user)
909 def update_note_count(%User{} = user, note_count \\ nil) do
914 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
920 |> cast(%{note_count: note_count}, [:note_count])
921 |> update_and_set_cache()
924 @spec maybe_fetch_follow_information(User.t()) :: User.t()
925 def maybe_fetch_follow_information(user) do
926 with {:ok, user} <- fetch_follow_information(user) do
930 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
936 def fetch_follow_information(user) do
937 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
939 |> follow_information_changeset(info)
940 |> update_and_set_cache()
944 defp follow_information_changeset(user, params) do
951 :hide_followers_count,
956 def update_follower_count(%User{} = user) do
957 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
958 follower_count_query =
959 User.Query.build(%{followers: user, deactivated: false})
960 |> select([u], %{count: count(u.id)})
963 |> where(id: ^user.id)
964 |> join(:inner, [u], s in subquery(follower_count_query))
966 set: [follower_count: s.count]
969 |> Repo.update_all([])
971 {1, [user]} -> set_cache(user)
975 {:ok, maybe_fetch_follow_information(user)}
979 @spec update_following_count(User.t()) :: User.t()
980 def update_following_count(%User{local: false} = user) do
981 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
982 maybe_fetch_follow_information(user)
988 def update_following_count(%User{local: true} = user) do
989 following_count = FollowingRelationship.following_count(user)
992 |> follow_information_changeset(%{following_count: following_count})
996 def set_unread_conversation_count(%User{local: true} = user) do
997 unread_query = Participation.unread_conversation_count_for_user(user)
1000 |> join(:inner, [u], p in subquery(unread_query))
1002 set: [unread_conversation_count: p.count]
1004 |> where([u], u.id == ^user.id)
1006 |> Repo.update_all([])
1008 {1, [user]} -> set_cache(user)
1013 def set_unread_conversation_count(user), do: {:ok, user}
1015 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1017 Participation.unread_conversation_count_for_user(user)
1018 |> where([p], p.conversation_id == ^conversation.id)
1021 |> join(:inner, [u], p in subquery(unread_query))
1023 inc: [unread_conversation_count: 1]
1025 |> where([u], u.id == ^user.id)
1026 |> where([u, p], p.count == 0)
1028 |> Repo.update_all([])
1030 {1, [user]} -> set_cache(user)
1035 def increment_unread_conversation_count(_, user), do: {:ok, user}
1037 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1038 def get_users_from_set(ap_ids, local_only \\ true) do
1039 criteria = %{ap_id: ap_ids, deactivated: false}
1040 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1042 User.Query.build(criteria)
1046 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1047 def get_recipients_from_activity(%Activity{recipients: to}) do
1048 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1052 @spec mute(User.t(), User.t(), boolean()) ::
1053 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1054 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1055 add_to_mutes(muter, mutee, notifications?)
1058 def unmute(%User{} = muter, %User{} = mutee) do
1059 remove_from_mutes(muter, mutee)
1062 def subscribe(%User{} = subscriber, %User{} = target) do
1063 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1065 if blocks?(target, subscriber) and deny_follow_blocked do
1066 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1068 # Note: the relationship is inverse: subscriber acts as relationship target
1069 UserRelationship.create_inverse_subscription(target, subscriber)
1073 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1074 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1075 subscribe(subscriber, subscribee)
1079 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1080 # Note: the relationship is inverse: subscriber acts as relationship target
1081 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1084 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1085 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1086 unsubscribe(unsubscriber, user)
1090 def block(%User{} = blocker, %User{} = blocked) do
1091 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1093 if following?(blocker, blocked) do
1094 {:ok, blocker, _} = unfollow(blocker, blocked)
1100 # clear any requested follows as well
1102 case CommonAPI.reject_follow_request(blocked, blocker) do
1103 {:ok, %User{} = updated_blocked} -> updated_blocked
1107 unsubscribe(blocked, blocker)
1109 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1111 {:ok, blocker} = update_follower_count(blocker)
1112 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1113 add_to_block(blocker, blocked)
1116 # helper to handle the block given only an actor's AP id
1117 def block(%User{} = blocker, %{ap_id: ap_id}) do
1118 block(blocker, get_cached_by_ap_id(ap_id))
1121 def unblock(%User{} = blocker, %User{} = blocked) do
1122 remove_from_block(blocker, blocked)
1125 # helper to handle the block given only an actor's AP id
1126 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1127 unblock(blocker, get_cached_by_ap_id(ap_id))
1130 def mutes?(nil, _), do: false
1131 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1133 def mutes_user?(%User{} = user, %User{} = target) do
1134 UserRelationship.mute_exists?(user, target)
1137 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1138 def muted_notifications?(nil, _), do: false
1140 def muted_notifications?(%User{} = user, %User{} = target),
1141 do: UserRelationship.notification_mute_exists?(user, target)
1143 def blocks?(nil, _), do: false
1145 def blocks?(%User{} = user, %User{} = target) do
1146 blocks_user?(user, target) ||
1147 (!User.following?(user, target) && blocks_domain?(user, target))
1150 def blocks_user?(%User{} = user, %User{} = target) do
1151 UserRelationship.block_exists?(user, target)
1154 def blocks_user?(_, _), do: false
1156 def blocks_domain?(%User{} = user, %User{} = target) do
1157 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1158 %{host: host} = URI.parse(target.ap_id)
1159 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1162 def blocks_domain?(_, _), do: false
1164 def subscribed_to?(%User{} = user, %User{} = target) do
1165 # Note: the relationship is inverse: subscriber acts as relationship target
1166 UserRelationship.inverse_subscription_exists?(target, user)
1169 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1170 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1171 subscribed_to?(user, target)
1176 Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
1177 E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1179 @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1180 def outgoing_relations_ap_ids(_, []), do: %{}
1182 def outgoing_relations_ap_ids(%User{} = user, relationship_types)
1183 when is_list(relationship_types) do
1186 |> assoc(:outgoing_relationships)
1187 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1188 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1189 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1190 |> group_by([user_rel, u], user_rel.relationship_type)
1192 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1197 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1201 def deactivate_async(user, status \\ true) do
1202 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1205 def deactivate(user, status \\ true)
1207 def deactivate(users, status) when is_list(users) do
1208 Repo.transaction(fn ->
1209 for user <- users, do: deactivate(user, status)
1213 def deactivate(%User{} = user, status) do
1214 with {:ok, user} <- set_activation_status(user, status) do
1217 |> Enum.filter(& &1.local)
1218 |> Enum.each(fn follower ->
1219 follower |> update_following_count() |> set_cache()
1222 # Only update local user counts, remote will be update during the next pull.
1225 |> Enum.filter(& &1.local)
1226 |> Enum.each(&update_follower_count/1)
1232 def update_notification_settings(%User{} = user, settings) do
1234 |> cast(%{notification_settings: settings}, [])
1235 |> cast_embed(:notification_settings)
1236 |> validate_required([:notification_settings])
1237 |> update_and_set_cache()
1240 def delete(users) when is_list(users) do
1241 for user <- users, do: delete(user)
1244 def delete(%User{} = user) do
1245 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1248 def perform(:force_password_reset, user), do: force_password_reset(user)
1250 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1251 def perform(:delete, %User{} = user) do
1252 {:ok, _user} = ActivityPub.delete(user)
1254 # Remove all relationships
1257 |> Enum.each(fn follower ->
1258 ActivityPub.unfollow(follower, user)
1259 unfollow(follower, user)
1264 |> Enum.each(fn followed ->
1265 ActivityPub.unfollow(user, followed)
1266 unfollow(user, followed)
1269 delete_user_activities(user)
1270 invalidate_cache(user)
1274 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1275 def perform(:fetch_initial_posts, %User{} = user) do
1276 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1278 # Insert all the posts in reverse order, so they're in the right order on the timeline
1279 user.source_data["outbox"]
1280 |> Utils.fetch_ordered_collection(pages)
1282 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1285 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1287 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1288 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1289 when is_list(blocked_identifiers) do
1291 blocked_identifiers,
1292 fn blocked_identifier ->
1293 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1294 {:ok, _user_block} <- block(blocker, blocked),
1295 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1299 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1306 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1307 def perform(:follow_import, %User{} = follower, followed_identifiers)
1308 when is_list(followed_identifiers) do
1310 followed_identifiers,
1311 fn followed_identifier ->
1312 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1313 {:ok, follower} <- maybe_direct_follow(follower, followed),
1314 {:ok, _} <- ActivityPub.follow(follower, followed) do
1318 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1325 @spec external_users_query() :: Ecto.Query.t()
1326 def external_users_query do
1334 @spec external_users(keyword()) :: [User.t()]
1335 def external_users(opts \\ []) do
1337 external_users_query()
1338 |> select([u], struct(u, [:id, :ap_id]))
1342 do: where(query, [u], u.id > ^opts[:max_id]),
1347 do: limit(query, ^opts[:limit]),
1353 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1354 BackgroundWorker.enqueue("blocks_import", %{
1355 "blocker_id" => blocker.id,
1356 "blocked_identifiers" => blocked_identifiers
1360 def follow_import(%User{} = follower, followed_identifiers)
1361 when is_list(followed_identifiers) do
1362 BackgroundWorker.enqueue("follow_import", %{
1363 "follower_id" => follower.id,
1364 "followed_identifiers" => followed_identifiers
1368 def delete_user_activities(%User{ap_id: ap_id}) do
1370 |> Activity.Queries.by_actor()
1371 |> RepoStreamer.chunk_stream(50)
1372 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1376 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1378 |> Object.normalize()
1379 |> ActivityPub.delete()
1382 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1383 object = Object.normalize(activity)
1386 |> get_cached_by_ap_id()
1387 |> ActivityPub.unlike(object)
1390 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1391 object = Object.normalize(activity)
1394 |> get_cached_by_ap_id()
1395 |> ActivityPub.unannounce(object)
1398 defp delete_activity(_activity), do: "Doing nothing"
1400 def html_filter_policy(%User{no_rich_text: true}) do
1401 Pleroma.HTML.Scrubber.TwitterText
1404 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1406 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1408 def get_or_fetch_by_ap_id(ap_id) do
1409 user = get_cached_by_ap_id(ap_id)
1411 if !is_nil(user) and !needs_update?(user) do
1414 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1415 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1417 resp = fetch_by_ap_id(ap_id)
1419 if should_fetch_initial do
1420 with {:ok, %User{} = user} <- resp do
1421 fetch_initial_posts(user)
1430 Creates an internal service actor by URI if missing.
1431 Optionally takes nickname for addressing.
1433 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1434 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1436 case get_cached_by_ap_id(uri) do
1438 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1439 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1443 %User{invisible: false} = user ->
1453 @spec set_invisible(User.t()) :: {:ok, User.t()}
1454 defp set_invisible(user) do
1456 |> change(%{invisible: true})
1457 |> update_and_set_cache()
1460 @spec create_service_actor(String.t(), String.t()) ::
1461 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1462 defp create_service_actor(uri, nickname) do
1468 follower_address: uri <> "/followers"
1471 |> unique_constraint(:nickname)
1477 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1480 |> :public_key.pem_decode()
1482 |> :public_key.pem_entry_decode()
1487 def public_key(_), do: {:error, "not found key"}
1489 def get_public_key_for_ap_id(ap_id) do
1490 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1491 {:ok, public_key} <- public_key(user) do
1498 defp blank?(""), do: nil
1499 defp blank?(n), do: n
1501 def insert_or_update_user(data) do
1503 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1504 |> remote_user_creation()
1505 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1509 def ap_enabled?(%User{local: true}), do: true
1510 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1511 def ap_enabled?(_), do: false
1513 @doc "Gets or fetch a user by uri or nickname."
1514 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1515 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1516 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1518 # wait a period of time and return newest version of the User structs
1519 # this is because we have synchronous follow APIs and need to simulate them
1520 # with an async handshake
1521 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1522 with %User{} = a <- get_cached_by_id(a.id),
1523 %User{} = b <- get_cached_by_id(b.id) do
1530 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1531 with :ok <- :timer.sleep(timeout),
1532 %User{} = a <- get_cached_by_id(a.id),
1533 %User{} = b <- get_cached_by_id(b.id) do
1540 def parse_bio(bio) when is_binary(bio) and bio != "" do
1542 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1546 def parse_bio(_), do: ""
1548 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1549 # TODO: get profile URLs other than user.ap_id
1550 profile_urls = [user.ap_id]
1553 |> CommonUtils.format_input("text/plain",
1554 mentions_format: :full,
1555 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1560 def parse_bio(_, _), do: ""
1562 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1563 Repo.transaction(fn ->
1564 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1568 def tag(nickname, tags) when is_binary(nickname),
1569 do: tag(get_by_nickname(nickname), tags)
1571 def tag(%User{} = user, tags),
1572 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1574 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1575 Repo.transaction(fn ->
1576 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1580 def untag(nickname, tags) when is_binary(nickname),
1581 do: untag(get_by_nickname(nickname), tags)
1583 def untag(%User{} = user, tags),
1584 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1586 defp update_tags(%User{} = user, new_tags) do
1587 {:ok, updated_user} =
1589 |> change(%{tags: new_tags})
1590 |> update_and_set_cache()
1595 defp normalize_tags(tags) do
1598 |> Enum.map(&String.downcase/1)
1601 defp local_nickname_regex do
1602 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1603 @extended_local_nickname_regex
1605 @strict_local_nickname_regex
1609 def local_nickname(nickname_or_mention) do
1612 |> String.split("@")
1616 def full_nickname(nickname_or_mention),
1617 do: String.trim_leading(nickname_or_mention, "@")
1619 def error_user(ap_id) do
1623 nickname: "erroruser@example.com",
1624 inserted_at: NaiveDateTime.utc_now()
1628 @spec all_superusers() :: [User.t()]
1629 def all_superusers do
1630 User.Query.build(%{super_users: true, local: true, deactivated: false})
1634 def showing_reblogs?(%User{} = user, %User{} = target) do
1635 not UserRelationship.reblog_mute_exists?(user, target)
1639 The function returns a query to get users with no activity for given interval of days.
1640 Inactive users are those who didn't read any notification, or had any activity where
1641 the user is the activity's actor, during `inactivity_threshold` days.
1642 Deactivated users will not appear in this list.
1646 iex> Pleroma.User.list_inactive_users()
1649 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1650 def list_inactive_users_query(inactivity_threshold \\ 7) do
1651 negative_inactivity_threshold = -inactivity_threshold
1652 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1653 # Subqueries are not supported in `where` clauses, join gets too complicated.
1654 has_read_notifications =
1655 from(n in Pleroma.Notification,
1656 where: n.seen == true,
1658 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1661 |> Pleroma.Repo.all()
1663 from(u in Pleroma.User,
1664 left_join: a in Pleroma.Activity,
1665 on: u.ap_id == a.actor,
1666 where: not is_nil(u.nickname),
1667 where: u.deactivated != ^true,
1668 where: u.id not in ^has_read_notifications,
1671 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1672 is_nil(max(a.inserted_at))
1677 Enable or disable email notifications for user
1681 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1682 Pleroma.User{email_notifications: %{"digest" => true}}
1684 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1685 Pleroma.User{email_notifications: %{"digest" => false}}
1687 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1688 {:ok, t()} | {:error, Ecto.Changeset.t()}
1689 def switch_email_notifications(user, type, status) do
1690 User.update_email_notifications(user, %{type => status})
1694 Set `last_digest_emailed_at` value for the user to current time
1696 @spec touch_last_digest_emailed_at(t()) :: t()
1697 def touch_last_digest_emailed_at(user) do
1698 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1700 {:ok, updated_user} =
1702 |> change(%{last_digest_emailed_at: now})
1703 |> update_and_set_cache()
1708 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1709 def toggle_confirmation(%User{} = user) do
1711 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1712 |> update_and_set_cache()
1715 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1716 def toggle_confirmation(users) do
1717 Enum.map(users, &toggle_confirmation/1)
1720 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1724 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1725 # use instance-default
1726 config = Pleroma.Config.get([:assets, :mascots])
1727 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1728 mascot = Keyword.get(config, default_mascot)
1731 "id" => "default-mascot",
1732 "url" => mascot[:url],
1733 "preview_url" => mascot[:url],
1735 "mime_type" => mascot[:mime_type]
1740 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1742 def ensure_keys_present(%User{} = user) do
1743 with {:ok, pem} <- Keys.generate_rsa_pem() do
1745 |> cast(%{keys: pem}, [:keys])
1746 |> validate_required([:keys])
1747 |> update_and_set_cache()
1751 def get_ap_ids_by_nicknames(nicknames) do
1753 where: u.nickname in ^nicknames,
1759 defdelegate search(query, opts \\ []), to: User.Search
1761 defp put_password_hash(
1762 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1764 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1767 defp put_password_hash(changeset), do: changeset
1769 def is_internal_user?(%User{nickname: nil}), do: true
1770 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1771 def is_internal_user?(_), do: false
1773 # A hack because user delete activities have a fake id for whatever reason
1774 # TODO: Get rid of this
1775 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1777 def get_delivered_users_by_object_id(object_id) do
1779 inner_join: delivery in assoc(u, :deliveries),
1780 where: delivery.object_id == ^object_id
1785 def change_email(user, email) do
1787 |> cast(%{email: email}, [:email])
1788 |> validate_required([:email])
1789 |> unique_constraint(:email)
1790 |> validate_format(:email, @email_regex)
1791 |> update_and_set_cache()
1794 # Internal function; public one is `deactivate/2`
1795 defp set_activation_status(user, deactivated) do
1797 |> cast(%{deactivated: deactivated}, [:deactivated])
1798 |> update_and_set_cache()
1801 def update_banner(user, banner) do
1803 |> cast(%{banner: banner}, [:banner])
1804 |> update_and_set_cache()
1807 def update_background(user, background) do
1809 |> cast(%{background: background}, [:background])
1810 |> update_and_set_cache()
1813 def update_source_data(user, source_data) do
1815 |> cast(%{source_data: source_data}, [:source_data])
1816 |> update_and_set_cache()
1819 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1822 moderator: is_moderator
1826 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1827 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1828 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1829 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1832 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1833 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1837 def fields(%{fields: nil}), do: []
1839 def fields(%{fields: fields}), do: fields
1841 def validate_fields(changeset, remote? \\ false) do
1842 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1843 limit = Pleroma.Config.get([:instance, limit_name], 0)
1846 |> validate_length(:fields, max: limit)
1847 |> validate_change(:fields, fn :fields, fields ->
1848 if Enum.all?(fields, &valid_field?/1) do
1856 defp valid_field?(%{"name" => name, "value" => value}) do
1857 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1858 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1860 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1861 String.length(value) <= value_limit
1864 defp valid_field?(_), do: false
1866 defp truncate_field(%{"name" => name, "value" => value}) do
1868 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1871 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1873 %{"name" => name, "value" => value}
1876 def admin_api_update(user, params) do
1883 |> update_and_set_cache()
1886 @doc "Signs user out of all applications"
1887 def global_sign_out(user) do
1888 OAuth.Authorization.delete_user_authorizations(user)
1889 OAuth.Token.delete_user_tokens(user)
1892 def mascot_update(user, url) do
1894 |> cast(%{mascot: url}, [:mascot])
1895 |> validate_required([:mascot])
1896 |> update_and_set_cache()
1899 def mastodon_settings_update(user, settings) do
1901 |> cast(%{settings: settings}, [:settings])
1902 |> validate_required([:settings])
1903 |> update_and_set_cache()
1906 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1907 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1909 if need_confirmation? do
1911 confirmation_pending: true,
1912 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1916 confirmation_pending: false,
1917 confirmation_token: nil
1921 cast(user, params, [:confirmation_pending, :confirmation_token])
1924 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1925 if id not in user.pinned_activities do
1926 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1927 params = %{pinned_activities: user.pinned_activities ++ [id]}
1930 |> cast(params, [:pinned_activities])
1931 |> validate_length(:pinned_activities,
1932 max: max_pinned_statuses,
1933 message: "You have already pinned the maximum number of statuses"
1938 |> update_and_set_cache()
1941 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1942 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1945 |> cast(params, [:pinned_activities])
1946 |> update_and_set_cache()
1949 def update_email_notifications(user, settings) do
1950 email_notifications =
1951 user.email_notifications
1952 |> Map.merge(settings)
1953 |> Map.take(["digest"])
1955 params = %{email_notifications: email_notifications}
1956 fields = [:email_notifications]
1959 |> cast(params, fields)
1960 |> validate_required(fields)
1961 |> update_and_set_cache()
1964 defp set_domain_blocks(user, domain_blocks) do
1965 params = %{domain_blocks: domain_blocks}
1968 |> cast(params, [:domain_blocks])
1969 |> validate_required([:domain_blocks])
1970 |> update_and_set_cache()
1973 def block_domain(user, domain_blocked) do
1974 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1977 def unblock_domain(user, domain_blocked) do
1978 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1981 @spec add_to_block(User.t(), User.t()) ::
1982 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
1983 defp add_to_block(%User{} = user, %User{} = blocked) do
1984 UserRelationship.create_block(user, blocked)
1987 @spec add_to_block(User.t(), User.t()) ::
1988 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
1989 defp remove_from_block(%User{} = user, %User{} = blocked) do
1990 UserRelationship.delete_block(user, blocked)
1993 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
1994 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
1995 {:ok, user_notification_mute} <-
1996 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
1998 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2002 defp remove_from_mutes(user, %User{} = muted_user) do
2003 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2004 {:ok, user_notification_mute} <-
2005 UserRelationship.delete_notification_mute(user, muted_user) do
2006 {:ok, [user_mute, user_notification_mute]}
2010 def set_invisible(user, invisible) do
2011 params = %{invisible: invisible}
2014 |> cast(params, [:invisible])
2015 |> validate_required([:invisible])
2016 |> update_and_set_cache()