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)
124 @doc "Returns if the user should be allowed to authenticate"
125 def auth_active?(%User{deactivated: true}), do: false
127 def auth_active?(%User{confirmation_pending: true}),
128 do: !Pleroma.Config.get([:instance, :account_activation_required])
130 def auth_active?(%User{}), do: true
132 def visible_for?(user, for_user \\ nil)
134 def visible_for?(%User{invisible: true}, _), do: false
136 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
138 def visible_for?(%User{} = user, for_user) do
139 auth_active?(user) || superuser?(for_user)
142 def visible_for?(_, _), do: false
144 def superuser?(%User{local: true, is_admin: true}), do: true
145 def superuser?(%User{local: true, is_moderator: true}), do: true
146 def superuser?(_), do: false
148 def invisible?(%User{invisible: true}), do: true
149 def invisible?(_), do: false
151 def avatar_url(user, options \\ []) do
153 %{"url" => [%{"href" => href} | _]} -> href
154 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
158 def banner_url(user, options \\ []) do
160 %{"url" => [%{"href" => href} | _]} -> href
161 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
165 def profile_url(%User{source_data: %{"url" => url}}), do: url
166 def profile_url(%User{ap_id: ap_id}), do: ap_id
167 def profile_url(_), do: nil
169 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
171 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
172 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
174 @spec ap_following(User.t()) :: Sring.t()
175 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
176 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
178 def follow_state(%User{} = user, %User{} = target) do
179 case Utils.fetch_latest_follow(user, target) do
180 %{data: %{"state" => state}} -> state
181 # Ideally this would be nil, but then Cachex does not commit the value
186 def get_cached_follow_state(user, target) do
187 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
188 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
191 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
192 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
193 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
196 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
197 def restrict_deactivated(query) do
198 from(u in query, where: u.deactivated != ^true)
201 defdelegate following_count(user), to: FollowingRelationship
203 defp truncate_fields_param(params) do
204 if Map.has_key?(params, :fields) do
205 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
211 defp truncate_if_exists(params, key, max_length) do
212 if Map.has_key?(params, key) and is_binary(params[key]) do
213 {value, _chopped} = String.split_at(params[key], max_length)
214 Map.put(params, key, value)
220 def remote_user_creation(params) do
221 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
222 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
226 |> truncate_if_exists(:name, name_limit)
227 |> truncate_if_exists(:bio, bio_limit)
228 |> truncate_fields_param()
248 :hide_followers_count,
257 |> validate_required([:name, :ap_id])
258 |> unique_constraint(:nickname)
259 |> validate_format(:nickname, @email_regex)
260 |> validate_length(:bio, max: bio_limit)
261 |> validate_length(:name, max: name_limit)
262 |> validate_fields(true)
264 case params[:source_data] do
265 %{"followers" => followers, "following" => following} ->
267 |> put_change(:follower_address, followers)
268 |> put_change(:following_address, following)
271 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
272 put_change(changeset, :follower_address, followers)
276 def update_changeset(struct, params \\ %{}) do
277 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
278 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
293 :hide_followers_count,
298 :skip_thread_containment,
301 :pleroma_settings_store,
305 |> unique_constraint(:nickname)
306 |> validate_format(:nickname, local_nickname_regex())
307 |> validate_length(:bio, max: bio_limit)
308 |> validate_length(:name, min: 1, max: name_limit)
309 |> validate_fields(false)
312 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
313 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
314 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
316 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
318 params = if remote?, do: truncate_fields_param(params), else: params
341 :hide_followers_count,
345 |> unique_constraint(:nickname)
346 |> validate_format(:nickname, local_nickname_regex())
347 |> validate_length(:bio, max: bio_limit)
348 |> validate_length(:name, max: name_limit)
349 |> validate_fields(remote?)
352 def password_update_changeset(struct, params) do
354 |> cast(params, [:password, :password_confirmation])
355 |> validate_required([:password, :password_confirmation])
356 |> validate_confirmation(:password)
357 |> put_password_hash()
358 |> put_change(:password_reset_pending, false)
361 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
362 def reset_password(%User{id: user_id} = user, data) do
365 |> Multi.update(:user, password_update_changeset(user, data))
366 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
367 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
369 case Repo.transaction(multi) do
370 {:ok, %{user: user} = _} -> set_cache(user)
371 {:error, _, changeset, _} -> {:error, changeset}
375 def update_password_reset_pending(user, value) do
378 |> put_change(:password_reset_pending, value)
379 |> update_and_set_cache()
382 def force_password_reset_async(user) do
383 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
386 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
387 def force_password_reset(user), do: update_password_reset_pending(user, true)
389 def register_changeset(struct, params \\ %{}, opts \\ []) do
390 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
391 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
394 if is_nil(opts[:need_confirmation]) do
395 Pleroma.Config.get([:instance, :account_activation_required])
397 opts[:need_confirmation]
401 |> confirmation_changeset(need_confirmation: need_confirmation?)
402 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
403 |> validate_required([:name, :nickname, :password, :password_confirmation])
404 |> validate_confirmation(:password)
405 |> unique_constraint(:email)
406 |> unique_constraint(:nickname)
407 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
408 |> validate_format(:nickname, local_nickname_regex())
409 |> validate_format(:email, @email_regex)
410 |> validate_length(:bio, max: bio_limit)
411 |> validate_length(:name, min: 1, max: name_limit)
412 |> maybe_validate_required_email(opts[:external])
415 |> unique_constraint(:ap_id)
416 |> put_following_and_follower_address()
419 def maybe_validate_required_email(changeset, true), do: changeset
420 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
422 defp put_ap_id(changeset) do
423 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
424 put_change(changeset, :ap_id, ap_id)
427 defp put_following_and_follower_address(changeset) do
428 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
431 |> put_change(:follower_address, followers)
434 defp autofollow_users(user) do
435 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
438 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
441 follow_all(user, autofollowed_users)
444 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
445 def register(%Ecto.Changeset{} = changeset) do
446 with {:ok, user} <- Repo.insert(changeset) do
447 post_register_action(user)
451 def post_register_action(%User{} = user) do
452 with {:ok, user} <- autofollow_users(user),
453 {:ok, user} <- set_cache(user),
454 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
455 {:ok, _} <- try_send_confirmation_email(user) do
460 def try_send_confirmation_email(%User{} = user) do
461 if user.confirmation_pending &&
462 Pleroma.Config.get([:instance, :account_activation_required]) do
464 |> Pleroma.Emails.UserEmail.account_confirmation_email()
465 |> Pleroma.Emails.Mailer.deliver_async()
473 def try_send_confirmation_email(users) do
474 Enum.each(users, &try_send_confirmation_email/1)
477 def needs_update?(%User{local: true}), do: false
479 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
481 def needs_update?(%User{local: false} = user) do
482 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
485 def needs_update?(_), do: true
487 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
488 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
489 follow(follower, followed, "pending")
492 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
493 follow(follower, followed)
496 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
497 if not ap_enabled?(followed) do
498 follow(follower, followed)
504 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
505 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
506 def follow_all(follower, followeds) do
508 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
509 |> Enum.each(&follow(follower, &1, "accept"))
514 defdelegate following(user), to: FollowingRelationship
516 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
517 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
520 followed.deactivated ->
521 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
523 deny_follow_blocked and blocks?(followed, follower) ->
524 {:error, "Could not follow user: #{followed.nickname} blocked you."}
527 FollowingRelationship.follow(follower, followed, state)
529 {:ok, _} = update_follower_count(followed)
532 |> update_following_count()
537 def unfollow(%User{} = follower, %User{} = followed) do
538 if following?(follower, followed) and follower.ap_id != followed.ap_id do
539 FollowingRelationship.unfollow(follower, followed)
541 {:ok, followed} = update_follower_count(followed)
545 |> update_following_count()
548 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
550 {:error, "Not subscribed!"}
554 defdelegate following?(follower, followed), to: FollowingRelationship
556 def locked?(%User{} = user) do
561 Repo.get_by(User, id: id)
564 def get_by_ap_id(ap_id) do
565 Repo.get_by(User, ap_id: ap_id)
568 def get_all_by_ap_id(ap_ids) do
569 from(u in __MODULE__,
570 where: u.ap_id in ^ap_ids
575 def get_all_by_ids(ids) do
576 from(u in __MODULE__, where: u.id in ^ids)
580 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
581 # of the ap_id and the domain and tries to get that user
582 def get_by_guessed_nickname(ap_id) do
583 domain = URI.parse(ap_id).host
584 name = List.last(String.split(ap_id, "/"))
585 nickname = "#{name}@#{domain}"
587 get_cached_by_nickname(nickname)
590 def set_cache({:ok, user}), do: set_cache(user)
591 def set_cache({:error, err}), do: {:error, err}
593 def set_cache(%User{} = user) do
594 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
595 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
599 def update_and_set_cache(struct, params) do
601 |> update_changeset(params)
602 |> update_and_set_cache()
605 def update_and_set_cache(changeset) do
606 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
611 def invalidate_cache(user) do
612 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
613 Cachex.del(:user_cache, "nickname:#{user.nickname}")
616 def get_cached_by_ap_id(ap_id) do
617 key = "ap_id:#{ap_id}"
618 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
621 def get_cached_by_id(id) do
625 Cachex.fetch!(:user_cache, key, fn _ ->
629 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
630 {:commit, user.ap_id}
636 get_cached_by_ap_id(ap_id)
639 def get_cached_by_nickname(nickname) do
640 key = "nickname:#{nickname}"
642 Cachex.fetch!(:user_cache, key, fn ->
643 case get_or_fetch_by_nickname(nickname) do
644 {:ok, user} -> {:commit, user}
645 {:error, _error} -> {:ignore, nil}
650 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
651 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
654 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
655 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
657 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
658 get_cached_by_nickname(nickname_or_id)
660 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
661 get_cached_by_nickname(nickname_or_id)
668 def get_by_nickname(nickname) do
669 Repo.get_by(User, nickname: nickname) ||
670 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
671 Repo.get_by(User, nickname: local_nickname(nickname))
675 def get_by_email(email), do: Repo.get_by(User, email: email)
677 def get_by_nickname_or_email(nickname_or_email) do
678 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
681 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
683 def get_or_fetch_by_nickname(nickname) do
684 with %User{} = user <- get_by_nickname(nickname) do
688 with [_nick, _domain] <- String.split(nickname, "@"),
689 {:ok, user} <- fetch_by_nickname(nickname) do
690 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
691 fetch_initial_posts(user)
696 _e -> {:error, "not found " <> nickname}
701 @doc "Fetch some posts when the user has just been federated with"
702 def fetch_initial_posts(user) do
703 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
706 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
707 def get_followers_query(%User{} = user, nil) do
708 User.Query.build(%{followers: user, deactivated: false})
711 def get_followers_query(user, page) do
713 |> get_followers_query(nil)
714 |> User.Query.paginate(page, 20)
717 @spec get_followers_query(User.t()) :: Ecto.Query.t()
718 def get_followers_query(user), do: get_followers_query(user, nil)
720 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
721 def get_followers(user, page \\ nil) do
723 |> get_followers_query(page)
727 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
728 def get_external_followers(user, page \\ nil) do
730 |> get_followers_query(page)
731 |> User.Query.build(%{external: true})
735 def get_followers_ids(user, page \\ nil) do
737 |> get_followers_query(page)
742 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
743 def get_friends_query(%User{} = user, nil) do
744 User.Query.build(%{friends: user, deactivated: false})
747 def get_friends_query(user, page) do
749 |> get_friends_query(nil)
750 |> User.Query.paginate(page, 20)
753 @spec get_friends_query(User.t()) :: Ecto.Query.t()
754 def get_friends_query(user), do: get_friends_query(user, nil)
756 def get_friends(user, page \\ nil) do
758 |> get_friends_query(page)
762 def get_friends_ids(user, page \\ nil) do
764 |> get_friends_query(page)
769 defdelegate get_follow_requests(user), to: FollowingRelationship
771 def increase_note_count(%User{} = user) do
773 |> where(id: ^user.id)
774 |> update([u], inc: [note_count: 1])
776 |> Repo.update_all([])
778 {1, [user]} -> set_cache(user)
783 def decrease_note_count(%User{} = user) do
785 |> where(id: ^user.id)
788 note_count: fragment("greatest(0, note_count - 1)")
792 |> Repo.update_all([])
794 {1, [user]} -> set_cache(user)
799 def update_note_count(%User{} = user, note_count \\ nil) do
804 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
810 |> cast(%{note_count: note_count}, [:note_count])
811 |> update_and_set_cache()
814 @spec maybe_fetch_follow_information(User.t()) :: User.t()
815 def maybe_fetch_follow_information(user) do
816 with {:ok, user} <- fetch_follow_information(user) do
820 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
826 def fetch_follow_information(user) do
827 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
829 |> follow_information_changeset(info)
830 |> update_and_set_cache()
834 defp follow_information_changeset(user, params) do
841 :hide_followers_count,
846 def update_follower_count(%User{} = user) do
847 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
848 follower_count_query =
849 User.Query.build(%{followers: user, deactivated: false})
850 |> select([u], %{count: count(u.id)})
853 |> where(id: ^user.id)
854 |> join(:inner, [u], s in subquery(follower_count_query))
856 set: [follower_count: s.count]
859 |> Repo.update_all([])
861 {1, [user]} -> set_cache(user)
865 {:ok, maybe_fetch_follow_information(user)}
869 @spec update_following_count(User.t()) :: User.t()
870 def update_following_count(%User{local: false} = user) do
871 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
872 maybe_fetch_follow_information(user)
878 def update_following_count(%User{local: true} = user) do
879 following_count = FollowingRelationship.following_count(user)
882 |> follow_information_changeset(%{following_count: following_count})
886 def set_unread_conversation_count(%User{local: true} = user) do
887 unread_query = Participation.unread_conversation_count_for_user(user)
890 |> join(:inner, [u], p in subquery(unread_query))
892 set: [unread_conversation_count: p.count]
894 |> where([u], u.id == ^user.id)
896 |> Repo.update_all([])
898 {1, [user]} -> set_cache(user)
903 def set_unread_conversation_count(user), do: {:ok, user}
905 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
907 Participation.unread_conversation_count_for_user(user)
908 |> where([p], p.conversation_id == ^conversation.id)
911 |> join(:inner, [u], p in subquery(unread_query))
913 inc: [unread_conversation_count: 1]
915 |> where([u], u.id == ^user.id)
916 |> where([u, p], p.count == 0)
918 |> Repo.update_all([])
920 {1, [user]} -> set_cache(user)
925 def increment_unread_conversation_count(_, user), do: {:ok, user}
927 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
928 def get_users_from_set(ap_ids, local_only \\ true) do
929 criteria = %{ap_id: ap_ids, deactivated: false}
930 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
932 User.Query.build(criteria)
936 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
937 def get_recipients_from_activity(%Activity{recipients: to}) do
938 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
942 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
943 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
944 add_to_mutes(muter, ap_id, notifications?)
947 def unmute(muter, %{ap_id: ap_id}) do
948 remove_from_mutes(muter, ap_id)
951 def subscribe(subscriber, %{ap_id: ap_id}) do
952 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
953 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
955 if blocks?(subscribed, subscriber) and deny_follow_blocked do
956 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
958 User.add_to_subscribers(subscribed, subscriber.ap_id)
963 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
964 with %User{} = user <- get_cached_by_ap_id(ap_id) do
965 User.remove_from_subscribers(user, unsubscriber.ap_id)
969 def block(blocker, %User{ap_id: ap_id} = blocked) do
970 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
972 if following?(blocker, blocked) do
973 {:ok, blocker, _} = unfollow(blocker, blocked)
979 # clear any requested follows as well
981 case CommonAPI.reject_follow_request(blocked, blocker) do
982 {:ok, %User{} = updated_blocked} -> updated_blocked
987 if subscribed_to?(blocked, blocker) do
988 {:ok, blocker} = unsubscribe(blocked, blocker)
994 if following?(blocked, blocker), do: unfollow(blocked, blocker)
996 {:ok, blocker} = update_follower_count(blocker)
997 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
998 add_to_block(blocker, ap_id)
1001 # helper to handle the block given only an actor's AP id
1002 def block(blocker, %{ap_id: ap_id}) do
1003 block(blocker, get_cached_by_ap_id(ap_id))
1006 def unblock(blocker, %{ap_id: ap_id}) do
1007 remove_from_block(blocker, ap_id)
1010 def mutes?(nil, _), do: false
1011 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1013 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1014 def muted_notifications?(nil, _), do: false
1016 def muted_notifications?(user, %{ap_id: ap_id}),
1017 do: Enum.member?(user.muted_notifications, ap_id)
1019 def blocks?(%User{} = user, %User{} = target) do
1020 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1023 def blocks?(nil, _), do: false
1025 def blocks_ap_id?(%User{} = user, %User{} = target) do
1026 Enum.member?(user.blocks, target.ap_id)
1029 def blocks_ap_id?(_, _), do: false
1031 def blocks_domain?(%User{} = user, %User{} = target) do
1032 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1033 %{host: host} = URI.parse(target.ap_id)
1034 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1037 def blocks_domain?(_, _), do: false
1039 def subscribed_to?(user, %{ap_id: ap_id}) do
1040 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1041 Enum.member?(target.subscribers, user.ap_id)
1045 @spec muted_users(User.t()) :: [User.t()]
1046 def muted_users(user) do
1047 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1051 @spec blocked_users(User.t()) :: [User.t()]
1052 def blocked_users(user) do
1053 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1057 @spec subscribers(User.t()) :: [User.t()]
1058 def subscribers(user) do
1059 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1063 def deactivate_async(user, status \\ true) do
1064 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1067 def deactivate(user, status \\ true)
1069 def deactivate(users, status) when is_list(users) do
1070 Repo.transaction(fn ->
1071 for user <- users, do: deactivate(user, status)
1075 def deactivate(%User{} = user, status) do
1076 with {:ok, user} <- set_activation_status(user, status) do
1079 |> Enum.filter(& &1.local)
1080 |> Enum.each(fn follower ->
1081 follower |> update_following_count() |> set_cache()
1084 # Only update local user counts, remote will be update during the next pull.
1087 |> Enum.filter(& &1.local)
1088 |> Enum.each(&update_follower_count/1)
1094 def update_notification_settings(%User{} = user, settings) do
1097 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1100 notification_settings =
1101 user.notification_settings
1102 |> Map.merge(settings)
1103 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1105 params = %{notification_settings: notification_settings}
1108 |> cast(params, [:notification_settings])
1109 |> validate_required([:notification_settings])
1110 |> update_and_set_cache()
1113 def delete(users) when is_list(users) do
1114 for user <- users, do: delete(user)
1117 def delete(%User{} = user) do
1118 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1121 def perform(:force_password_reset, user), do: force_password_reset(user)
1123 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1124 def perform(:delete, %User{} = user) do
1125 {:ok, _user} = ActivityPub.delete(user)
1127 # Remove all relationships
1130 |> Enum.each(fn follower ->
1131 ActivityPub.unfollow(follower, user)
1132 unfollow(follower, user)
1137 |> Enum.each(fn followed ->
1138 ActivityPub.unfollow(user, followed)
1139 unfollow(user, followed)
1142 delete_user_activities(user)
1143 invalidate_cache(user)
1147 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1148 def perform(:fetch_initial_posts, %User{} = user) do
1149 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1151 # Insert all the posts in reverse order, so they're in the right order on the timeline
1152 user.source_data["outbox"]
1153 |> Utils.fetch_ordered_collection(pages)
1155 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1158 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1160 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1161 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1162 when is_list(blocked_identifiers) do
1164 blocked_identifiers,
1165 fn blocked_identifier ->
1166 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1167 {:ok, blocker} <- block(blocker, blocked),
1168 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1172 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1179 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1180 def perform(:follow_import, %User{} = follower, followed_identifiers)
1181 when is_list(followed_identifiers) do
1183 followed_identifiers,
1184 fn followed_identifier ->
1185 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1186 {:ok, follower} <- maybe_direct_follow(follower, followed),
1187 {:ok, _} <- ActivityPub.follow(follower, followed) do
1191 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1198 @spec external_users_query() :: Ecto.Query.t()
1199 def external_users_query do
1207 @spec external_users(keyword()) :: [User.t()]
1208 def external_users(opts \\ []) do
1210 external_users_query()
1211 |> select([u], struct(u, [:id, :ap_id]))
1215 do: where(query, [u], u.id > ^opts[:max_id]),
1220 do: limit(query, ^opts[:limit]),
1226 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1227 BackgroundWorker.enqueue("blocks_import", %{
1228 "blocker_id" => blocker.id,
1229 "blocked_identifiers" => blocked_identifiers
1233 def follow_import(%User{} = follower, followed_identifiers)
1234 when is_list(followed_identifiers) do
1235 BackgroundWorker.enqueue("follow_import", %{
1236 "follower_id" => follower.id,
1237 "followed_identifiers" => followed_identifiers
1241 def delete_user_activities(%User{ap_id: ap_id}) do
1243 |> Activity.Queries.by_actor()
1244 |> RepoStreamer.chunk_stream(50)
1245 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1249 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1251 |> Object.normalize()
1252 |> ActivityPub.delete()
1255 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1256 object = Object.normalize(activity)
1259 |> get_cached_by_ap_id()
1260 |> ActivityPub.unlike(object)
1263 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1264 object = Object.normalize(activity)
1267 |> get_cached_by_ap_id()
1268 |> ActivityPub.unannounce(object)
1271 defp delete_activity(_activity), do: "Doing nothing"
1273 def html_filter_policy(%User{no_rich_text: true}) do
1274 Pleroma.HTML.Scrubber.TwitterText
1277 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1279 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1281 def get_or_fetch_by_ap_id(ap_id) do
1282 user = get_cached_by_ap_id(ap_id)
1284 if !is_nil(user) and !needs_update?(user) do
1287 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1288 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1290 resp = fetch_by_ap_id(ap_id)
1292 if should_fetch_initial do
1293 with {:ok, %User{} = user} <- resp do
1294 fetch_initial_posts(user)
1303 Creates an internal service actor by URI if missing.
1304 Optionally takes nickname for addressing.
1306 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1307 with user when is_nil(user) <- get_cached_by_ap_id(uri) do
1314 follower_address: uri <> "/followers"
1323 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1326 |> :public_key.pem_decode()
1328 |> :public_key.pem_entry_decode()
1333 def public_key(_), do: {:error, "not found key"}
1335 def get_public_key_for_ap_id(ap_id) do
1336 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1337 {:ok, public_key} <- public_key(user) do
1344 defp blank?(""), do: nil
1345 defp blank?(n), do: n
1347 def insert_or_update_user(data) do
1349 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1350 |> remote_user_creation()
1351 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1355 def ap_enabled?(%User{local: true}), do: true
1356 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1357 def ap_enabled?(_), do: false
1359 @doc "Gets or fetch a user by uri or nickname."
1360 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1361 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1362 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1364 # wait a period of time and return newest version of the User structs
1365 # this is because we have synchronous follow APIs and need to simulate them
1366 # with an async handshake
1367 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1368 with %User{} = a <- get_cached_by_id(a.id),
1369 %User{} = b <- get_cached_by_id(b.id) do
1376 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1377 with :ok <- :timer.sleep(timeout),
1378 %User{} = a <- get_cached_by_id(a.id),
1379 %User{} = b <- get_cached_by_id(b.id) do
1386 def parse_bio(bio) when is_binary(bio) and bio != "" do
1388 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1392 def parse_bio(_), do: ""
1394 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1395 # TODO: get profile URLs other than user.ap_id
1396 profile_urls = [user.ap_id]
1399 |> CommonUtils.format_input("text/plain",
1400 mentions_format: :full,
1401 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1406 def parse_bio(_, _), do: ""
1408 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1409 Repo.transaction(fn ->
1410 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1414 def tag(nickname, tags) when is_binary(nickname),
1415 do: tag(get_by_nickname(nickname), tags)
1417 def tag(%User{} = user, tags),
1418 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1420 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1421 Repo.transaction(fn ->
1422 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1426 def untag(nickname, tags) when is_binary(nickname),
1427 do: untag(get_by_nickname(nickname), tags)
1429 def untag(%User{} = user, tags),
1430 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1432 defp update_tags(%User{} = user, new_tags) do
1433 {:ok, updated_user} =
1435 |> change(%{tags: new_tags})
1436 |> update_and_set_cache()
1441 defp normalize_tags(tags) do
1444 |> Enum.map(&String.downcase/1)
1447 defp local_nickname_regex do
1448 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1449 @extended_local_nickname_regex
1451 @strict_local_nickname_regex
1455 def local_nickname(nickname_or_mention) do
1458 |> String.split("@")
1462 def full_nickname(nickname_or_mention),
1463 do: String.trim_leading(nickname_or_mention, "@")
1465 def error_user(ap_id) do
1469 nickname: "erroruser@example.com",
1470 inserted_at: NaiveDateTime.utc_now()
1474 @spec all_superusers() :: [User.t()]
1475 def all_superusers do
1476 User.Query.build(%{super_users: true, local: true, deactivated: false})
1480 def showing_reblogs?(%User{} = user, %User{} = target) do
1481 target.ap_id not in user.muted_reblogs
1485 The function returns a query to get users with no activity for given interval of days.
1486 Inactive users are those who didn't read any notification, or had any activity where
1487 the user is the activity's actor, during `inactivity_threshold` days.
1488 Deactivated users will not appear in this list.
1492 iex> Pleroma.User.list_inactive_users()
1495 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1496 def list_inactive_users_query(inactivity_threshold \\ 7) do
1497 negative_inactivity_threshold = -inactivity_threshold
1498 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1499 # Subqueries are not supported in `where` clauses, join gets too complicated.
1500 has_read_notifications =
1501 from(n in Pleroma.Notification,
1502 where: n.seen == true,
1504 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1507 |> Pleroma.Repo.all()
1509 from(u in Pleroma.User,
1510 left_join: a in Pleroma.Activity,
1511 on: u.ap_id == a.actor,
1512 where: not is_nil(u.nickname),
1513 where: u.deactivated != ^true,
1514 where: u.id not in ^has_read_notifications,
1517 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1518 is_nil(max(a.inserted_at))
1523 Enable or disable email notifications for user
1527 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1528 Pleroma.User{email_notifications: %{"digest" => true}}
1530 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1531 Pleroma.User{email_notifications: %{"digest" => false}}
1533 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1534 {:ok, t()} | {:error, Ecto.Changeset.t()}
1535 def switch_email_notifications(user, type, status) do
1536 User.update_email_notifications(user, %{type => status})
1540 Set `last_digest_emailed_at` value for the user to current time
1542 @spec touch_last_digest_emailed_at(t()) :: t()
1543 def touch_last_digest_emailed_at(user) do
1544 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1546 {:ok, updated_user} =
1548 |> change(%{last_digest_emailed_at: now})
1549 |> update_and_set_cache()
1554 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1555 def toggle_confirmation(%User{} = user) do
1557 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1558 |> update_and_set_cache()
1561 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1562 def toggle_confirmation(users) do
1563 Enum.map(users, &toggle_confirmation/1)
1566 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1570 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1571 # use instance-default
1572 config = Pleroma.Config.get([:assets, :mascots])
1573 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1574 mascot = Keyword.get(config, default_mascot)
1577 "id" => "default-mascot",
1578 "url" => mascot[:url],
1579 "preview_url" => mascot[:url],
1581 "mime_type" => mascot[:mime_type]
1586 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1588 def ensure_keys_present(%User{} = user) do
1589 with {:ok, pem} <- Keys.generate_rsa_pem() do
1591 |> cast(%{keys: pem}, [:keys])
1592 |> validate_required([:keys])
1593 |> update_and_set_cache()
1597 def get_ap_ids_by_nicknames(nicknames) do
1599 where: u.nickname in ^nicknames,
1605 defdelegate search(query, opts \\ []), to: User.Search
1607 defp put_password_hash(
1608 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1610 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1613 defp put_password_hash(changeset), do: changeset
1615 def is_internal_user?(%User{nickname: nil}), do: true
1616 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1617 def is_internal_user?(_), do: false
1619 # A hack because user delete activities have a fake id for whatever reason
1620 # TODO: Get rid of this
1621 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1623 def get_delivered_users_by_object_id(object_id) do
1625 inner_join: delivery in assoc(u, :deliveries),
1626 where: delivery.object_id == ^object_id
1631 def change_email(user, email) do
1633 |> cast(%{email: email}, [:email])
1634 |> validate_required([:email])
1635 |> unique_constraint(:email)
1636 |> validate_format(:email, @email_regex)
1637 |> update_and_set_cache()
1640 # Internal function; public one is `deactivate/2`
1641 defp set_activation_status(user, deactivated) do
1643 |> cast(%{deactivated: deactivated}, [:deactivated])
1644 |> update_and_set_cache()
1647 def update_banner(user, banner) do
1649 |> cast(%{banner: banner}, [:banner])
1650 |> update_and_set_cache()
1653 def update_background(user, background) do
1655 |> cast(%{background: background}, [:background])
1656 |> update_and_set_cache()
1659 def update_source_data(user, source_data) do
1661 |> cast(%{source_data: source_data}, [:source_data])
1662 |> update_and_set_cache()
1665 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1668 moderator: is_moderator
1672 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1673 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1674 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1675 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1678 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1679 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1683 def fields(%{fields: nil}), do: []
1685 def fields(%{fields: fields}), do: fields
1687 def validate_fields(changeset, remote? \\ false) do
1688 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1689 limit = Pleroma.Config.get([:instance, limit_name], 0)
1692 |> validate_length(:fields, max: limit)
1693 |> validate_change(:fields, fn :fields, fields ->
1694 if Enum.all?(fields, &valid_field?/1) do
1702 defp valid_field?(%{"name" => name, "value" => value}) do
1703 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1704 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1706 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1707 String.length(value) <= value_limit
1710 defp valid_field?(_), do: false
1712 defp truncate_field(%{"name" => name, "value" => value}) do
1714 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1717 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1719 %{"name" => name, "value" => value}
1722 def admin_api_update(user, params) do
1729 |> update_and_set_cache()
1732 def mascot_update(user, url) do
1734 |> cast(%{mascot: url}, [:mascot])
1735 |> validate_required([:mascot])
1736 |> update_and_set_cache()
1739 def mastodon_settings_update(user, settings) do
1741 |> cast(%{settings: settings}, [:settings])
1742 |> validate_required([:settings])
1743 |> update_and_set_cache()
1746 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1747 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1749 if need_confirmation? do
1751 confirmation_pending: true,
1752 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1756 confirmation_pending: false,
1757 confirmation_token: nil
1761 cast(user, params, [:confirmation_pending, :confirmation_token])
1764 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1765 if id not in user.pinned_activities do
1766 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1767 params = %{pinned_activities: user.pinned_activities ++ [id]}
1770 |> cast(params, [:pinned_activities])
1771 |> validate_length(:pinned_activities,
1772 max: max_pinned_statuses,
1773 message: "You have already pinned the maximum number of statuses"
1778 |> update_and_set_cache()
1781 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1782 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1785 |> cast(params, [:pinned_activities])
1786 |> update_and_set_cache()
1789 def update_email_notifications(user, settings) do
1790 email_notifications =
1791 user.email_notifications
1792 |> Map.merge(settings)
1793 |> Map.take(["digest"])
1795 params = %{email_notifications: email_notifications}
1796 fields = [:email_notifications]
1799 |> cast(params, fields)
1800 |> validate_required(fields)
1801 |> update_and_set_cache()
1804 defp set_subscribers(user, subscribers) do
1805 params = %{subscribers: subscribers}
1808 |> cast(params, [:subscribers])
1809 |> validate_required([:subscribers])
1810 |> update_and_set_cache()
1813 def add_to_subscribers(user, subscribed) do
1814 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1817 def remove_from_subscribers(user, subscribed) do
1818 set_subscribers(user, List.delete(user.subscribers, subscribed))
1821 defp set_domain_blocks(user, domain_blocks) do
1822 params = %{domain_blocks: domain_blocks}
1825 |> cast(params, [:domain_blocks])
1826 |> validate_required([:domain_blocks])
1827 |> update_and_set_cache()
1830 def block_domain(user, domain_blocked) do
1831 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1834 def unblock_domain(user, domain_blocked) do
1835 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1838 defp set_blocks(user, blocks) do
1839 params = %{blocks: blocks}
1842 |> cast(params, [:blocks])
1843 |> validate_required([:blocks])
1844 |> update_and_set_cache()
1847 def add_to_block(user, blocked) do
1848 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1851 def remove_from_block(user, blocked) do
1852 set_blocks(user, List.delete(user.blocks, blocked))
1855 defp set_mutes(user, mutes) do
1856 params = %{mutes: mutes}
1859 |> cast(params, [:mutes])
1860 |> validate_required([:mutes])
1861 |> update_and_set_cache()
1864 def add_to_mutes(user, muted, notifications?) do
1865 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1866 set_notification_mutes(
1868 Enum.uniq([muted | user.muted_notifications]),
1874 def remove_from_mutes(user, muted) do
1875 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1876 set_notification_mutes(
1878 List.delete(user.muted_notifications, muted),
1884 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1888 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1889 params = %{muted_notifications: muted_notifications}
1892 |> cast(params, [:muted_notifications])
1893 |> validate_required([:muted_notifications])
1894 |> update_and_set_cache()
1897 def add_reblog_mute(user, ap_id) do
1898 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1901 |> cast(params, [:muted_reblogs])
1902 |> update_and_set_cache()
1905 def remove_reblog_mute(user, ap_id) do
1906 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1909 |> cast(params, [:muted_reblogs])
1910 |> update_and_set_cache()
1913 def set_invisible(user, invisible) do
1914 params = %{invisible: invisible}
1917 |> cast(params, [:invisible])
1918 |> validate_required([:invisible])
1919 |> update_and_set_cache()