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(:skip_thread_containment, :boolean, default: false)
108 field(:notification_settings, :map,
112 "non_follows" => true,
113 "non_followers" => true
117 has_many(:notifications, Notification)
118 has_many(:registrations, Registration)
119 has_many(:deliveries, Delivery)
121 field(:info, :map, default: %{})
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 |> Map.put(:info, params[:info] || %{})
229 |> truncate_if_exists(:name, name_limit)
230 |> truncate_if_exists(:bio, bio_limit)
231 |> truncate_fields_param()
251 :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,
301 :skip_thread_containment,
304 :pleroma_settings_store,
308 |> unique_constraint(:nickname)
309 |> validate_format(:nickname, local_nickname_regex())
310 |> validate_length(:bio, max: bio_limit)
311 |> validate_length(:name, min: 1, max: name_limit)
312 |> validate_fields(false)
315 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
316 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
317 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
319 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
321 params = if remote?, do: truncate_fields_param(params), else: params
344 :hide_followers_count,
348 |> unique_constraint(:nickname)
349 |> validate_format(:nickname, local_nickname_regex())
350 |> validate_length(:bio, max: bio_limit)
351 |> validate_length(:name, max: name_limit)
352 |> validate_fields(remote?)
355 def password_update_changeset(struct, params) do
357 |> cast(params, [:password, :password_confirmation])
358 |> validate_required([:password, :password_confirmation])
359 |> validate_confirmation(:password)
360 |> put_password_hash()
361 |> put_change(:password_reset_pending, false)
364 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
365 def reset_password(%User{id: user_id} = user, data) do
368 |> Multi.update(:user, password_update_changeset(user, data))
369 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
370 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
372 case Repo.transaction(multi) do
373 {:ok, %{user: user} = _} -> set_cache(user)
374 {:error, _, changeset, _} -> {:error, changeset}
378 def update_password_reset_pending(user, value) do
381 |> put_change(:password_reset_pending, value)
382 |> update_and_set_cache()
385 def force_password_reset_async(user) do
386 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
389 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
390 def force_password_reset(user), do: update_password_reset_pending(user, true)
392 def register_changeset(struct, params \\ %{}, opts \\ []) do
393 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
394 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
397 if is_nil(opts[:need_confirmation]) do
398 Pleroma.Config.get([:instance, :account_activation_required])
400 opts[:need_confirmation]
404 |> confirmation_changeset(need_confirmation: need_confirmation?)
405 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
406 |> validate_required([:name, :nickname, :password, :password_confirmation])
407 |> validate_confirmation(:password)
408 |> unique_constraint(:email)
409 |> unique_constraint(:nickname)
410 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
411 |> validate_format(:nickname, local_nickname_regex())
412 |> validate_format(:email, @email_regex)
413 |> validate_length(:bio, max: bio_limit)
414 |> validate_length(:name, min: 1, max: name_limit)
415 |> maybe_validate_required_email(opts[:external])
418 |> unique_constraint(:ap_id)
419 |> put_following_and_follower_address()
422 def maybe_validate_required_email(changeset, true), do: changeset
423 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
425 defp put_ap_id(changeset) do
426 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
427 put_change(changeset, :ap_id, ap_id)
430 defp put_following_and_follower_address(changeset) do
431 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
434 |> put_change(:follower_address, followers)
437 defp autofollow_users(user) do
438 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
441 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
444 follow_all(user, autofollowed_users)
447 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
448 def register(%Ecto.Changeset{} = changeset) do
449 with {:ok, user} <- Repo.insert(changeset) do
450 post_register_action(user)
454 def post_register_action(%User{} = user) do
455 with {:ok, user} <- autofollow_users(user),
456 {:ok, user} <- set_cache(user),
457 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
458 {:ok, _} <- try_send_confirmation_email(user) do
463 def try_send_confirmation_email(%User{} = user) do
464 if user.confirmation_pending &&
465 Pleroma.Config.get([:instance, :account_activation_required]) do
467 |> Pleroma.Emails.UserEmail.account_confirmation_email()
468 |> Pleroma.Emails.Mailer.deliver_async()
476 def try_send_confirmation_email(users) do
477 Enum.each(users, &try_send_confirmation_email/1)
480 def needs_update?(%User{local: true}), do: false
482 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
484 def needs_update?(%User{local: false} = user) do
485 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
488 def needs_update?(_), do: true
490 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
491 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
492 follow(follower, followed, "pending")
495 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
496 follow(follower, followed)
499 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
500 if not ap_enabled?(followed) do
501 follow(follower, followed)
507 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
508 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
509 def follow_all(follower, followeds) do
511 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
512 |> Enum.each(&follow(follower, &1, "accept"))
517 defdelegate following(user), to: FollowingRelationship
519 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
520 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
523 followed.deactivated ->
524 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
526 deny_follow_blocked and blocks?(followed, follower) ->
527 {:error, "Could not follow user: #{followed.nickname} blocked you."}
530 FollowingRelationship.follow(follower, followed, state)
532 {:ok, _} = update_follower_count(followed)
535 |> update_following_count()
540 def unfollow(%User{} = follower, %User{} = followed) do
541 if following?(follower, followed) and follower.ap_id != followed.ap_id do
542 FollowingRelationship.unfollow(follower, followed)
544 {:ok, followed} = update_follower_count(followed)
548 |> update_following_count()
551 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
553 {:error, "Not subscribed!"}
557 defdelegate following?(follower, followed), to: FollowingRelationship
559 def locked?(%User{} = user) do
564 Repo.get_by(User, id: id)
567 def get_by_ap_id(ap_id) do
568 Repo.get_by(User, ap_id: ap_id)
571 def get_all_by_ap_id(ap_ids) do
572 from(u in __MODULE__,
573 where: u.ap_id in ^ap_ids
578 def get_all_by_ids(ids) do
579 from(u in __MODULE__, where: u.id in ^ids)
583 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
584 # of the ap_id and the domain and tries to get that user
585 def get_by_guessed_nickname(ap_id) do
586 domain = URI.parse(ap_id).host
587 name = List.last(String.split(ap_id, "/"))
588 nickname = "#{name}@#{domain}"
590 get_cached_by_nickname(nickname)
593 def set_cache({:ok, user}), do: set_cache(user)
594 def set_cache({:error, err}), do: {:error, err}
596 def set_cache(%User{} = user) do
597 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
598 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
602 def update_and_set_cache(struct, params) do
604 |> update_changeset(params)
605 |> update_and_set_cache()
608 def update_and_set_cache(changeset) do
609 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
614 def invalidate_cache(user) do
615 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
616 Cachex.del(:user_cache, "nickname:#{user.nickname}")
619 def get_cached_by_ap_id(ap_id) do
620 key = "ap_id:#{ap_id}"
621 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
624 def get_cached_by_id(id) do
628 Cachex.fetch!(:user_cache, key, fn _ ->
632 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
633 {:commit, user.ap_id}
639 get_cached_by_ap_id(ap_id)
642 def get_cached_by_nickname(nickname) do
643 key = "nickname:#{nickname}"
645 Cachex.fetch!(:user_cache, key, fn ->
646 case get_or_fetch_by_nickname(nickname) do
647 {:ok, user} -> {:commit, user}
648 {:error, _error} -> {:ignore, nil}
653 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
654 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
657 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
658 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
660 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
661 get_cached_by_nickname(nickname_or_id)
663 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
664 get_cached_by_nickname(nickname_or_id)
671 def get_by_nickname(nickname) do
672 Repo.get_by(User, nickname: nickname) ||
673 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
674 Repo.get_by(User, nickname: local_nickname(nickname))
678 def get_by_email(email), do: Repo.get_by(User, email: email)
680 def get_by_nickname_or_email(nickname_or_email) do
681 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
684 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
686 def get_or_fetch_by_nickname(nickname) do
687 with %User{} = user <- get_by_nickname(nickname) do
691 with [_nick, _domain] <- String.split(nickname, "@"),
692 {:ok, user} <- fetch_by_nickname(nickname) do
693 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
694 fetch_initial_posts(user)
699 _e -> {:error, "not found " <> nickname}
704 @doc "Fetch some posts when the user has just been federated with"
705 def fetch_initial_posts(user) do
706 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
709 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
710 def get_followers_query(%User{} = user, nil) do
711 User.Query.build(%{followers: user, deactivated: false})
714 def get_followers_query(user, page) do
716 |> get_followers_query(nil)
717 |> User.Query.paginate(page, 20)
720 @spec get_followers_query(User.t()) :: Ecto.Query.t()
721 def get_followers_query(user), do: get_followers_query(user, nil)
723 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
724 def get_followers(user, page \\ nil) do
726 |> get_followers_query(page)
730 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
731 def get_external_followers(user, page \\ nil) do
733 |> get_followers_query(page)
734 |> User.Query.build(%{external: true})
738 def get_followers_ids(user, page \\ nil) do
740 |> get_followers_query(page)
745 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
746 def get_friends_query(%User{} = user, nil) do
747 User.Query.build(%{friends: user, deactivated: false})
750 def get_friends_query(user, page) do
752 |> get_friends_query(nil)
753 |> User.Query.paginate(page, 20)
756 @spec get_friends_query(User.t()) :: Ecto.Query.t()
757 def get_friends_query(user), do: get_friends_query(user, nil)
759 def get_friends(user, page \\ nil) do
761 |> get_friends_query(page)
765 def get_friends_ap_ids(user) do
767 |> get_friends_query(nil)
768 |> select([u], u.ap_id)
772 def get_friends_ids(user, page \\ nil) do
774 |> get_friends_query(page)
779 defdelegate get_follow_requests(user), to: FollowingRelationship
781 def increase_note_count(%User{} = user) do
783 |> where(id: ^user.id)
784 |> update([u], inc: [note_count: 1])
786 |> Repo.update_all([])
788 {1, [user]} -> set_cache(user)
793 def decrease_note_count(%User{} = user) do
795 |> where(id: ^user.id)
798 note_count: fragment("greatest(0, note_count - 1)")
802 |> Repo.update_all([])
804 {1, [user]} -> set_cache(user)
809 def update_note_count(%User{} = user, note_count \\ nil) do
814 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
820 |> cast(%{note_count: note_count}, [:note_count])
821 |> update_and_set_cache()
824 @spec maybe_fetch_follow_information(User.t()) :: User.t()
825 def maybe_fetch_follow_information(user) do
826 with {:ok, user} <- fetch_follow_information(user) do
830 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
836 def fetch_follow_information(user) do
837 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
839 |> follow_information_changeset(info)
840 |> update_and_set_cache()
844 defp follow_information_changeset(user, params) do
851 :hide_followers_count,
856 def update_follower_count(%User{} = user) do
857 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
858 follower_count_query =
859 User.Query.build(%{followers: user, deactivated: false})
860 |> select([u], %{count: count(u.id)})
863 |> where(id: ^user.id)
864 |> join(:inner, [u], s in subquery(follower_count_query))
866 set: [follower_count: s.count]
869 |> Repo.update_all([])
871 {1, [user]} -> set_cache(user)
875 {:ok, maybe_fetch_follow_information(user)}
879 @spec update_following_count(User.t()) :: User.t()
880 def update_following_count(%User{local: false} = user) do
881 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
882 maybe_fetch_follow_information(user)
888 def update_following_count(%User{local: true} = user) do
889 following_count = FollowingRelationship.following_count(user)
892 |> follow_information_changeset(%{following_count: following_count})
896 def set_unread_conversation_count(%User{local: true} = user) do
897 unread_query = Participation.unread_conversation_count_for_user(user)
900 |> join(:inner, [u], p in subquery(unread_query))
902 set: [unread_conversation_count: p.count]
904 |> where([u], u.id == ^user.id)
906 |> Repo.update_all([])
908 {1, [user]} -> set_cache(user)
913 def set_unread_conversation_count(user), do: {:ok, user}
915 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
917 Participation.unread_conversation_count_for_user(user)
918 |> where([p], p.conversation_id == ^conversation.id)
921 |> join(:inner, [u], p in subquery(unread_query))
923 inc: [unread_conversation_count: 1]
925 |> where([u], u.id == ^user.id)
926 |> where([u, p], p.count == 0)
928 |> Repo.update_all([])
930 {1, [user]} -> set_cache(user)
935 def increment_unread_conversation_count(_, user), do: {:ok, user}
937 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
938 def get_users_from_set(ap_ids, local_only \\ true) do
939 criteria = %{ap_id: ap_ids, deactivated: false}
940 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
942 User.Query.build(criteria)
946 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
947 def get_recipients_from_activity(%Activity{recipients: to}) do
948 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
952 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
953 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
954 add_to_mutes(muter, ap_id, notifications?)
957 def unmute(muter, %{ap_id: ap_id}) do
958 remove_from_mutes(muter, ap_id)
961 def subscribe(subscriber, %{ap_id: ap_id}) do
962 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
963 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
965 if blocks?(subscribed, subscriber) and deny_follow_blocked do
966 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
968 User.add_to_subscribers(subscribed, subscriber.ap_id)
973 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
974 with %User{} = user <- get_cached_by_ap_id(ap_id) do
975 User.remove_from_subscribers(user, unsubscriber.ap_id)
979 def block(blocker, %User{ap_id: ap_id} = blocked) do
980 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
982 if following?(blocker, blocked) do
983 {:ok, blocker, _} = unfollow(blocker, blocked)
989 # clear any requested follows as well
991 case CommonAPI.reject_follow_request(blocked, blocker) do
992 {:ok, %User{} = updated_blocked} -> updated_blocked
997 if subscribed_to?(blocked, blocker) do
998 {:ok, blocker} = unsubscribe(blocked, blocker)
1004 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1006 {:ok, blocker} = update_follower_count(blocker)
1007 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1008 add_to_block(blocker, ap_id)
1011 # helper to handle the block given only an actor's AP id
1012 def block(blocker, %{ap_id: ap_id}) do
1013 block(blocker, get_cached_by_ap_id(ap_id))
1016 def unblock(blocker, %{ap_id: ap_id}) do
1017 remove_from_block(blocker, ap_id)
1020 def mutes?(nil, _), do: false
1021 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1023 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1024 def muted_notifications?(nil, _), do: false
1026 def muted_notifications?(user, %{ap_id: ap_id}),
1027 do: Enum.member?(user.muted_notifications, ap_id)
1029 def blocks?(%User{} = user, %User{} = target) do
1030 blocks_ap_id?(user, target) ||
1031 (!User.following?(user, target) && blocks_domain?(user, target))
1034 def blocks?(nil, _), do: false
1036 def blocks_ap_id?(%User{} = user, %User{} = target) do
1037 Enum.member?(user.blocks, target.ap_id)
1040 def blocks_ap_id?(_, _), do: false
1042 def blocks_domain?(%User{} = user, %User{} = target) do
1043 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1044 %{host: host} = URI.parse(target.ap_id)
1045 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1048 def blocks_domain?(_, _), do: false
1050 def subscribed_to?(user, %{ap_id: ap_id}) do
1051 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1052 Enum.member?(target.subscribers, user.ap_id)
1056 @spec muted_users(User.t()) :: [User.t()]
1057 def muted_users(user) do
1058 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1062 @spec blocked_users(User.t()) :: [User.t()]
1063 def blocked_users(user) do
1064 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1068 @spec subscribers(User.t()) :: [User.t()]
1069 def subscribers(user) do
1070 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1074 def deactivate_async(user, status \\ true) do
1075 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1078 def deactivate(user, status \\ true)
1080 def deactivate(users, status) when is_list(users) do
1081 Repo.transaction(fn ->
1082 for user <- users, do: deactivate(user, status)
1086 def deactivate(%User{} = user, status) do
1087 with {:ok, user} <- set_activation_status(user, status) do
1090 |> Enum.filter(& &1.local)
1091 |> Enum.each(fn follower ->
1092 follower |> update_following_count() |> set_cache()
1095 # Only update local user counts, remote will be update during the next pull.
1098 |> Enum.filter(& &1.local)
1099 |> Enum.each(&update_follower_count/1)
1105 def update_notification_settings(%User{} = user, settings) do
1108 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1111 notification_settings =
1112 user.notification_settings
1113 |> Map.merge(settings)
1114 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1116 params = %{notification_settings: notification_settings}
1119 |> cast(params, [:notification_settings])
1120 |> validate_required([:notification_settings])
1121 |> update_and_set_cache()
1124 def delete(users) when is_list(users) do
1125 for user <- users, do: delete(user)
1128 def delete(%User{} = user) do
1129 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1132 def perform(:force_password_reset, user), do: force_password_reset(user)
1134 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1135 def perform(:delete, %User{} = user) do
1136 {:ok, _user} = ActivityPub.delete(user)
1138 # Remove all relationships
1141 |> Enum.each(fn follower ->
1142 ActivityPub.unfollow(follower, user)
1143 unfollow(follower, user)
1148 |> Enum.each(fn followed ->
1149 ActivityPub.unfollow(user, followed)
1150 unfollow(user, followed)
1153 delete_user_activities(user)
1154 invalidate_cache(user)
1158 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1159 def perform(:fetch_initial_posts, %User{} = user) do
1160 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1162 # Insert all the posts in reverse order, so they're in the right order on the timeline
1163 user.source_data["outbox"]
1164 |> Utils.fetch_ordered_collection(pages)
1166 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1169 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1171 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1172 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1173 when is_list(blocked_identifiers) do
1175 blocked_identifiers,
1176 fn blocked_identifier ->
1177 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1178 {:ok, blocker} <- block(blocker, blocked),
1179 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1183 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1190 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1191 def perform(:follow_import, %User{} = follower, followed_identifiers)
1192 when is_list(followed_identifiers) do
1194 followed_identifiers,
1195 fn followed_identifier ->
1196 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1197 {:ok, follower} <- maybe_direct_follow(follower, followed),
1198 {:ok, _} <- ActivityPub.follow(follower, followed) do
1202 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1209 @spec external_users_query() :: Ecto.Query.t()
1210 def external_users_query do
1218 @spec external_users(keyword()) :: [User.t()]
1219 def external_users(opts \\ []) do
1221 external_users_query()
1222 |> select([u], struct(u, [:id, :ap_id, :info]))
1226 do: where(query, [u], u.id > ^opts[:max_id]),
1231 do: limit(query, ^opts[:limit]),
1237 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1238 BackgroundWorker.enqueue("blocks_import", %{
1239 "blocker_id" => blocker.id,
1240 "blocked_identifiers" => blocked_identifiers
1244 def follow_import(%User{} = follower, followed_identifiers)
1245 when is_list(followed_identifiers) do
1246 BackgroundWorker.enqueue("follow_import", %{
1247 "follower_id" => follower.id,
1248 "followed_identifiers" => followed_identifiers
1252 def delete_user_activities(%User{ap_id: ap_id}) do
1254 |> Activity.Queries.by_actor()
1255 |> RepoStreamer.chunk_stream(50)
1256 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1260 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1262 |> Object.normalize()
1263 |> ActivityPub.delete()
1266 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1267 object = Object.normalize(activity)
1270 |> get_cached_by_ap_id()
1271 |> ActivityPub.unlike(object)
1274 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1275 object = Object.normalize(activity)
1278 |> get_cached_by_ap_id()
1279 |> ActivityPub.unannounce(object)
1282 defp delete_activity(_activity), do: "Doing nothing"
1284 def html_filter_policy(%User{no_rich_text: true}) do
1285 Pleroma.HTML.Scrubber.TwitterText
1288 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1290 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1292 def get_or_fetch_by_ap_id(ap_id) do
1293 user = get_cached_by_ap_id(ap_id)
1295 if !is_nil(user) and !needs_update?(user) do
1298 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1299 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1301 resp = fetch_by_ap_id(ap_id)
1303 if should_fetch_initial do
1304 with {:ok, %User{} = user} <- resp do
1305 fetch_initial_posts(user)
1314 Creates an internal service actor by URI if missing.
1315 Optionally takes nickname for addressing.
1317 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1318 with user when is_nil(user) <- get_cached_by_ap_id(uri) do
1325 follower_address: uri <> "/followers"
1334 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1337 |> :public_key.pem_decode()
1339 |> :public_key.pem_entry_decode()
1344 def public_key(_), do: {:error, "not found key"}
1346 def get_public_key_for_ap_id(ap_id) do
1347 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1348 {:ok, public_key} <- public_key(user) do
1355 defp blank?(""), do: nil
1356 defp blank?(n), do: n
1358 def insert_or_update_user(data) do
1360 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1361 |> remote_user_creation()
1362 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1366 def ap_enabled?(%User{local: true}), do: true
1367 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1368 def ap_enabled?(_), do: false
1370 @doc "Gets or fetch a user by uri or nickname."
1371 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1372 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1373 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1375 # wait a period of time and return newest version of the User structs
1376 # this is because we have synchronous follow APIs and need to simulate them
1377 # with an async handshake
1378 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1379 with %User{} = a <- get_cached_by_id(a.id),
1380 %User{} = b <- get_cached_by_id(b.id) do
1387 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1388 with :ok <- :timer.sleep(timeout),
1389 %User{} = a <- get_cached_by_id(a.id),
1390 %User{} = b <- get_cached_by_id(b.id) do
1397 def parse_bio(bio) when is_binary(bio) and bio != "" do
1399 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1403 def parse_bio(_), do: ""
1405 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1406 # TODO: get profile URLs other than user.ap_id
1407 profile_urls = [user.ap_id]
1410 |> CommonUtils.format_input("text/plain",
1411 mentions_format: :full,
1412 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1417 def parse_bio(_, _), do: ""
1419 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1420 Repo.transaction(fn ->
1421 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1425 def tag(nickname, tags) when is_binary(nickname),
1426 do: tag(get_by_nickname(nickname), tags)
1428 def tag(%User{} = user, tags),
1429 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1431 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1432 Repo.transaction(fn ->
1433 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1437 def untag(nickname, tags) when is_binary(nickname),
1438 do: untag(get_by_nickname(nickname), tags)
1440 def untag(%User{} = user, tags),
1441 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1443 defp update_tags(%User{} = user, new_tags) do
1444 {:ok, updated_user} =
1446 |> change(%{tags: new_tags})
1447 |> update_and_set_cache()
1452 defp normalize_tags(tags) do
1455 |> Enum.map(&String.downcase/1)
1458 defp local_nickname_regex do
1459 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1460 @extended_local_nickname_regex
1462 @strict_local_nickname_regex
1466 def local_nickname(nickname_or_mention) do
1469 |> String.split("@")
1473 def full_nickname(nickname_or_mention),
1474 do: String.trim_leading(nickname_or_mention, "@")
1476 def error_user(ap_id) do
1480 nickname: "erroruser@example.com",
1481 inserted_at: NaiveDateTime.utc_now()
1485 @spec all_superusers() :: [User.t()]
1486 def all_superusers do
1487 User.Query.build(%{super_users: true, local: true, deactivated: false})
1491 def showing_reblogs?(%User{} = user, %User{} = target) do
1492 target.ap_id not in user.muted_reblogs
1496 The function returns a query to get users with no activity for given interval of days.
1497 Inactive users are those who didn't read any notification, or had any activity where
1498 the user is the activity's actor, during `inactivity_threshold` days.
1499 Deactivated users will not appear in this list.
1503 iex> Pleroma.User.list_inactive_users()
1506 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1507 def list_inactive_users_query(inactivity_threshold \\ 7) do
1508 negative_inactivity_threshold = -inactivity_threshold
1509 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1510 # Subqueries are not supported in `where` clauses, join gets too complicated.
1511 has_read_notifications =
1512 from(n in Pleroma.Notification,
1513 where: n.seen == true,
1515 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1518 |> Pleroma.Repo.all()
1520 from(u in Pleroma.User,
1521 left_join: a in Pleroma.Activity,
1522 on: u.ap_id == a.actor,
1523 where: not is_nil(u.nickname),
1524 where: u.deactivated != ^true,
1525 where: u.id not in ^has_read_notifications,
1528 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1529 is_nil(max(a.inserted_at))
1534 Enable or disable email notifications for user
1538 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1539 Pleroma.User{email_notifications: %{"digest" => true}}
1541 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1542 Pleroma.User{email_notifications: %{"digest" => false}}
1544 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1545 {:ok, t()} | {:error, Ecto.Changeset.t()}
1546 def switch_email_notifications(user, type, status) do
1547 User.update_email_notifications(user, %{type => status})
1551 Set `last_digest_emailed_at` value for the user to current time
1553 @spec touch_last_digest_emailed_at(t()) :: t()
1554 def touch_last_digest_emailed_at(user) do
1555 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1557 {:ok, updated_user} =
1559 |> change(%{last_digest_emailed_at: now})
1560 |> update_and_set_cache()
1565 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1566 def toggle_confirmation(%User{} = user) do
1568 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1569 |> update_and_set_cache()
1572 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1573 def toggle_confirmation(users) do
1574 Enum.map(users, &toggle_confirmation/1)
1577 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1581 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1582 # use instance-default
1583 config = Pleroma.Config.get([:assets, :mascots])
1584 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1585 mascot = Keyword.get(config, default_mascot)
1588 "id" => "default-mascot",
1589 "url" => mascot[:url],
1590 "preview_url" => mascot[:url],
1592 "mime_type" => mascot[:mime_type]
1597 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1599 def ensure_keys_present(%User{} = user) do
1600 with {:ok, pem} <- Keys.generate_rsa_pem() do
1602 |> cast(%{keys: pem}, [:keys])
1603 |> validate_required([:keys])
1604 |> update_and_set_cache()
1608 def get_ap_ids_by_nicknames(nicknames) do
1610 where: u.nickname in ^nicknames,
1616 defdelegate search(query, opts \\ []), to: User.Search
1618 defp put_password_hash(
1619 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1621 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1624 defp put_password_hash(changeset), do: changeset
1626 def is_internal_user?(%User{nickname: nil}), do: true
1627 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1628 def is_internal_user?(_), do: false
1630 # A hack because user delete activities have a fake id for whatever reason
1631 # TODO: Get rid of this
1632 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1634 def get_delivered_users_by_object_id(object_id) do
1636 inner_join: delivery in assoc(u, :deliveries),
1637 where: delivery.object_id == ^object_id
1642 def change_email(user, email) do
1644 |> cast(%{email: email}, [:email])
1645 |> validate_required([:email])
1646 |> unique_constraint(:email)
1647 |> validate_format(:email, @email_regex)
1648 |> update_and_set_cache()
1651 # Internal function; public one is `deactivate/2`
1652 defp set_activation_status(user, deactivated) do
1654 |> cast(%{deactivated: deactivated}, [:deactivated])
1655 |> update_and_set_cache()
1658 def update_banner(user, banner) do
1660 |> cast(%{banner: banner}, [:banner])
1661 |> update_and_set_cache()
1664 def update_background(user, background) do
1666 |> cast(%{background: background}, [:background])
1667 |> update_and_set_cache()
1670 def update_source_data(user, source_data) do
1672 |> cast(%{source_data: source_data}, [:source_data])
1673 |> update_and_set_cache()
1676 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1679 moderator: is_moderator
1683 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1684 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1685 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1686 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1689 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1690 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1694 def fields(%{fields: nil}), do: []
1696 def fields(%{fields: fields}), do: fields
1698 def validate_fields(changeset, remote? \\ false) do
1699 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1700 limit = Pleroma.Config.get([:instance, limit_name], 0)
1703 |> validate_length(:fields, max: limit)
1704 |> validate_change(:fields, fn :fields, fields ->
1705 if Enum.all?(fields, &valid_field?/1) do
1713 defp valid_field?(%{"name" => name, "value" => value}) do
1714 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1715 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1717 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1718 String.length(value) <= value_limit
1721 defp valid_field?(_), do: false
1723 defp truncate_field(%{"name" => name, "value" => value}) do
1725 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1728 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1730 %{"name" => name, "value" => value}
1733 def admin_api_update(user, params) do
1740 |> update_and_set_cache()
1743 def mascot_update(user, url) do
1745 |> cast(%{mascot: url}, [:mascot])
1746 |> validate_required([:mascot])
1747 |> update_and_set_cache()
1750 def mastodon_settings_update(user, settings) do
1752 |> cast(%{settings: settings}, [:settings])
1753 |> validate_required([:settings])
1754 |> update_and_set_cache()
1757 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1758 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1760 if need_confirmation? do
1762 confirmation_pending: true,
1763 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1767 confirmation_pending: false,
1768 confirmation_token: nil
1772 cast(user, params, [:confirmation_pending, :confirmation_token])
1775 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1776 if id not in user.pinned_activities do
1777 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1778 params = %{pinned_activities: user.pinned_activities ++ [id]}
1781 |> cast(params, [:pinned_activities])
1782 |> validate_length(:pinned_activities,
1783 max: max_pinned_statuses,
1784 message: "You have already pinned the maximum number of statuses"
1789 |> update_and_set_cache()
1792 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1793 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1796 |> cast(params, [:pinned_activities])
1797 |> update_and_set_cache()
1800 def update_email_notifications(user, settings) do
1801 email_notifications =
1802 user.email_notifications
1803 |> Map.merge(settings)
1804 |> Map.take(["digest"])
1806 params = %{email_notifications: email_notifications}
1807 fields = [:email_notifications]
1810 |> cast(params, fields)
1811 |> validate_required(fields)
1812 |> update_and_set_cache()
1815 defp set_subscribers(user, subscribers) do
1816 params = %{subscribers: subscribers}
1819 |> cast(params, [:subscribers])
1820 |> validate_required([:subscribers])
1821 |> update_and_set_cache()
1824 def add_to_subscribers(user, subscribed) do
1825 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1828 def remove_from_subscribers(user, subscribed) do
1829 set_subscribers(user, List.delete(user.subscribers, subscribed))
1832 defp set_domain_blocks(user, domain_blocks) do
1833 params = %{domain_blocks: domain_blocks}
1836 |> cast(params, [:domain_blocks])
1837 |> validate_required([:domain_blocks])
1838 |> update_and_set_cache()
1841 def block_domain(user, domain_blocked) do
1842 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1845 def unblock_domain(user, domain_blocked) do
1846 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1849 defp set_blocks(user, blocks) do
1850 params = %{blocks: blocks}
1853 |> cast(params, [:blocks])
1854 |> validate_required([:blocks])
1855 |> update_and_set_cache()
1858 def add_to_block(user, blocked) do
1859 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1862 def remove_from_block(user, blocked) do
1863 set_blocks(user, List.delete(user.blocks, blocked))
1866 defp set_mutes(user, mutes) do
1867 params = %{mutes: mutes}
1870 |> cast(params, [:mutes])
1871 |> validate_required([:mutes])
1872 |> update_and_set_cache()
1875 def add_to_mutes(user, muted, notifications?) do
1876 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1877 set_notification_mutes(
1879 Enum.uniq([muted | user.muted_notifications]),
1885 def remove_from_mutes(user, muted) do
1886 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1887 set_notification_mutes(
1889 List.delete(user.muted_notifications, muted),
1895 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1899 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1900 params = %{muted_notifications: muted_notifications}
1903 |> cast(params, [:muted_notifications])
1904 |> validate_required([:muted_notifications])
1905 |> update_and_set_cache()
1908 def add_reblog_mute(user, ap_id) do
1909 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1912 |> cast(params, [:muted_reblogs])
1913 |> update_and_set_cache()
1916 def remove_reblog_mute(user, ap_id) do
1917 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1920 |> cast(params, [:muted_reblogs])
1921 |> update_and_set_cache()
1924 def set_invisible(user, invisible) do
1925 params = %{invisible: invisible}
1928 |> cast(params, [:invisible])
1929 |> validate_required([:invisible])
1930 |> update_and_set_cache()