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
13 alias Pleroma.Activity
14 alias Pleroma.Conversation.Participation
15 alias Pleroma.Delivery
16 alias Pleroma.FollowingRelationship
18 alias Pleroma.Notification
20 alias Pleroma.Registration
22 alias Pleroma.RepoStreamer
25 alias Pleroma.Web.ActivityPub.ActivityPub
26 alias Pleroma.Web.ActivityPub.Utils
27 alias Pleroma.Web.CommonAPI
28 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
29 alias Pleroma.Web.OAuth
30 alias Pleroma.Web.RelMe
31 alias Pleroma.Workers.BackgroundWorker
35 @type t :: %__MODULE__{}
37 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
39 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
40 @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])?)*$/
42 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
43 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
47 field(:email, :string)
49 field(:nickname, :string)
50 field(:password_hash, :string)
51 field(:password, :string, virtual: true)
52 field(:password_confirmation, :string, virtual: true)
54 field(:ap_id, :string)
56 field(:local, :boolean, default: true)
57 field(:follower_address, :string)
58 field(:following_address, :string)
59 field(:search_rank, :float, virtual: true)
60 field(:search_type, :integer, virtual: true)
61 field(:tags, {:array, :string}, default: [])
62 field(:last_refreshed_at, :naive_datetime_usec)
63 field(:last_digest_emailed_at, :naive_datetime)
65 field(:banner, :map, default: %{})
66 field(:background, :map, default: %{})
67 field(:source_data, :map, default: %{})
68 field(:note_count, :integer, default: 0)
69 field(:follower_count, :integer, default: 0)
70 field(:following_count, :integer, default: 0)
71 field(:locked, :boolean, default: false)
72 field(:confirmation_pending, :boolean, default: false)
73 field(:password_reset_pending, :boolean, default: false)
74 field(:confirmation_token, :string, default: nil)
75 field(:default_scope, :string, default: "public")
76 field(:blocks, {:array, :string}, default: [])
77 field(:domain_blocks, {:array, :string}, default: [])
78 field(:mutes, {:array, :string}, default: [])
79 field(:muted_reblogs, {:array, :string}, default: [])
80 field(:muted_notifications, {:array, :string}, default: [])
81 field(:subscribers, {:array, :string}, default: [])
82 field(:deactivated, :boolean, default: false)
83 field(:no_rich_text, :boolean, default: false)
84 field(:ap_enabled, :boolean, default: false)
85 field(:is_moderator, :boolean, default: false)
86 field(:is_admin, :boolean, default: false)
87 field(:show_role, :boolean, default: true)
88 field(:settings, :map, default: nil)
89 field(:magic_key, :string, default: nil)
90 field(:uri, :string, default: nil)
91 field(:hide_followers_count, :boolean, default: false)
92 field(:hide_follows_count, :boolean, default: false)
93 field(:hide_followers, :boolean, default: false)
94 field(:hide_follows, :boolean, default: false)
95 field(:hide_favorites, :boolean, default: true)
96 field(:unread_conversation_count, :integer, default: 0)
97 field(:pinned_activities, {:array, :string}, default: [])
98 field(:email_notifications, :map, default: %{"digest" => false})
99 field(:mascot, :map, default: nil)
100 field(:emoji, {:array, :map}, default: [])
101 field(:pleroma_settings_store, :map, default: %{})
102 field(:fields, {:array, :map}, default: [])
103 field(:raw_fields, {:array, :map}, default: [])
104 field(:discoverable, :boolean, default: false)
105 field(:invisible, :boolean, default: false)
106 field(:allow_following_move, :boolean, default: true)
107 field(:skip_thread_containment, :boolean, default: false)
108 field(:also_known_as, {:array, :string}, default: [])
110 field(:notification_settings, :map,
114 "non_follows" => true,
115 "non_followers" => true
119 has_many(:notifications, Notification)
120 has_many(:registrations, Registration)
121 has_many(:deliveries, Delivery)
126 @doc "Returns if the user should be allowed to authenticate"
127 def auth_active?(%User{deactivated: true}), do: false
129 def auth_active?(%User{confirmation_pending: true}),
130 do: !Pleroma.Config.get([:instance, :account_activation_required])
132 def auth_active?(%User{}), do: true
134 def visible_for?(user, for_user \\ nil)
136 def visible_for?(%User{invisible: true}, _), do: false
138 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
140 def visible_for?(%User{} = user, for_user) do
141 auth_active?(user) || superuser?(for_user)
144 def visible_for?(_, _), do: false
146 def superuser?(%User{local: true, is_admin: true}), do: true
147 def superuser?(%User{local: true, is_moderator: true}), do: true
148 def superuser?(_), do: false
150 def invisible?(%User{invisible: true}), do: true
151 def invisible?(_), do: false
153 def avatar_url(user, options \\ []) do
155 %{"url" => [%{"href" => href} | _]} -> href
156 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
160 def banner_url(user, options \\ []) do
162 %{"url" => [%{"href" => href} | _]} -> href
163 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
167 def profile_url(%User{source_data: %{"url" => url}}), do: url
168 def profile_url(%User{ap_id: ap_id}), do: ap_id
169 def profile_url(_), do: nil
171 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
173 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
174 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
176 @spec ap_following(User.t()) :: Sring.t()
177 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
178 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
180 def follow_state(%User{} = user, %User{} = target) do
181 case Utils.fetch_latest_follow(user, target) do
182 %{data: %{"state" => state}} -> state
183 # Ideally this would be nil, but then Cachex does not commit the value
188 def get_cached_follow_state(user, target) do
189 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
190 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
193 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
194 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
195 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
198 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
199 def restrict_deactivated(query) do
200 from(u in query, where: u.deactivated != ^true)
203 defdelegate following_count(user), to: FollowingRelationship
205 defp truncate_fields_param(params) do
206 if Map.has_key?(params, :fields) do
207 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
213 defp truncate_if_exists(params, key, max_length) do
214 if Map.has_key?(params, key) and is_binary(params[key]) do
215 {value, _chopped} = String.split_at(params[key], max_length)
216 Map.put(params, key, value)
222 def remote_user_creation(params) do
223 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
224 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
228 |> truncate_if_exists(:name, name_limit)
229 |> truncate_if_exists(:bio, bio_limit)
230 |> truncate_fields_param()
250 :hide_followers_count,
260 |> validate_required([:name, :ap_id])
261 |> unique_constraint(:nickname)
262 |> validate_format(:nickname, @email_regex)
263 |> validate_length(:bio, max: bio_limit)
264 |> validate_length(:name, max: name_limit)
265 |> validate_fields(true)
267 case params[:source_data] do
268 %{"followers" => followers, "following" => following} ->
270 |> put_change(:follower_address, followers)
271 |> put_change(:following_address, following)
274 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
275 put_change(changeset, :follower_address, followers)
279 def update_changeset(struct, params \\ %{}) do
280 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
281 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
296 :hide_followers_count,
299 :allow_following_move,
302 :skip_thread_containment,
305 :pleroma_settings_store,
310 |> unique_constraint(:nickname)
311 |> validate_format(:nickname, local_nickname_regex())
312 |> validate_length(:bio, max: bio_limit)
313 |> validate_length(:name, min: 1, max: name_limit)
314 |> validate_fields(false)
317 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
318 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
319 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
321 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
323 params = if remote?, do: truncate_fields_param(params), else: params
345 :allow_following_move,
347 :hide_followers_count,
352 |> unique_constraint(:nickname)
353 |> validate_format(:nickname, local_nickname_regex())
354 |> validate_length(:bio, max: bio_limit)
355 |> validate_length(:name, max: name_limit)
356 |> validate_fields(remote?)
359 def password_update_changeset(struct, params) do
361 |> cast(params, [:password, :password_confirmation])
362 |> validate_required([:password, :password_confirmation])
363 |> validate_confirmation(:password)
364 |> put_password_hash()
365 |> put_change(:password_reset_pending, false)
368 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
369 def reset_password(%User{id: user_id} = user, data) do
372 |> Multi.update(:user, password_update_changeset(user, data))
373 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
374 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
376 case Repo.transaction(multi) do
377 {:ok, %{user: user} = _} -> set_cache(user)
378 {:error, _, changeset, _} -> {:error, changeset}
382 def update_password_reset_pending(user, value) do
385 |> put_change(:password_reset_pending, value)
386 |> update_and_set_cache()
389 def force_password_reset_async(user) do
390 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
393 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
394 def force_password_reset(user), do: update_password_reset_pending(user, true)
396 def register_changeset(struct, params \\ %{}, opts \\ []) do
397 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
398 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
401 if is_nil(opts[:need_confirmation]) do
402 Pleroma.Config.get([:instance, :account_activation_required])
404 opts[:need_confirmation]
408 |> confirmation_changeset(need_confirmation: need_confirmation?)
409 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
410 |> validate_required([:name, :nickname, :password, :password_confirmation])
411 |> validate_confirmation(:password)
412 |> unique_constraint(:email)
413 |> unique_constraint(:nickname)
414 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
415 |> validate_format(:nickname, local_nickname_regex())
416 |> validate_format(:email, @email_regex)
417 |> validate_length(:bio, max: bio_limit)
418 |> validate_length(:name, min: 1, max: name_limit)
419 |> maybe_validate_required_email(opts[:external])
422 |> unique_constraint(:ap_id)
423 |> put_following_and_follower_address()
426 def maybe_validate_required_email(changeset, true), do: changeset
427 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
429 defp put_ap_id(changeset) do
430 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
431 put_change(changeset, :ap_id, ap_id)
434 defp put_following_and_follower_address(changeset) do
435 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
438 |> put_change(:follower_address, followers)
441 defp autofollow_users(user) do
442 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
445 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
448 follow_all(user, autofollowed_users)
451 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
452 def register(%Ecto.Changeset{} = changeset) do
453 with {:ok, user} <- Repo.insert(changeset) do
454 post_register_action(user)
458 def post_register_action(%User{} = user) do
459 with {:ok, user} <- autofollow_users(user),
460 {:ok, user} <- set_cache(user),
461 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
462 {:ok, _} <- try_send_confirmation_email(user) do
467 def try_send_confirmation_email(%User{} = user) do
468 if user.confirmation_pending &&
469 Pleroma.Config.get([:instance, :account_activation_required]) do
471 |> Pleroma.Emails.UserEmail.account_confirmation_email()
472 |> Pleroma.Emails.Mailer.deliver_async()
480 def try_send_confirmation_email(users) do
481 Enum.each(users, &try_send_confirmation_email/1)
484 def needs_update?(%User{local: true}), do: false
486 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
488 def needs_update?(%User{local: false} = user) do
489 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
492 def needs_update?(_), do: true
494 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
495 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
496 follow(follower, followed, "pending")
499 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
500 follow(follower, followed)
503 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
504 if not ap_enabled?(followed) do
505 follow(follower, followed)
511 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
512 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
513 def follow_all(follower, followeds) do
515 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
516 |> Enum.each(&follow(follower, &1, "accept"))
521 defdelegate following(user), to: FollowingRelationship
523 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
524 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
527 followed.deactivated ->
528 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
530 deny_follow_blocked and blocks?(followed, follower) ->
531 {:error, "Could not follow user: #{followed.nickname} blocked you."}
534 FollowingRelationship.follow(follower, followed, state)
536 {:ok, _} = update_follower_count(followed)
539 |> update_following_count()
544 def unfollow(%User{} = follower, %User{} = followed) do
545 if following?(follower, followed) and follower.ap_id != followed.ap_id do
546 FollowingRelationship.unfollow(follower, followed)
548 {:ok, followed} = update_follower_count(followed)
552 |> update_following_count()
555 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
557 {:error, "Not subscribed!"}
561 defdelegate following?(follower, followed), to: FollowingRelationship
563 def locked?(%User{} = user) do
568 Repo.get_by(User, id: id)
571 def get_by_ap_id(ap_id) do
572 Repo.get_by(User, ap_id: ap_id)
575 def get_all_by_ap_id(ap_ids) do
576 from(u in __MODULE__,
577 where: u.ap_id in ^ap_ids
582 def get_all_by_ids(ids) do
583 from(u in __MODULE__, where: u.id in ^ids)
587 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
588 # of the ap_id and the domain and tries to get that user
589 def get_by_guessed_nickname(ap_id) do
590 domain = URI.parse(ap_id).host
591 name = List.last(String.split(ap_id, "/"))
592 nickname = "#{name}@#{domain}"
594 get_cached_by_nickname(nickname)
597 def set_cache({:ok, user}), do: set_cache(user)
598 def set_cache({:error, err}), do: {:error, err}
600 def set_cache(%User{} = user) do
601 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
602 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
606 def update_and_set_cache(struct, params) do
608 |> update_changeset(params)
609 |> update_and_set_cache()
612 def update_and_set_cache(changeset) do
613 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
618 def invalidate_cache(user) do
619 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
620 Cachex.del(:user_cache, "nickname:#{user.nickname}")
623 def get_cached_by_ap_id(ap_id) do
624 key = "ap_id:#{ap_id}"
625 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
628 def get_cached_by_id(id) do
632 Cachex.fetch!(:user_cache, key, fn _ ->
636 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
637 {:commit, user.ap_id}
643 get_cached_by_ap_id(ap_id)
646 def get_cached_by_nickname(nickname) do
647 key = "nickname:#{nickname}"
649 Cachex.fetch!(:user_cache, key, fn ->
650 case get_or_fetch_by_nickname(nickname) do
651 {:ok, user} -> {:commit, user}
652 {:error, _error} -> {:ignore, nil}
657 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
658 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
661 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
662 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
664 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
665 get_cached_by_nickname(nickname_or_id)
667 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
668 get_cached_by_nickname(nickname_or_id)
675 def get_by_nickname(nickname) do
676 Repo.get_by(User, nickname: nickname) ||
677 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
678 Repo.get_by(User, nickname: local_nickname(nickname))
682 def get_by_email(email), do: Repo.get_by(User, email: email)
684 def get_by_nickname_or_email(nickname_or_email) do
685 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
688 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
690 def get_or_fetch_by_nickname(nickname) do
691 with %User{} = user <- get_by_nickname(nickname) do
695 with [_nick, _domain] <- String.split(nickname, "@"),
696 {:ok, user} <- fetch_by_nickname(nickname) do
697 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
698 fetch_initial_posts(user)
703 _e -> {:error, "not found " <> nickname}
708 @doc "Fetch some posts when the user has just been federated with"
709 def fetch_initial_posts(user) do
710 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
713 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
714 def get_followers_query(%User{} = user, nil) do
715 User.Query.build(%{followers: user, deactivated: false})
718 def get_followers_query(user, page) do
720 |> get_followers_query(nil)
721 |> User.Query.paginate(page, 20)
724 @spec get_followers_query(User.t()) :: Ecto.Query.t()
725 def get_followers_query(user), do: get_followers_query(user, nil)
727 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
728 def get_followers(user, page \\ nil) do
730 |> get_followers_query(page)
734 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
735 def get_external_followers(user, page \\ nil) do
737 |> get_followers_query(page)
738 |> User.Query.build(%{external: true})
742 def get_followers_ids(user, page \\ nil) do
744 |> get_followers_query(page)
749 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
750 def get_friends_query(%User{} = user, nil) do
751 User.Query.build(%{friends: user, deactivated: false})
754 def get_friends_query(user, page) do
756 |> get_friends_query(nil)
757 |> User.Query.paginate(page, 20)
760 @spec get_friends_query(User.t()) :: Ecto.Query.t()
761 def get_friends_query(user), do: get_friends_query(user, nil)
763 def get_friends(user, page \\ nil) do
765 |> get_friends_query(page)
769 def get_friends_ids(user, page \\ nil) do
771 |> get_friends_query(page)
776 defdelegate get_follow_requests(user), to: FollowingRelationship
778 def increase_note_count(%User{} = user) do
780 |> where(id: ^user.id)
781 |> update([u], inc: [note_count: 1])
783 |> Repo.update_all([])
785 {1, [user]} -> set_cache(user)
790 def decrease_note_count(%User{} = user) do
792 |> where(id: ^user.id)
795 note_count: fragment("greatest(0, note_count - 1)")
799 |> Repo.update_all([])
801 {1, [user]} -> set_cache(user)
806 def update_note_count(%User{} = user, note_count \\ nil) do
811 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
817 |> cast(%{note_count: note_count}, [:note_count])
818 |> update_and_set_cache()
821 @spec maybe_fetch_follow_information(User.t()) :: User.t()
822 def maybe_fetch_follow_information(user) do
823 with {:ok, user} <- fetch_follow_information(user) do
827 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
833 def fetch_follow_information(user) do
834 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
836 |> follow_information_changeset(info)
837 |> update_and_set_cache()
841 defp follow_information_changeset(user, params) do
848 :hide_followers_count,
853 def update_follower_count(%User{} = user) do
854 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
855 follower_count_query =
856 User.Query.build(%{followers: user, deactivated: false})
857 |> select([u], %{count: count(u.id)})
860 |> where(id: ^user.id)
861 |> join(:inner, [u], s in subquery(follower_count_query))
863 set: [follower_count: s.count]
866 |> Repo.update_all([])
868 {1, [user]} -> set_cache(user)
872 {:ok, maybe_fetch_follow_information(user)}
876 @spec update_following_count(User.t()) :: User.t()
877 def update_following_count(%User{local: false} = user) do
878 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
879 maybe_fetch_follow_information(user)
885 def update_following_count(%User{local: true} = user) do
886 following_count = FollowingRelationship.following_count(user)
889 |> follow_information_changeset(%{following_count: following_count})
893 def set_unread_conversation_count(%User{local: true} = user) do
894 unread_query = Participation.unread_conversation_count_for_user(user)
897 |> join(:inner, [u], p in subquery(unread_query))
899 set: [unread_conversation_count: p.count]
901 |> where([u], u.id == ^user.id)
903 |> Repo.update_all([])
905 {1, [user]} -> set_cache(user)
910 def set_unread_conversation_count(user), do: {:ok, user}
912 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
914 Participation.unread_conversation_count_for_user(user)
915 |> where([p], p.conversation_id == ^conversation.id)
918 |> join(:inner, [u], p in subquery(unread_query))
920 inc: [unread_conversation_count: 1]
922 |> where([u], u.id == ^user.id)
923 |> where([u, p], p.count == 0)
925 |> Repo.update_all([])
927 {1, [user]} -> set_cache(user)
932 def increment_unread_conversation_count(_, user), do: {:ok, user}
934 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
935 def get_users_from_set(ap_ids, local_only \\ true) do
936 criteria = %{ap_id: ap_ids, deactivated: false}
937 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
939 User.Query.build(criteria)
943 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
944 def get_recipients_from_activity(%Activity{recipients: to}) do
945 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
949 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
950 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
951 add_to_mutes(muter, ap_id, notifications?)
954 def unmute(muter, %{ap_id: ap_id}) do
955 remove_from_mutes(muter, ap_id)
958 def subscribe(subscriber, %{ap_id: ap_id}) do
959 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
960 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
962 if blocks?(subscribed, subscriber) and deny_follow_blocked do
963 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
965 User.add_to_subscribers(subscribed, subscriber.ap_id)
970 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
971 with %User{} = user <- get_cached_by_ap_id(ap_id) do
972 User.remove_from_subscribers(user, unsubscriber.ap_id)
976 def block(blocker, %User{ap_id: ap_id} = blocked) do
977 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
979 if following?(blocker, blocked) do
980 {:ok, blocker, _} = unfollow(blocker, blocked)
986 # clear any requested follows as well
988 case CommonAPI.reject_follow_request(blocked, blocker) do
989 {:ok, %User{} = updated_blocked} -> updated_blocked
994 if subscribed_to?(blocked, blocker) do
995 {:ok, blocker} = unsubscribe(blocked, blocker)
1001 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1003 {:ok, blocker} = update_follower_count(blocker)
1004 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1005 add_to_block(blocker, ap_id)
1008 # helper to handle the block given only an actor's AP id
1009 def block(blocker, %{ap_id: ap_id}) do
1010 block(blocker, get_cached_by_ap_id(ap_id))
1013 def unblock(blocker, %{ap_id: ap_id}) do
1014 remove_from_block(blocker, ap_id)
1017 def mutes?(nil, _), do: false
1018 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1020 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1021 def muted_notifications?(nil, _), do: false
1023 def muted_notifications?(user, %{ap_id: ap_id}),
1024 do: Enum.member?(user.muted_notifications, ap_id)
1026 def blocks?(%User{} = user, %User{} = target) do
1027 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1030 def blocks?(nil, _), do: false
1032 def blocks_ap_id?(%User{} = user, %User{} = target) do
1033 Enum.member?(user.blocks, target.ap_id)
1036 def blocks_ap_id?(_, _), do: false
1038 def blocks_domain?(%User{} = user, %User{} = target) do
1039 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1040 %{host: host} = URI.parse(target.ap_id)
1041 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1044 def blocks_domain?(_, _), do: false
1046 def subscribed_to?(user, %{ap_id: ap_id}) do
1047 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1048 Enum.member?(target.subscribers, user.ap_id)
1052 @spec muted_users(User.t()) :: [User.t()]
1053 def muted_users(user) do
1054 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1058 @spec blocked_users(User.t()) :: [User.t()]
1059 def blocked_users(user) do
1060 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1064 @spec subscribers(User.t()) :: [User.t()]
1065 def subscribers(user) do
1066 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1070 def deactivate_async(user, status \\ true) do
1071 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1074 def deactivate(user, status \\ true)
1076 def deactivate(users, status) when is_list(users) do
1077 Repo.transaction(fn ->
1078 for user <- users, do: deactivate(user, status)
1082 def deactivate(%User{} = user, status) do
1083 with {:ok, user} <- set_activation_status(user, status) do
1086 |> Enum.filter(& &1.local)
1087 |> Enum.each(fn follower ->
1088 follower |> update_following_count() |> set_cache()
1091 # Only update local user counts, remote will be update during the next pull.
1094 |> Enum.filter(& &1.local)
1095 |> Enum.each(&update_follower_count/1)
1101 def update_notification_settings(%User{} = user, settings) do
1104 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1107 notification_settings =
1108 user.notification_settings
1109 |> Map.merge(settings)
1110 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1112 params = %{notification_settings: notification_settings}
1115 |> cast(params, [:notification_settings])
1116 |> validate_required([:notification_settings])
1117 |> update_and_set_cache()
1120 def delete(users) when is_list(users) do
1121 for user <- users, do: delete(user)
1124 def delete(%User{} = user) do
1125 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1128 def perform(:force_password_reset, user), do: force_password_reset(user)
1130 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1131 def perform(:delete, %User{} = user) do
1132 {:ok, _user} = ActivityPub.delete(user)
1134 # Remove all relationships
1137 |> Enum.each(fn follower ->
1138 ActivityPub.unfollow(follower, user)
1139 unfollow(follower, user)
1144 |> Enum.each(fn followed ->
1145 ActivityPub.unfollow(user, followed)
1146 unfollow(user, followed)
1149 delete_user_activities(user)
1150 invalidate_cache(user)
1154 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1155 def perform(:fetch_initial_posts, %User{} = user) do
1156 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1158 # Insert all the posts in reverse order, so they're in the right order on the timeline
1159 user.source_data["outbox"]
1160 |> Utils.fetch_ordered_collection(pages)
1162 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1165 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1167 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1168 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1169 when is_list(blocked_identifiers) do
1171 blocked_identifiers,
1172 fn blocked_identifier ->
1173 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1174 {:ok, blocker} <- block(blocker, blocked),
1175 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1179 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1186 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1187 def perform(:follow_import, %User{} = follower, followed_identifiers)
1188 when is_list(followed_identifiers) do
1190 followed_identifiers,
1191 fn followed_identifier ->
1192 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1193 {:ok, follower} <- maybe_direct_follow(follower, followed),
1194 {:ok, _} <- ActivityPub.follow(follower, followed) do
1198 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1205 @spec external_users_query() :: Ecto.Query.t()
1206 def external_users_query do
1214 @spec external_users(keyword()) :: [User.t()]
1215 def external_users(opts \\ []) do
1217 external_users_query()
1218 |> select([u], struct(u, [:id, :ap_id]))
1222 do: where(query, [u], u.id > ^opts[:max_id]),
1227 do: limit(query, ^opts[:limit]),
1233 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1234 BackgroundWorker.enqueue("blocks_import", %{
1235 "blocker_id" => blocker.id,
1236 "blocked_identifiers" => blocked_identifiers
1240 def follow_import(%User{} = follower, followed_identifiers)
1241 when is_list(followed_identifiers) do
1242 BackgroundWorker.enqueue("follow_import", %{
1243 "follower_id" => follower.id,
1244 "followed_identifiers" => followed_identifiers
1248 def delete_user_activities(%User{ap_id: ap_id}) do
1250 |> Activity.Queries.by_actor()
1251 |> RepoStreamer.chunk_stream(50)
1252 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1256 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1258 |> Object.normalize()
1259 |> ActivityPub.delete()
1262 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1263 object = Object.normalize(activity)
1266 |> get_cached_by_ap_id()
1267 |> ActivityPub.unlike(object)
1270 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1271 object = Object.normalize(activity)
1274 |> get_cached_by_ap_id()
1275 |> ActivityPub.unannounce(object)
1278 defp delete_activity(_activity), do: "Doing nothing"
1280 def html_filter_policy(%User{no_rich_text: true}) do
1281 Pleroma.HTML.Scrubber.TwitterText
1284 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1286 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1288 def get_or_fetch_by_ap_id(ap_id) do
1289 user = get_cached_by_ap_id(ap_id)
1291 if !is_nil(user) and !needs_update?(user) do
1294 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1295 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1297 resp = fetch_by_ap_id(ap_id)
1299 if should_fetch_initial do
1300 with {:ok, %User{} = user} <- resp do
1301 fetch_initial_posts(user)
1310 Creates an internal service actor by URI if missing.
1311 Optionally takes nickname for addressing.
1313 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1314 with user when is_nil(user) <- get_cached_by_ap_id(uri) do
1321 follower_address: uri <> "/followers"
1330 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1333 |> :public_key.pem_decode()
1335 |> :public_key.pem_entry_decode()
1340 def public_key(_), do: {:error, "not found key"}
1342 def get_public_key_for_ap_id(ap_id) do
1343 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1344 {:ok, public_key} <- public_key(user) do
1351 defp blank?(""), do: nil
1352 defp blank?(n), do: n
1354 def insert_or_update_user(data) do
1356 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1357 |> remote_user_creation()
1358 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1362 def ap_enabled?(%User{local: true}), do: true
1363 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1364 def ap_enabled?(_), do: false
1366 @doc "Gets or fetch a user by uri or nickname."
1367 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1368 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1369 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1371 # wait a period of time and return newest version of the User structs
1372 # this is because we have synchronous follow APIs and need to simulate them
1373 # with an async handshake
1374 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1375 with %User{} = a <- get_cached_by_id(a.id),
1376 %User{} = b <- get_cached_by_id(b.id) do
1383 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1384 with :ok <- :timer.sleep(timeout),
1385 %User{} = a <- get_cached_by_id(a.id),
1386 %User{} = b <- get_cached_by_id(b.id) do
1393 def parse_bio(bio) when is_binary(bio) and bio != "" do
1395 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1399 def parse_bio(_), do: ""
1401 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1402 # TODO: get profile URLs other than user.ap_id
1403 profile_urls = [user.ap_id]
1406 |> CommonUtils.format_input("text/plain",
1407 mentions_format: :full,
1408 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1413 def parse_bio(_, _), do: ""
1415 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1416 Repo.transaction(fn ->
1417 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1421 def tag(nickname, tags) when is_binary(nickname),
1422 do: tag(get_by_nickname(nickname), tags)
1424 def tag(%User{} = user, tags),
1425 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1427 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1428 Repo.transaction(fn ->
1429 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1433 def untag(nickname, tags) when is_binary(nickname),
1434 do: untag(get_by_nickname(nickname), tags)
1436 def untag(%User{} = user, tags),
1437 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1439 defp update_tags(%User{} = user, new_tags) do
1440 {:ok, updated_user} =
1442 |> change(%{tags: new_tags})
1443 |> update_and_set_cache()
1448 defp normalize_tags(tags) do
1451 |> Enum.map(&String.downcase/1)
1454 defp local_nickname_regex do
1455 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1456 @extended_local_nickname_regex
1458 @strict_local_nickname_regex
1462 def local_nickname(nickname_or_mention) do
1465 |> String.split("@")
1469 def full_nickname(nickname_or_mention),
1470 do: String.trim_leading(nickname_or_mention, "@")
1472 def error_user(ap_id) do
1476 nickname: "erroruser@example.com",
1477 inserted_at: NaiveDateTime.utc_now()
1481 @spec all_superusers() :: [User.t()]
1482 def all_superusers do
1483 User.Query.build(%{super_users: true, local: true, deactivated: false})
1487 def showing_reblogs?(%User{} = user, %User{} = target) do
1488 target.ap_id not in user.muted_reblogs
1492 The function returns a query to get users with no activity for given interval of days.
1493 Inactive users are those who didn't read any notification, or had any activity where
1494 the user is the activity's actor, during `inactivity_threshold` days.
1495 Deactivated users will not appear in this list.
1499 iex> Pleroma.User.list_inactive_users()
1502 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1503 def list_inactive_users_query(inactivity_threshold \\ 7) do
1504 negative_inactivity_threshold = -inactivity_threshold
1505 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1506 # Subqueries are not supported in `where` clauses, join gets too complicated.
1507 has_read_notifications =
1508 from(n in Pleroma.Notification,
1509 where: n.seen == true,
1511 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1514 |> Pleroma.Repo.all()
1516 from(u in Pleroma.User,
1517 left_join: a in Pleroma.Activity,
1518 on: u.ap_id == a.actor,
1519 where: not is_nil(u.nickname),
1520 where: u.deactivated != ^true,
1521 where: u.id not in ^has_read_notifications,
1524 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1525 is_nil(max(a.inserted_at))
1530 Enable or disable email notifications for user
1534 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1535 Pleroma.User{email_notifications: %{"digest" => true}}
1537 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1538 Pleroma.User{email_notifications: %{"digest" => false}}
1540 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1541 {:ok, t()} | {:error, Ecto.Changeset.t()}
1542 def switch_email_notifications(user, type, status) do
1543 User.update_email_notifications(user, %{type => status})
1547 Set `last_digest_emailed_at` value for the user to current time
1549 @spec touch_last_digest_emailed_at(t()) :: t()
1550 def touch_last_digest_emailed_at(user) do
1551 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1553 {:ok, updated_user} =
1555 |> change(%{last_digest_emailed_at: now})
1556 |> update_and_set_cache()
1561 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1562 def toggle_confirmation(%User{} = user) do
1564 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1565 |> update_and_set_cache()
1568 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1569 def toggle_confirmation(users) do
1570 Enum.map(users, &toggle_confirmation/1)
1573 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1577 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1578 # use instance-default
1579 config = Pleroma.Config.get([:assets, :mascots])
1580 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1581 mascot = Keyword.get(config, default_mascot)
1584 "id" => "default-mascot",
1585 "url" => mascot[:url],
1586 "preview_url" => mascot[:url],
1588 "mime_type" => mascot[:mime_type]
1593 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1595 def ensure_keys_present(%User{} = user) do
1596 with {:ok, pem} <- Keys.generate_rsa_pem() do
1598 |> cast(%{keys: pem}, [:keys])
1599 |> validate_required([:keys])
1600 |> update_and_set_cache()
1604 def get_ap_ids_by_nicknames(nicknames) do
1606 where: u.nickname in ^nicknames,
1612 defdelegate search(query, opts \\ []), to: User.Search
1614 defp put_password_hash(
1615 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1617 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1620 defp put_password_hash(changeset), do: changeset
1622 def is_internal_user?(%User{nickname: nil}), do: true
1623 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1624 def is_internal_user?(_), do: false
1626 # A hack because user delete activities have a fake id for whatever reason
1627 # TODO: Get rid of this
1628 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1630 def get_delivered_users_by_object_id(object_id) do
1632 inner_join: delivery in assoc(u, :deliveries),
1633 where: delivery.object_id == ^object_id
1638 def change_email(user, email) do
1640 |> cast(%{email: email}, [:email])
1641 |> validate_required([:email])
1642 |> unique_constraint(:email)
1643 |> validate_format(:email, @email_regex)
1644 |> update_and_set_cache()
1647 # Internal function; public one is `deactivate/2`
1648 defp set_activation_status(user, deactivated) do
1650 |> cast(%{deactivated: deactivated}, [:deactivated])
1651 |> update_and_set_cache()
1654 def update_banner(user, banner) do
1656 |> cast(%{banner: banner}, [:banner])
1657 |> update_and_set_cache()
1660 def update_background(user, background) do
1662 |> cast(%{background: background}, [:background])
1663 |> update_and_set_cache()
1666 def update_source_data(user, source_data) do
1668 |> cast(%{source_data: source_data}, [:source_data])
1669 |> update_and_set_cache()
1672 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1675 moderator: is_moderator
1679 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1680 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1681 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1682 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1685 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1686 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1690 def fields(%{fields: nil}), do: []
1692 def fields(%{fields: fields}), do: fields
1694 def validate_fields(changeset, remote? \\ false) do
1695 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1696 limit = Pleroma.Config.get([:instance, limit_name], 0)
1699 |> validate_length(:fields, max: limit)
1700 |> validate_change(:fields, fn :fields, fields ->
1701 if Enum.all?(fields, &valid_field?/1) do
1709 defp valid_field?(%{"name" => name, "value" => value}) do
1710 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1711 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1713 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1714 String.length(value) <= value_limit
1717 defp valid_field?(_), do: false
1719 defp truncate_field(%{"name" => name, "value" => value}) do
1721 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1724 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1726 %{"name" => name, "value" => value}
1729 def admin_api_update(user, params) do
1736 |> update_and_set_cache()
1739 def mascot_update(user, url) do
1741 |> cast(%{mascot: url}, [:mascot])
1742 |> validate_required([:mascot])
1743 |> update_and_set_cache()
1746 def mastodon_settings_update(user, settings) do
1748 |> cast(%{settings: settings}, [:settings])
1749 |> validate_required([:settings])
1750 |> update_and_set_cache()
1753 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1754 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1756 if need_confirmation? do
1758 confirmation_pending: true,
1759 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1763 confirmation_pending: false,
1764 confirmation_token: nil
1768 cast(user, params, [:confirmation_pending, :confirmation_token])
1771 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1772 if id not in user.pinned_activities do
1773 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1774 params = %{pinned_activities: user.pinned_activities ++ [id]}
1777 |> cast(params, [:pinned_activities])
1778 |> validate_length(:pinned_activities,
1779 max: max_pinned_statuses,
1780 message: "You have already pinned the maximum number of statuses"
1785 |> update_and_set_cache()
1788 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1789 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1792 |> cast(params, [:pinned_activities])
1793 |> update_and_set_cache()
1796 def update_email_notifications(user, settings) do
1797 email_notifications =
1798 user.email_notifications
1799 |> Map.merge(settings)
1800 |> Map.take(["digest"])
1802 params = %{email_notifications: email_notifications}
1803 fields = [:email_notifications]
1806 |> cast(params, fields)
1807 |> validate_required(fields)
1808 |> update_and_set_cache()
1811 defp set_subscribers(user, subscribers) do
1812 params = %{subscribers: subscribers}
1815 |> cast(params, [:subscribers])
1816 |> validate_required([:subscribers])
1817 |> update_and_set_cache()
1820 def add_to_subscribers(user, subscribed) do
1821 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1824 def remove_from_subscribers(user, subscribed) do
1825 set_subscribers(user, List.delete(user.subscribers, subscribed))
1828 defp set_domain_blocks(user, domain_blocks) do
1829 params = %{domain_blocks: domain_blocks}
1832 |> cast(params, [:domain_blocks])
1833 |> validate_required([:domain_blocks])
1834 |> update_and_set_cache()
1837 def block_domain(user, domain_blocked) do
1838 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1841 def unblock_domain(user, domain_blocked) do
1842 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1845 defp set_blocks(user, blocks) do
1846 params = %{blocks: blocks}
1849 |> cast(params, [:blocks])
1850 |> validate_required([:blocks])
1851 |> update_and_set_cache()
1854 def add_to_block(user, blocked) do
1855 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1858 def remove_from_block(user, blocked) do
1859 set_blocks(user, List.delete(user.blocks, blocked))
1862 defp set_mutes(user, mutes) do
1863 params = %{mutes: mutes}
1866 |> cast(params, [:mutes])
1867 |> validate_required([:mutes])
1868 |> update_and_set_cache()
1871 def add_to_mutes(user, muted, notifications?) do
1872 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1873 set_notification_mutes(
1875 Enum.uniq([muted | user.muted_notifications]),
1881 def remove_from_mutes(user, muted) do
1882 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1883 set_notification_mutes(
1885 List.delete(user.muted_notifications, muted),
1891 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1895 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1896 params = %{muted_notifications: muted_notifications}
1899 |> cast(params, [:muted_notifications])
1900 |> validate_required([:muted_notifications])
1901 |> update_and_set_cache()
1904 def add_reblog_mute(user, ap_id) do
1905 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1908 |> cast(params, [:muted_reblogs])
1909 |> update_and_set_cache()
1912 def remove_reblog_mute(user, ap_id) do
1913 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1916 |> cast(params, [:muted_reblogs])
1917 |> update_and_set_cache()
1920 def set_invisible(user, invisible) do
1921 params = %{invisible: invisible}
1924 |> cast(params, [:invisible])
1925 |> validate_required([:invisible])
1926 |> update_and_set_cache()