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_ids(user, page \\ nil) do
767 |> get_friends_query(page)
772 defdelegate get_follow_requests(user), to: FollowingRelationship
774 def increase_note_count(%User{} = user) do
776 |> where(id: ^user.id)
777 |> update([u], inc: [note_count: 1])
779 |> Repo.update_all([])
781 {1, [user]} -> set_cache(user)
786 def decrease_note_count(%User{} = user) do
788 |> where(id: ^user.id)
791 note_count: fragment("greatest(0, note_count - 1)")
795 |> Repo.update_all([])
797 {1, [user]} -> set_cache(user)
802 def update_note_count(%User{} = user, note_count \\ nil) do
807 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
813 |> cast(%{note_count: note_count}, [:note_count])
814 |> update_and_set_cache()
817 @spec maybe_fetch_follow_information(User.t()) :: User.t()
818 def maybe_fetch_follow_information(user) do
819 with {:ok, user} <- fetch_follow_information(user) do
823 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
829 def fetch_follow_information(user) do
830 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
832 |> follow_information_changeset(info)
833 |> update_and_set_cache()
837 defp follow_information_changeset(user, params) do
844 :hide_followers_count,
849 def update_follower_count(%User{} = user) do
850 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
851 follower_count_query =
852 User.Query.build(%{followers: user, deactivated: false})
853 |> select([u], %{count: count(u.id)})
856 |> where(id: ^user.id)
857 |> join(:inner, [u], s in subquery(follower_count_query))
859 set: [follower_count: s.count]
862 |> Repo.update_all([])
864 {1, [user]} -> set_cache(user)
868 {:ok, maybe_fetch_follow_information(user)}
872 @spec update_following_count(User.t()) :: User.t()
873 def update_following_count(%User{local: false} = user) do
874 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
875 maybe_fetch_follow_information(user)
881 def update_following_count(%User{local: true} = user) do
882 following_count = FollowingRelationship.following_count(user)
885 |> follow_information_changeset(%{following_count: following_count})
889 def set_unread_conversation_count(%User{local: true} = user) do
890 unread_query = Participation.unread_conversation_count_for_user(user)
893 |> join(:inner, [u], p in subquery(unread_query))
895 set: [unread_conversation_count: p.count]
897 |> where([u], u.id == ^user.id)
899 |> Repo.update_all([])
901 {1, [user]} -> set_cache(user)
906 def set_unread_conversation_count(user), do: {:ok, user}
908 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
910 Participation.unread_conversation_count_for_user(user)
911 |> where([p], p.conversation_id == ^conversation.id)
914 |> join(:inner, [u], p in subquery(unread_query))
916 inc: [unread_conversation_count: 1]
918 |> where([u], u.id == ^user.id)
919 |> where([u, p], p.count == 0)
921 |> Repo.update_all([])
923 {1, [user]} -> set_cache(user)
928 def increment_unread_conversation_count(_, user), do: {:ok, user}
930 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
931 def get_users_from_set(ap_ids, local_only \\ true) do
932 criteria = %{ap_id: ap_ids, deactivated: false}
933 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
935 User.Query.build(criteria)
939 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
940 def get_recipients_from_activity(%Activity{recipients: to}) do
941 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
945 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
946 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
947 add_to_mutes(muter, ap_id, notifications?)
950 def unmute(muter, %{ap_id: ap_id}) do
951 remove_from_mutes(muter, ap_id)
954 def subscribe(subscriber, %{ap_id: ap_id}) do
955 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
956 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
958 if blocks?(subscribed, subscriber) and deny_follow_blocked do
959 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
961 User.add_to_subscribers(subscribed, subscriber.ap_id)
966 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
967 with %User{} = user <- get_cached_by_ap_id(ap_id) do
968 User.remove_from_subscribers(user, unsubscriber.ap_id)
972 def block(blocker, %User{ap_id: ap_id} = blocked) do
973 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
975 if following?(blocker, blocked) do
976 {:ok, blocker, _} = unfollow(blocker, blocked)
982 # clear any requested follows as well
984 case CommonAPI.reject_follow_request(blocked, blocker) do
985 {:ok, %User{} = updated_blocked} -> updated_blocked
990 if subscribed_to?(blocked, blocker) do
991 {:ok, blocker} = unsubscribe(blocked, blocker)
997 if following?(blocked, blocker), do: unfollow(blocked, blocker)
999 {:ok, blocker} = update_follower_count(blocker)
1000 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1001 add_to_block(blocker, ap_id)
1004 # helper to handle the block given only an actor's AP id
1005 def block(blocker, %{ap_id: ap_id}) do
1006 block(blocker, get_cached_by_ap_id(ap_id))
1009 def unblock(blocker, %{ap_id: ap_id}) do
1010 remove_from_block(blocker, ap_id)
1013 def mutes?(nil, _), do: false
1014 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1016 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1017 def muted_notifications?(nil, _), do: false
1019 def muted_notifications?(user, %{ap_id: ap_id}),
1020 do: Enum.member?(user.muted_notifications, ap_id)
1022 def blocks?(%User{} = user, %User{} = target) do
1023 blocks_ap_id?(user, target) ||
1024 (!User.following?(user, target) && blocks_domain?(user, target))
1027 def blocks?(nil, _), do: false
1029 def blocks_ap_id?(%User{} = user, %User{} = target) do
1030 Enum.member?(user.blocks, target.ap_id)
1033 def blocks_ap_id?(_, _), do: false
1035 def blocks_domain?(%User{} = user, %User{} = target) do
1036 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1037 %{host: host} = URI.parse(target.ap_id)
1038 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1041 def blocks_domain?(_, _), do: false
1043 def subscribed_to?(user, %{ap_id: ap_id}) do
1044 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1045 Enum.member?(target.subscribers, user.ap_id)
1049 @spec muted_users(User.t()) :: [User.t()]
1050 def muted_users(user) do
1051 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1055 @spec blocked_users(User.t()) :: [User.t()]
1056 def blocked_users(user) do
1057 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1061 @spec subscribers(User.t()) :: [User.t()]
1062 def subscribers(user) do
1063 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1067 def deactivate_async(user, status \\ true) do
1068 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1071 def deactivate(user, status \\ true)
1073 def deactivate(users, status) when is_list(users) do
1074 Repo.transaction(fn ->
1075 for user <- users, do: deactivate(user, status)
1079 def deactivate(%User{} = user, status) do
1080 with {:ok, user} <- set_activation_status(user, status) do
1083 |> Enum.filter(& &1.local)
1084 |> Enum.each(fn follower ->
1085 follower |> update_following_count() |> set_cache()
1088 # Only update local user counts, remote will be update during the next pull.
1091 |> Enum.filter(& &1.local)
1092 |> Enum.each(&update_follower_count/1)
1098 def update_notification_settings(%User{} = user, settings) do
1101 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1104 notification_settings =
1105 user.notification_settings
1106 |> Map.merge(settings)
1107 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1109 params = %{notification_settings: notification_settings}
1112 |> cast(params, [:notification_settings])
1113 |> validate_required([:notification_settings])
1114 |> update_and_set_cache()
1117 def delete(users) when is_list(users) do
1118 for user <- users, do: delete(user)
1121 def delete(%User{} = user) do
1122 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1125 def perform(:force_password_reset, user), do: force_password_reset(user)
1127 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1128 def perform(:delete, %User{} = user) do
1129 {:ok, _user} = ActivityPub.delete(user)
1131 # Remove all relationships
1134 |> Enum.each(fn follower ->
1135 ActivityPub.unfollow(follower, user)
1136 unfollow(follower, user)
1141 |> Enum.each(fn followed ->
1142 ActivityPub.unfollow(user, followed)
1143 unfollow(user, followed)
1146 delete_user_activities(user)
1147 invalidate_cache(user)
1151 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1152 def perform(:fetch_initial_posts, %User{} = user) do
1153 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1155 # Insert all the posts in reverse order, so they're in the right order on the timeline
1156 user.source_data["outbox"]
1157 |> Utils.fetch_ordered_collection(pages)
1159 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1162 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1164 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1165 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1166 when is_list(blocked_identifiers) do
1168 blocked_identifiers,
1169 fn blocked_identifier ->
1170 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1171 {:ok, blocker} <- block(blocker, blocked),
1172 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1176 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1183 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1184 def perform(:follow_import, %User{} = follower, followed_identifiers)
1185 when is_list(followed_identifiers) do
1187 followed_identifiers,
1188 fn followed_identifier ->
1189 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1190 {:ok, follower} <- maybe_direct_follow(follower, followed),
1191 {:ok, _} <- ActivityPub.follow(follower, followed) do
1195 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1202 @spec external_users_query() :: Ecto.Query.t()
1203 def external_users_query do
1211 @spec external_users(keyword()) :: [User.t()]
1212 def external_users(opts \\ []) do
1214 external_users_query()
1215 |> select([u], struct(u, [:id, :ap_id, :info]))
1219 do: where(query, [u], u.id > ^opts[:max_id]),
1224 do: limit(query, ^opts[:limit]),
1230 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1231 BackgroundWorker.enqueue("blocks_import", %{
1232 "blocker_id" => blocker.id,
1233 "blocked_identifiers" => blocked_identifiers
1237 def follow_import(%User{} = follower, followed_identifiers)
1238 when is_list(followed_identifiers) do
1239 BackgroundWorker.enqueue("follow_import", %{
1240 "follower_id" => follower.id,
1241 "followed_identifiers" => followed_identifiers
1245 def delete_user_activities(%User{ap_id: ap_id}) do
1247 |> Activity.Queries.by_actor()
1248 |> RepoStreamer.chunk_stream(50)
1249 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1253 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1255 |> Object.normalize()
1256 |> ActivityPub.delete()
1259 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1260 object = Object.normalize(activity)
1263 |> get_cached_by_ap_id()
1264 |> ActivityPub.unlike(object)
1267 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1268 object = Object.normalize(activity)
1271 |> get_cached_by_ap_id()
1272 |> ActivityPub.unannounce(object)
1275 defp delete_activity(_activity), do: "Doing nothing"
1277 def html_filter_policy(%User{no_rich_text: true}) do
1278 Pleroma.HTML.Scrubber.TwitterText
1281 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1283 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1285 def get_or_fetch_by_ap_id(ap_id) do
1286 user = get_cached_by_ap_id(ap_id)
1288 if !is_nil(user) and !needs_update?(user) do
1291 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1292 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1294 resp = fetch_by_ap_id(ap_id)
1296 if should_fetch_initial do
1297 with {:ok, %User{} = user} <- resp do
1298 fetch_initial_posts(user)
1307 Creates an internal service actor by URI if missing.
1308 Optionally takes nickname for addressing.
1310 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1311 with user when is_nil(user) <- get_cached_by_ap_id(uri) do
1318 follower_address: uri <> "/followers"
1327 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1330 |> :public_key.pem_decode()
1332 |> :public_key.pem_entry_decode()
1337 def public_key(_), do: {:error, "not found key"}
1339 def get_public_key_for_ap_id(ap_id) do
1340 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1341 {:ok, public_key} <- public_key(user) do
1348 defp blank?(""), do: nil
1349 defp blank?(n), do: n
1351 def insert_or_update_user(data) do
1353 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1354 |> remote_user_creation()
1355 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1359 def ap_enabled?(%User{local: true}), do: true
1360 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1361 def ap_enabled?(_), do: false
1363 @doc "Gets or fetch a user by uri or nickname."
1364 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1365 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1366 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1368 # wait a period of time and return newest version of the User structs
1369 # this is because we have synchronous follow APIs and need to simulate them
1370 # with an async handshake
1371 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1372 with %User{} = a <- get_cached_by_id(a.id),
1373 %User{} = b <- get_cached_by_id(b.id) do
1380 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1381 with :ok <- :timer.sleep(timeout),
1382 %User{} = a <- get_cached_by_id(a.id),
1383 %User{} = b <- get_cached_by_id(b.id) do
1390 def parse_bio(bio) when is_binary(bio) and bio != "" do
1392 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1396 def parse_bio(_), do: ""
1398 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1399 # TODO: get profile URLs other than user.ap_id
1400 profile_urls = [user.ap_id]
1403 |> CommonUtils.format_input("text/plain",
1404 mentions_format: :full,
1405 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1410 def parse_bio(_, _), do: ""
1412 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1413 Repo.transaction(fn ->
1414 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1418 def tag(nickname, tags) when is_binary(nickname),
1419 do: tag(get_by_nickname(nickname), tags)
1421 def tag(%User{} = user, tags),
1422 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1424 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1425 Repo.transaction(fn ->
1426 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1430 def untag(nickname, tags) when is_binary(nickname),
1431 do: untag(get_by_nickname(nickname), tags)
1433 def untag(%User{} = user, tags),
1434 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1436 defp update_tags(%User{} = user, new_tags) do
1437 {:ok, updated_user} =
1439 |> change(%{tags: new_tags})
1440 |> update_and_set_cache()
1445 defp normalize_tags(tags) do
1448 |> Enum.map(&String.downcase/1)
1451 defp local_nickname_regex do
1452 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1453 @extended_local_nickname_regex
1455 @strict_local_nickname_regex
1459 def local_nickname(nickname_or_mention) do
1462 |> String.split("@")
1466 def full_nickname(nickname_or_mention),
1467 do: String.trim_leading(nickname_or_mention, "@")
1469 def error_user(ap_id) do
1473 nickname: "erroruser@example.com",
1474 inserted_at: NaiveDateTime.utc_now()
1478 @spec all_superusers() :: [User.t()]
1479 def all_superusers do
1480 User.Query.build(%{super_users: true, local: true, deactivated: false})
1484 def showing_reblogs?(%User{} = user, %User{} = target) do
1485 target.ap_id not in user.muted_reblogs
1489 The function returns a query to get users with no activity for given interval of days.
1490 Inactive users are those who didn't read any notification, or had any activity where
1491 the user is the activity's actor, during `inactivity_threshold` days.
1492 Deactivated users will not appear in this list.
1496 iex> Pleroma.User.list_inactive_users()
1499 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1500 def list_inactive_users_query(inactivity_threshold \\ 7) do
1501 negative_inactivity_threshold = -inactivity_threshold
1502 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1503 # Subqueries are not supported in `where` clauses, join gets too complicated.
1504 has_read_notifications =
1505 from(n in Pleroma.Notification,
1506 where: n.seen == true,
1508 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1511 |> Pleroma.Repo.all()
1513 from(u in Pleroma.User,
1514 left_join: a in Pleroma.Activity,
1515 on: u.ap_id == a.actor,
1516 where: not is_nil(u.nickname),
1517 where: u.deactivated != ^true,
1518 where: u.id not in ^has_read_notifications,
1521 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1522 is_nil(max(a.inserted_at))
1527 Enable or disable email notifications for user
1531 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1532 Pleroma.User{email_notifications: %{"digest" => true}}
1534 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1535 Pleroma.User{email_notifications: %{"digest" => false}}
1537 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1538 {:ok, t()} | {:error, Ecto.Changeset.t()}
1539 def switch_email_notifications(user, type, status) do
1540 User.update_email_notifications(user, %{type => status})
1544 Set `last_digest_emailed_at` value for the user to current time
1546 @spec touch_last_digest_emailed_at(t()) :: t()
1547 def touch_last_digest_emailed_at(user) do
1548 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1550 {:ok, updated_user} =
1552 |> change(%{last_digest_emailed_at: now})
1553 |> update_and_set_cache()
1558 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1559 def toggle_confirmation(%User{} = user) do
1561 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1562 |> update_and_set_cache()
1565 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1566 def toggle_confirmation(users) do
1567 Enum.map(users, &toggle_confirmation/1)
1570 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1574 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1575 # use instance-default
1576 config = Pleroma.Config.get([:assets, :mascots])
1577 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1578 mascot = Keyword.get(config, default_mascot)
1581 "id" => "default-mascot",
1582 "url" => mascot[:url],
1583 "preview_url" => mascot[:url],
1585 "mime_type" => mascot[:mime_type]
1590 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1592 def ensure_keys_present(%User{} = user) do
1593 with {:ok, pem} <- Keys.generate_rsa_pem() do
1595 |> cast(%{keys: pem}, [:keys])
1596 |> validate_required([:keys])
1597 |> update_and_set_cache()
1601 def get_ap_ids_by_nicknames(nicknames) do
1603 where: u.nickname in ^nicknames,
1609 defdelegate search(query, opts \\ []), to: User.Search
1611 defp put_password_hash(
1612 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1614 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1617 defp put_password_hash(changeset), do: changeset
1619 def is_internal_user?(%User{nickname: nil}), do: true
1620 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1621 def is_internal_user?(_), do: false
1623 # A hack because user delete activities have a fake id for whatever reason
1624 # TODO: Get rid of this
1625 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1627 def get_delivered_users_by_object_id(object_id) do
1629 inner_join: delivery in assoc(u, :deliveries),
1630 where: delivery.object_id == ^object_id
1635 def change_email(user, email) do
1637 |> cast(%{email: email}, [:email])
1638 |> validate_required([:email])
1639 |> unique_constraint(:email)
1640 |> validate_format(:email, @email_regex)
1641 |> update_and_set_cache()
1644 # Internal function; public one is `deactivate/2`
1645 defp set_activation_status(user, deactivated) do
1647 |> cast(%{deactivated: deactivated}, [:deactivated])
1648 |> update_and_set_cache()
1651 def update_banner(user, banner) do
1653 |> cast(%{banner: banner}, [:banner])
1654 |> update_and_set_cache()
1657 def update_background(user, background) do
1659 |> cast(%{background: background}, [:background])
1660 |> update_and_set_cache()
1663 def update_source_data(user, source_data) do
1665 |> cast(%{source_data: source_data}, [:source_data])
1666 |> update_and_set_cache()
1669 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1672 moderator: is_moderator
1676 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1677 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1678 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1679 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1682 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1683 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1687 def fields(%{fields: nil}), do: []
1689 def fields(%{fields: fields}), do: fields
1691 def validate_fields(changeset, remote? \\ false) do
1692 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1693 limit = Pleroma.Config.get([:instance, limit_name], 0)
1696 |> validate_length(:fields, max: limit)
1697 |> validate_change(:fields, fn :fields, fields ->
1698 if Enum.all?(fields, &valid_field?/1) do
1706 defp valid_field?(%{"name" => name, "value" => value}) do
1707 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1708 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1710 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1711 String.length(value) <= value_limit
1714 defp valid_field?(_), do: false
1716 defp truncate_field(%{"name" => name, "value" => value}) do
1718 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1721 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1723 %{"name" => name, "value" => value}
1726 def admin_api_update(user, params) do
1733 |> update_and_set_cache()
1736 def mascot_update(user, url) do
1738 |> cast(%{mascot: url}, [:mascot])
1739 |> validate_required([:mascot])
1740 |> update_and_set_cache()
1743 def mastodon_settings_update(user, settings) do
1745 |> cast(%{settings: settings}, [:settings])
1746 |> validate_required([:settings])
1747 |> update_and_set_cache()
1750 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1751 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1753 if need_confirmation? do
1755 confirmation_pending: true,
1756 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1760 confirmation_pending: false,
1761 confirmation_token: nil
1765 cast(user, params, [:confirmation_pending, :confirmation_token])
1768 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1769 if id not in user.pinned_activities do
1770 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1771 params = %{pinned_activities: user.pinned_activities ++ [id]}
1774 |> cast(params, [:pinned_activities])
1775 |> validate_length(:pinned_activities,
1776 max: max_pinned_statuses,
1777 message: "You have already pinned the maximum number of statuses"
1782 |> update_and_set_cache()
1785 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1786 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1789 |> cast(params, [:pinned_activities])
1790 |> update_and_set_cache()
1793 def update_email_notifications(user, settings) do
1794 email_notifications =
1795 user.email_notifications
1796 |> Map.merge(settings)
1797 |> Map.take(["digest"])
1799 params = %{email_notifications: email_notifications}
1800 fields = [:email_notifications]
1803 |> cast(params, fields)
1804 |> validate_required(fields)
1805 |> update_and_set_cache()
1808 defp set_subscribers(user, subscribers) do
1809 params = %{subscribers: subscribers}
1812 |> cast(params, [:subscribers])
1813 |> validate_required([:subscribers])
1814 |> update_and_set_cache()
1817 def add_to_subscribers(user, subscribed) do
1818 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1821 def remove_from_subscribers(user, subscribed) do
1822 set_subscribers(user, List.delete(user.subscribers, subscribed))
1825 defp set_domain_blocks(user, domain_blocks) do
1826 params = %{domain_blocks: domain_blocks}
1829 |> cast(params, [:domain_blocks])
1830 |> validate_required([:domain_blocks])
1831 |> update_and_set_cache()
1834 def block_domain(user, domain_blocked) do
1835 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1838 def unblock_domain(user, domain_blocked) do
1839 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1842 defp set_blocks(user, blocks) do
1843 params = %{blocks: blocks}
1846 |> cast(params, [:blocks])
1847 |> validate_required([:blocks])
1848 |> update_and_set_cache()
1851 def add_to_block(user, blocked) do
1852 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1855 def remove_from_block(user, blocked) do
1856 set_blocks(user, List.delete(user.blocks, blocked))
1859 defp set_mutes(user, mutes) do
1860 params = %{mutes: mutes}
1863 |> cast(params, [:mutes])
1864 |> validate_required([:mutes])
1865 |> update_and_set_cache()
1868 def add_to_mutes(user, muted, notifications?) do
1869 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1870 set_notification_mutes(
1872 Enum.uniq([muted | user.muted_notifications]),
1878 def remove_from_mutes(user, muted) do
1879 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1880 set_notification_mutes(
1882 List.delete(user.muted_notifications, muted),
1888 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1892 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1893 params = %{muted_notifications: muted_notifications}
1896 |> cast(params, [:muted_notifications])
1897 |> validate_required([:muted_notifications])
1898 |> update_and_set_cache()
1901 def add_reblog_mute(user, ap_id) do
1902 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1905 |> cast(params, [:muted_reblogs])
1906 |> update_and_set_cache()
1909 def remove_reblog_mute(user, ap_id) do
1910 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1913 |> cast(params, [:muted_reblogs])
1914 |> update_and_set_cache()
1917 def set_invisible(user, invisible) do
1918 params = %{invisible: invisible}
1921 |> cast(params, [:invisible])
1922 |> validate_required([:invisible])
1923 |> update_and_set_cache()