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) || blocks_domain?(user, target)
1026 def blocks?(nil, _), do: false
1028 def blocks_ap_id?(%User{} = user, %User{} = target) do
1029 Enum.member?(user.blocks, target.ap_id)
1032 def blocks_ap_id?(_, _), do: false
1034 def blocks_domain?(%User{} = user, %User{} = target) do
1035 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1036 %{host: host} = URI.parse(target.ap_id)
1037 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1040 def blocks_domain?(_, _), do: false
1042 def subscribed_to?(user, %{ap_id: ap_id}) do
1043 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1044 Enum.member?(target.subscribers, user.ap_id)
1048 @spec muted_users(User.t()) :: [User.t()]
1049 def muted_users(user) do
1050 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1054 @spec blocked_users(User.t()) :: [User.t()]
1055 def blocked_users(user) do
1056 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1060 @spec subscribers(User.t()) :: [User.t()]
1061 def subscribers(user) do
1062 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1066 def deactivate_async(user, status \\ true) do
1067 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1070 def deactivate(user, status \\ true)
1072 def deactivate(users, status) when is_list(users) do
1073 Repo.transaction(fn ->
1074 for user <- users, do: deactivate(user, status)
1078 def deactivate(%User{} = user, status) do
1079 with {:ok, user} <- set_activation_status(user, status) do
1082 |> Enum.filter(& &1.local)
1083 |> Enum.each(fn follower ->
1084 follower |> update_following_count() |> set_cache()
1087 # Only update local user counts, remote will be update during the next pull.
1090 |> Enum.filter(& &1.local)
1091 |> Enum.each(&update_follower_count/1)
1097 def update_notification_settings(%User{} = user, settings) do
1100 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1103 notification_settings =
1104 user.notification_settings
1105 |> Map.merge(settings)
1106 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1108 params = %{notification_settings: notification_settings}
1111 |> cast(params, [:notification_settings])
1112 |> validate_required([:notification_settings])
1113 |> update_and_set_cache()
1116 def delete(users) when is_list(users) do
1117 for user <- users, do: delete(user)
1120 def delete(%User{} = user) do
1121 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1124 def perform(:force_password_reset, user), do: force_password_reset(user)
1126 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1127 def perform(:delete, %User{} = user) do
1128 {:ok, _user} = ActivityPub.delete(user)
1130 # Remove all relationships
1133 |> Enum.each(fn follower ->
1134 ActivityPub.unfollow(follower, user)
1135 unfollow(follower, user)
1140 |> Enum.each(fn followed ->
1141 ActivityPub.unfollow(user, followed)
1142 unfollow(user, followed)
1145 delete_user_activities(user)
1146 invalidate_cache(user)
1150 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1151 def perform(:fetch_initial_posts, %User{} = user) do
1152 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1154 # Insert all the posts in reverse order, so they're in the right order on the timeline
1155 user.source_data["outbox"]
1156 |> Utils.fetch_ordered_collection(pages)
1158 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1161 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1163 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1164 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1165 when is_list(blocked_identifiers) do
1167 blocked_identifiers,
1168 fn blocked_identifier ->
1169 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1170 {:ok, blocker} <- block(blocker, blocked),
1171 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1175 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1182 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1183 def perform(:follow_import, %User{} = follower, followed_identifiers)
1184 when is_list(followed_identifiers) do
1186 followed_identifiers,
1187 fn followed_identifier ->
1188 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1189 {:ok, follower} <- maybe_direct_follow(follower, followed),
1190 {:ok, _} <- ActivityPub.follow(follower, followed) do
1194 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1201 @spec external_users_query() :: Ecto.Query.t()
1202 def external_users_query do
1210 @spec external_users(keyword()) :: [User.t()]
1211 def external_users(opts \\ []) do
1213 external_users_query()
1214 |> select([u], struct(u, [:id, :ap_id, :info]))
1218 do: where(query, [u], u.id > ^opts[:max_id]),
1223 do: limit(query, ^opts[:limit]),
1229 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1230 BackgroundWorker.enqueue("blocks_import", %{
1231 "blocker_id" => blocker.id,
1232 "blocked_identifiers" => blocked_identifiers
1236 def follow_import(%User{} = follower, followed_identifiers)
1237 when is_list(followed_identifiers) do
1238 BackgroundWorker.enqueue("follow_import", %{
1239 "follower_id" => follower.id,
1240 "followed_identifiers" => followed_identifiers
1244 def delete_user_activities(%User{ap_id: ap_id}) do
1246 |> Activity.Queries.by_actor()
1247 |> RepoStreamer.chunk_stream(50)
1248 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1252 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1254 |> Object.normalize()
1255 |> ActivityPub.delete()
1258 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1259 object = Object.normalize(activity)
1262 |> get_cached_by_ap_id()
1263 |> ActivityPub.unlike(object)
1266 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1267 object = Object.normalize(activity)
1270 |> get_cached_by_ap_id()
1271 |> ActivityPub.unannounce(object)
1274 defp delete_activity(_activity), do: "Doing nothing"
1276 def html_filter_policy(%User{no_rich_text: true}) do
1277 Pleroma.HTML.Scrubber.TwitterText
1280 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1282 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1284 def get_or_fetch_by_ap_id(ap_id) do
1285 user = get_cached_by_ap_id(ap_id)
1287 if !is_nil(user) and !needs_update?(user) do
1290 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1291 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1293 resp = fetch_by_ap_id(ap_id)
1295 if should_fetch_initial do
1296 with {:ok, %User{} = user} <- resp do
1297 fetch_initial_posts(user)
1306 Creates an internal service actor by URI if missing.
1307 Optionally takes nickname for addressing.
1309 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1310 with user when is_nil(user) <- get_cached_by_ap_id(uri) do
1317 follower_address: uri <> "/followers"
1326 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1329 |> :public_key.pem_decode()
1331 |> :public_key.pem_entry_decode()
1336 def public_key(_), do: {:error, "not found key"}
1338 def get_public_key_for_ap_id(ap_id) do
1339 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1340 {:ok, public_key} <- public_key(user) do
1347 defp blank?(""), do: nil
1348 defp blank?(n), do: n
1350 def insert_or_update_user(data) do
1352 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1353 |> remote_user_creation()
1354 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1358 def ap_enabled?(%User{local: true}), do: true
1359 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1360 def ap_enabled?(_), do: false
1362 @doc "Gets or fetch a user by uri or nickname."
1363 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1364 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1365 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1367 # wait a period of time and return newest version of the User structs
1368 # this is because we have synchronous follow APIs and need to simulate them
1369 # with an async handshake
1370 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1371 with %User{} = a <- get_cached_by_id(a.id),
1372 %User{} = b <- get_cached_by_id(b.id) do
1379 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1380 with :ok <- :timer.sleep(timeout),
1381 %User{} = a <- get_cached_by_id(a.id),
1382 %User{} = b <- get_cached_by_id(b.id) do
1389 def parse_bio(bio) when is_binary(bio) and bio != "" do
1391 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1395 def parse_bio(_), do: ""
1397 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1398 # TODO: get profile URLs other than user.ap_id
1399 profile_urls = [user.ap_id]
1402 |> CommonUtils.format_input("text/plain",
1403 mentions_format: :full,
1404 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1409 def parse_bio(_, _), do: ""
1411 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1412 Repo.transaction(fn ->
1413 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1417 def tag(nickname, tags) when is_binary(nickname),
1418 do: tag(get_by_nickname(nickname), tags)
1420 def tag(%User{} = user, tags),
1421 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1423 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1424 Repo.transaction(fn ->
1425 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1429 def untag(nickname, tags) when is_binary(nickname),
1430 do: untag(get_by_nickname(nickname), tags)
1432 def untag(%User{} = user, tags),
1433 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1435 defp update_tags(%User{} = user, new_tags) do
1436 {:ok, updated_user} =
1438 |> change(%{tags: new_tags})
1439 |> update_and_set_cache()
1444 defp normalize_tags(tags) do
1447 |> Enum.map(&String.downcase/1)
1450 defp local_nickname_regex do
1451 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1452 @extended_local_nickname_regex
1454 @strict_local_nickname_regex
1458 def local_nickname(nickname_or_mention) do
1461 |> String.split("@")
1465 def full_nickname(nickname_or_mention),
1466 do: String.trim_leading(nickname_or_mention, "@")
1468 def error_user(ap_id) do
1472 nickname: "erroruser@example.com",
1473 inserted_at: NaiveDateTime.utc_now()
1477 @spec all_superusers() :: [User.t()]
1478 def all_superusers do
1479 User.Query.build(%{super_users: true, local: true, deactivated: false})
1483 def showing_reblogs?(%User{} = user, %User{} = target) do
1484 target.ap_id not in user.muted_reblogs
1488 The function returns a query to get users with no activity for given interval of days.
1489 Inactive users are those who didn't read any notification, or had any activity where
1490 the user is the activity's actor, during `inactivity_threshold` days.
1491 Deactivated users will not appear in this list.
1495 iex> Pleroma.User.list_inactive_users()
1498 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1499 def list_inactive_users_query(inactivity_threshold \\ 7) do
1500 negative_inactivity_threshold = -inactivity_threshold
1501 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1502 # Subqueries are not supported in `where` clauses, join gets too complicated.
1503 has_read_notifications =
1504 from(n in Pleroma.Notification,
1505 where: n.seen == true,
1507 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1510 |> Pleroma.Repo.all()
1512 from(u in Pleroma.User,
1513 left_join: a in Pleroma.Activity,
1514 on: u.ap_id == a.actor,
1515 where: not is_nil(u.nickname),
1516 where: u.deactivated != ^true,
1517 where: u.id not in ^has_read_notifications,
1520 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1521 is_nil(max(a.inserted_at))
1526 Enable or disable email notifications for user
1530 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1531 Pleroma.User{email_notifications: %{"digest" => true}}
1533 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1534 Pleroma.User{email_notifications: %{"digest" => false}}
1536 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1537 {:ok, t()} | {:error, Ecto.Changeset.t()}
1538 def switch_email_notifications(user, type, status) do
1539 User.update_email_notifications(user, %{type => status})
1543 Set `last_digest_emailed_at` value for the user to current time
1545 @spec touch_last_digest_emailed_at(t()) :: t()
1546 def touch_last_digest_emailed_at(user) do
1547 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1549 {:ok, updated_user} =
1551 |> change(%{last_digest_emailed_at: now})
1552 |> update_and_set_cache()
1557 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1558 def toggle_confirmation(%User{} = user) do
1560 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1561 |> update_and_set_cache()
1564 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1565 def toggle_confirmation(users) do
1566 Enum.map(users, &toggle_confirmation/1)
1569 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1573 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1574 # use instance-default
1575 config = Pleroma.Config.get([:assets, :mascots])
1576 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1577 mascot = Keyword.get(config, default_mascot)
1580 "id" => "default-mascot",
1581 "url" => mascot[:url],
1582 "preview_url" => mascot[:url],
1584 "mime_type" => mascot[:mime_type]
1589 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1591 def ensure_keys_present(%User{} = user) do
1592 with {:ok, pem} <- Keys.generate_rsa_pem() do
1594 |> cast(%{keys: pem}, [:keys])
1595 |> validate_required([:keys])
1596 |> update_and_set_cache()
1600 def get_ap_ids_by_nicknames(nicknames) do
1602 where: u.nickname in ^nicknames,
1608 defdelegate search(query, opts \\ []), to: User.Search
1610 defp put_password_hash(
1611 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1613 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1616 defp put_password_hash(changeset), do: changeset
1618 def is_internal_user?(%User{nickname: nil}), do: true
1619 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1620 def is_internal_user?(_), do: false
1622 # A hack because user delete activities have a fake id for whatever reason
1623 # TODO: Get rid of this
1624 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1626 def get_delivered_users_by_object_id(object_id) do
1628 inner_join: delivery in assoc(u, :deliveries),
1629 where: delivery.object_id == ^object_id
1634 def change_email(user, email) do
1636 |> cast(%{email: email}, [:email])
1637 |> validate_required([:email])
1638 |> unique_constraint(:email)
1639 |> validate_format(:email, @email_regex)
1640 |> update_and_set_cache()
1643 # Internal function; public one is `deactivate/2`
1644 defp set_activation_status(user, deactivated) do
1646 |> cast(%{deactivated: deactivated}, [:deactivated])
1647 |> update_and_set_cache()
1650 def update_banner(user, banner) do
1652 |> cast(%{banner: banner}, [:banner])
1653 |> update_and_set_cache()
1656 def update_background(user, background) do
1658 |> cast(%{background: background}, [:background])
1659 |> update_and_set_cache()
1662 def update_source_data(user, source_data) do
1664 |> cast(%{source_data: source_data}, [:source_data])
1665 |> update_and_set_cache()
1668 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1671 moderator: is_moderator
1675 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1676 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1677 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1678 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1681 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1682 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1686 def fields(%{fields: nil}), do: []
1688 def fields(%{fields: fields}), do: fields
1690 def validate_fields(changeset, remote? \\ false) do
1691 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1692 limit = Pleroma.Config.get([:instance, limit_name], 0)
1695 |> validate_length(:fields, max: limit)
1696 |> validate_change(:fields, fn :fields, fields ->
1697 if Enum.all?(fields, &valid_field?/1) do
1705 defp valid_field?(%{"name" => name, "value" => value}) do
1706 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1707 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1709 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1710 String.length(value) <= value_limit
1713 defp valid_field?(_), do: false
1715 defp truncate_field(%{"name" => name, "value" => value}) do
1717 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1720 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1722 %{"name" => name, "value" => value}
1725 def admin_api_update(user, params) do
1732 |> update_and_set_cache()
1735 def mascot_update(user, url) do
1737 |> cast(%{mascot: url}, [:mascot])
1738 |> validate_required([:mascot])
1739 |> update_and_set_cache()
1742 def mastodon_settings_update(user, settings) do
1744 |> cast(%{settings: settings}, [:settings])
1745 |> validate_required([:settings])
1746 |> update_and_set_cache()
1749 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1750 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1752 if need_confirmation? do
1754 confirmation_pending: true,
1755 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1759 confirmation_pending: false,
1760 confirmation_token: nil
1764 cast(user, params, [:confirmation_pending, :confirmation_token])
1767 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1768 if id not in user.pinned_activities do
1769 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1770 params = %{pinned_activities: user.pinned_activities ++ [id]}
1773 |> cast(params, [:pinned_activities])
1774 |> validate_length(:pinned_activities,
1775 max: max_pinned_statuses,
1776 message: "You have already pinned the maximum number of statuses"
1781 |> update_and_set_cache()
1784 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1785 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1788 |> cast(params, [:pinned_activities])
1789 |> update_and_set_cache()
1792 def update_email_notifications(user, settings) do
1793 email_notifications =
1794 user.email_notifications
1795 |> Map.merge(settings)
1796 |> Map.take(["digest"])
1798 params = %{email_notifications: email_notifications}
1799 fields = [:email_notifications]
1802 |> cast(params, fields)
1803 |> validate_required(fields)
1804 |> update_and_set_cache()
1807 defp set_subscribers(user, subscribers) do
1808 params = %{subscribers: subscribers}
1811 |> cast(params, [:subscribers])
1812 |> validate_required([:subscribers])
1813 |> update_and_set_cache()
1816 def add_to_subscribers(user, subscribed) do
1817 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1820 def remove_from_subscribers(user, subscribed) do
1821 set_subscribers(user, List.delete(user.subscribers, subscribed))
1824 defp set_domain_blocks(user, domain_blocks) do
1825 params = %{domain_blocks: domain_blocks}
1828 |> cast(params, [:domain_blocks])
1829 |> validate_required([:domain_blocks])
1830 |> update_and_set_cache()
1833 def block_domain(user, domain_blocked) do
1834 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1837 def unblock_domain(user, domain_blocked) do
1838 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1841 defp set_blocks(user, blocks) do
1842 params = %{blocks: blocks}
1845 |> cast(params, [:blocks])
1846 |> validate_required([:blocks])
1847 |> update_and_set_cache()
1850 def add_to_block(user, blocked) do
1851 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1854 def remove_from_block(user, blocked) do
1855 set_blocks(user, List.delete(user.blocks, blocked))
1858 defp set_mutes(user, mutes) do
1859 params = %{mutes: mutes}
1862 |> cast(params, [:mutes])
1863 |> validate_required([:mutes])
1864 |> update_and_set_cache()
1867 def add_to_mutes(user, muted, notifications?) do
1868 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1869 set_notification_mutes(
1871 Enum.uniq([muted | user.muted_notifications]),
1877 def remove_from_mutes(user, muted) do
1878 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1879 set_notification_mutes(
1881 List.delete(user.muted_notifications, muted),
1887 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1891 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1892 params = %{muted_notifications: muted_notifications}
1895 |> cast(params, [:muted_notifications])
1896 |> validate_required([:muted_notifications])
1897 |> update_and_set_cache()
1900 def add_reblog_mute(user, ap_id) do
1901 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1904 |> cast(params, [:muted_reblogs])
1905 |> update_and_set_cache()
1908 def remove_reblog_mute(user, ap_id) do
1909 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1912 |> cast(params, [:muted_reblogs])
1913 |> update_and_set_cache()
1916 def set_invisible(user, invisible) do
1917 params = %{invisible: invisible}
1920 |> cast(params, [:invisible])
1921 |> validate_required([:invisible])
1922 |> update_and_set_cache()