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 user_info(%User{} = user, args \\ %{}) do
181 following_count = Map.get(args, :following_count, user.following_count)
182 follower_count = Map.get(args, :follower_count, user.follower_count)
185 note_count: user.note_count,
187 confirmation_pending: user.confirmation_pending,
188 default_scope: user.default_scope,
189 follower_count: follower_count,
190 following_count: following_count
194 def follow_state(%User{} = user, %User{} = target) do
195 case Utils.fetch_latest_follow(user, target) do
196 %{data: %{"state" => state}} -> state
197 # Ideally this would be nil, but then Cachex does not commit the value
202 def get_cached_follow_state(user, target) do
203 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
204 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
207 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
208 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
209 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
212 def set_info_cache(user, args) do
213 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
216 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
217 def restrict_deactivated(query) do
218 from(u in query, where: u.deactivated != ^true)
221 defdelegate following_count(user), to: FollowingRelationship
223 defp truncate_fields_param(params) do
224 if Map.has_key?(params, :fields) do
225 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
231 defp truncate_if_exists(params, key, max_length) do
232 if Map.has_key?(params, key) and is_binary(params[key]) do
233 {value, _chopped} = String.split_at(params[key], max_length)
234 Map.put(params, key, value)
240 def remote_user_creation(params) do
241 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
242 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
246 |> Map.put(:info, params[:info] || %{})
247 |> truncate_if_exists(:name, name_limit)
248 |> truncate_if_exists(:bio, bio_limit)
249 |> truncate_fields_param()
269 :hide_followers_count,
278 |> validate_required([:name, :ap_id])
279 |> unique_constraint(:nickname)
280 |> validate_format(:nickname, @email_regex)
281 |> validate_length(:bio, max: bio_limit)
282 |> validate_length(:name, max: name_limit)
283 |> validate_fields(true)
285 case params[:source_data] do
286 %{"followers" => followers, "following" => following} ->
288 |> put_change(:follower_address, followers)
289 |> put_change(:following_address, following)
292 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
293 put_change(changeset, :follower_address, followers)
297 def update_changeset(struct, params \\ %{}) do
298 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
299 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
314 :hide_followers_count,
319 :skip_thread_containment,
322 :pleroma_settings_store,
326 |> unique_constraint(:nickname)
327 |> validate_format(:nickname, local_nickname_regex())
328 |> validate_length(:bio, max: bio_limit)
329 |> validate_length(:name, min: 1, max: name_limit)
330 |> validate_fields(false)
333 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
334 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
335 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
337 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
339 params = if remote?, do: truncate_fields_param(params), else: params
362 :hide_followers_count,
366 |> unique_constraint(:nickname)
367 |> validate_format(:nickname, local_nickname_regex())
368 |> validate_length(:bio, max: bio_limit)
369 |> validate_length(:name, max: name_limit)
370 |> validate_fields(remote?)
373 def password_update_changeset(struct, params) do
375 |> cast(params, [:password, :password_confirmation])
376 |> validate_required([:password, :password_confirmation])
377 |> validate_confirmation(:password)
378 |> put_password_hash()
379 |> put_change(:password_reset_pending, false)
382 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
383 def reset_password(%User{id: user_id} = user, data) do
386 |> Multi.update(:user, password_update_changeset(user, data))
387 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
388 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
390 case Repo.transaction(multi) do
391 {:ok, %{user: user} = _} -> set_cache(user)
392 {:error, _, changeset, _} -> {:error, changeset}
396 def update_password_reset_pending(user, value) do
399 |> put_change(:password_reset_pending, value)
400 |> update_and_set_cache()
403 def force_password_reset_async(user) do
404 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
407 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
408 def force_password_reset(user), do: update_password_reset_pending(user, true)
410 def register_changeset(struct, params \\ %{}, opts \\ []) do
411 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
412 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
415 if is_nil(opts[:need_confirmation]) do
416 Pleroma.Config.get([:instance, :account_activation_required])
418 opts[:need_confirmation]
422 |> confirmation_changeset(need_confirmation: need_confirmation?)
423 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
424 |> validate_required([:name, :nickname, :password, :password_confirmation])
425 |> validate_confirmation(:password)
426 |> unique_constraint(:email)
427 |> unique_constraint(:nickname)
428 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
429 |> validate_format(:nickname, local_nickname_regex())
430 |> validate_format(:email, @email_regex)
431 |> validate_length(:bio, max: bio_limit)
432 |> validate_length(:name, min: 1, max: name_limit)
433 |> maybe_validate_required_email(opts[:external])
436 |> unique_constraint(:ap_id)
437 |> put_following_and_follower_address()
440 def maybe_validate_required_email(changeset, true), do: changeset
441 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
443 defp put_ap_id(changeset) do
444 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
445 put_change(changeset, :ap_id, ap_id)
448 defp put_following_and_follower_address(changeset) do
449 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
452 |> put_change(:follower_address, followers)
455 defp autofollow_users(user) do
456 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
459 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
462 follow_all(user, autofollowed_users)
465 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
466 def register(%Ecto.Changeset{} = changeset) do
467 with {:ok, user} <- Repo.insert(changeset) do
468 post_register_action(user)
472 def post_register_action(%User{} = user) do
473 with {:ok, user} <- autofollow_users(user),
474 {:ok, user} <- set_cache(user),
475 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
476 {:ok, _} <- try_send_confirmation_email(user) do
481 def try_send_confirmation_email(%User{} = user) do
482 if user.confirmation_pending &&
483 Pleroma.Config.get([:instance, :account_activation_required]) do
485 |> Pleroma.Emails.UserEmail.account_confirmation_email()
486 |> Pleroma.Emails.Mailer.deliver_async()
494 def needs_update?(%User{local: true}), do: false
496 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
498 def needs_update?(%User{local: false} = user) do
499 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
502 def needs_update?(_), do: true
504 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
505 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
506 follow(follower, followed, "pending")
509 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
510 follow(follower, followed)
513 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
514 if not ap_enabled?(followed) do
515 follow(follower, followed)
521 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
522 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
523 def follow_all(follower, followeds) do
525 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
526 |> Enum.each(&follow(follower, &1, "accept"))
531 defdelegate following(user), to: FollowingRelationship
533 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
534 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
537 followed.deactivated ->
538 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
540 deny_follow_blocked and blocks?(followed, follower) ->
541 {:error, "Could not follow user: #{followed.nickname} blocked you."}
544 FollowingRelationship.follow(follower, followed, state)
546 {:ok, _} = update_follower_count(followed)
549 |> update_following_count()
554 def unfollow(%User{} = follower, %User{} = followed) do
555 if following?(follower, followed) and follower.ap_id != followed.ap_id do
556 FollowingRelationship.unfollow(follower, followed)
558 {:ok, followed} = update_follower_count(followed)
562 |> update_following_count()
565 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
567 {:error, "Not subscribed!"}
571 defdelegate following?(follower, followed), to: FollowingRelationship
573 def locked?(%User{} = user) do
578 Repo.get_by(User, id: id)
581 def get_by_ap_id(ap_id) do
582 Repo.get_by(User, ap_id: ap_id)
585 def get_all_by_ap_id(ap_ids) do
586 from(u in __MODULE__,
587 where: u.ap_id in ^ap_ids
592 def get_all_by_ids(ids) do
593 from(u in __MODULE__, where: u.id in ^ids)
597 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
598 # of the ap_id and the domain and tries to get that user
599 def get_by_guessed_nickname(ap_id) do
600 domain = URI.parse(ap_id).host
601 name = List.last(String.split(ap_id, "/"))
602 nickname = "#{name}@#{domain}"
604 get_cached_by_nickname(nickname)
607 def set_cache({:ok, user}), do: set_cache(user)
608 def set_cache({:error, err}), do: {:error, err}
610 def set_cache(%User{} = user) do
611 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
612 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
613 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
617 def update_and_set_cache(struct, params) do
619 |> update_changeset(params)
620 |> update_and_set_cache()
623 def update_and_set_cache(changeset) do
624 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
629 def invalidate_cache(user) do
630 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
631 Cachex.del(:user_cache, "nickname:#{user.nickname}")
632 Cachex.del(:user_cache, "user_info:#{user.id}")
635 def get_cached_by_ap_id(ap_id) do
636 key = "ap_id:#{ap_id}"
637 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
640 def get_cached_by_id(id) do
644 Cachex.fetch!(:user_cache, key, fn _ ->
648 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
649 {:commit, user.ap_id}
655 get_cached_by_ap_id(ap_id)
658 def get_cached_by_nickname(nickname) do
659 key = "nickname:#{nickname}"
661 Cachex.fetch!(:user_cache, key, fn ->
662 case get_or_fetch_by_nickname(nickname) do
663 {:ok, user} -> {:commit, user}
664 {:error, _error} -> {:ignore, nil}
669 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
670 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
673 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
674 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
676 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
677 get_cached_by_nickname(nickname_or_id)
679 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
680 get_cached_by_nickname(nickname_or_id)
687 def get_by_nickname(nickname) do
688 Repo.get_by(User, nickname: nickname) ||
689 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
690 Repo.get_by(User, nickname: local_nickname(nickname))
694 def get_by_email(email), do: Repo.get_by(User, email: email)
696 def get_by_nickname_or_email(nickname_or_email) do
697 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
700 def get_cached_user_info(user) do
701 key = "user_info:#{user.id}"
702 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
705 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
707 def get_or_fetch_by_nickname(nickname) do
708 with %User{} = user <- get_by_nickname(nickname) do
712 with [_nick, _domain] <- String.split(nickname, "@"),
713 {:ok, user} <- fetch_by_nickname(nickname) do
714 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
715 fetch_initial_posts(user)
720 _e -> {:error, "not found " <> nickname}
725 @doc "Fetch some posts when the user has just been federated with"
726 def fetch_initial_posts(user) do
727 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
730 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
731 def get_followers_query(%User{} = user, nil) do
732 User.Query.build(%{followers: user, deactivated: false})
735 def get_followers_query(user, page) do
737 |> get_followers_query(nil)
738 |> User.Query.paginate(page, 20)
741 @spec get_followers_query(User.t()) :: Ecto.Query.t()
742 def get_followers_query(user), do: get_followers_query(user, nil)
744 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
745 def get_followers(user, page \\ nil) do
747 |> get_followers_query(page)
751 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
752 def get_external_followers(user, page \\ nil) do
754 |> get_followers_query(page)
755 |> User.Query.build(%{external: true})
759 def get_followers_ids(user, page \\ nil) do
761 |> get_followers_query(page)
766 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
767 def get_friends_query(%User{} = user, nil) do
768 User.Query.build(%{friends: user, deactivated: false})
771 def get_friends_query(user, page) do
773 |> get_friends_query(nil)
774 |> User.Query.paginate(page, 20)
777 @spec get_friends_query(User.t()) :: Ecto.Query.t()
778 def get_friends_query(user), do: get_friends_query(user, nil)
780 def get_friends(user, page \\ nil) do
782 |> get_friends_query(page)
786 def get_friends_ids(user, page \\ nil) do
788 |> get_friends_query(page)
793 defdelegate get_follow_requests(user), to: FollowingRelationship
795 def increase_note_count(%User{} = user) do
797 |> where(id: ^user.id)
798 |> update([u], inc: [note_count: 1])
800 |> Repo.update_all([])
802 {1, [user]} -> set_cache(user)
807 def decrease_note_count(%User{} = user) do
809 |> where(id: ^user.id)
812 note_count: fragment("greatest(0, note_count - 1)")
816 |> Repo.update_all([])
818 {1, [user]} -> set_cache(user)
823 def update_note_count(%User{} = user, note_count \\ nil) do
828 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
834 |> cast(%{note_count: note_count}, [:note_count])
835 |> update_and_set_cache()
838 @spec maybe_fetch_follow_information(User.t()) :: User.t()
839 def maybe_fetch_follow_information(user) do
840 with {:ok, user} <- fetch_follow_information(user) do
844 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
850 def fetch_follow_information(user) do
851 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
853 |> follow_information_changeset(info)
854 |> update_and_set_cache()
858 defp follow_information_changeset(user, params) do
865 :hide_followers_count,
870 def update_follower_count(%User{} = user) do
871 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
872 follower_count_query =
873 User.Query.build(%{followers: user, deactivated: false})
874 |> select([u], %{count: count(u.id)})
877 |> where(id: ^user.id)
878 |> join(:inner, [u], s in subquery(follower_count_query))
880 set: [follower_count: s.count]
883 |> Repo.update_all([])
885 {1, [user]} -> set_cache(user)
889 {:ok, maybe_fetch_follow_information(user)}
893 @spec update_following_count(User.t()) :: User.t()
894 def update_following_count(%User{local: false} = user) do
895 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
896 maybe_fetch_follow_information(user)
902 def update_following_count(%User{local: true} = user) do
903 following_count = FollowingRelationship.following_count(user)
906 |> follow_information_changeset(%{following_count: following_count})
910 def set_unread_conversation_count(%User{local: true} = user) do
911 unread_query = Participation.unread_conversation_count_for_user(user)
914 |> join(:inner, [u], p in subquery(unread_query))
916 set: [unread_conversation_count: p.count]
918 |> where([u], u.id == ^user.id)
920 |> Repo.update_all([])
922 {1, [user]} -> set_cache(user)
927 def set_unread_conversation_count(user), do: {:ok, user}
929 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
931 Participation.unread_conversation_count_for_user(user)
932 |> where([p], p.conversation_id == ^conversation.id)
935 |> join(:inner, [u], p in subquery(unread_query))
937 inc: [unread_conversation_count: 1]
939 |> where([u], u.id == ^user.id)
940 |> where([u, p], p.count == 0)
942 |> Repo.update_all([])
944 {1, [user]} -> set_cache(user)
949 def increment_unread_conversation_count(_, user), do: {:ok, user}
951 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
952 def get_users_from_set(ap_ids, local_only \\ true) do
953 criteria = %{ap_id: ap_ids, deactivated: false}
954 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
956 User.Query.build(criteria)
960 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
961 def get_recipients_from_activity(%Activity{recipients: to}) do
962 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
966 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
967 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
968 add_to_mutes(muter, ap_id, notifications?)
971 def unmute(muter, %{ap_id: ap_id}) do
972 remove_from_mutes(muter, ap_id)
975 def subscribe(subscriber, %{ap_id: ap_id}) do
976 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
977 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
979 if blocks?(subscribed, subscriber) and deny_follow_blocked do
980 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
982 User.add_to_subscribers(subscribed, subscriber.ap_id)
987 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
988 with %User{} = user <- get_cached_by_ap_id(ap_id) do
989 User.remove_from_subscribers(user, unsubscriber.ap_id)
993 def block(blocker, %User{ap_id: ap_id} = blocked) do
994 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
996 if following?(blocker, blocked) do
997 {:ok, blocker, _} = unfollow(blocker, blocked)
1003 # clear any requested follows as well
1005 case CommonAPI.reject_follow_request(blocked, blocker) do
1006 {:ok, %User{} = updated_blocked} -> updated_blocked
1011 if subscribed_to?(blocked, blocker) do
1012 {:ok, blocker} = unsubscribe(blocked, blocker)
1018 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1020 {:ok, blocker} = update_follower_count(blocker)
1021 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1022 add_to_block(blocker, ap_id)
1025 # helper to handle the block given only an actor's AP id
1026 def block(blocker, %{ap_id: ap_id}) do
1027 block(blocker, get_cached_by_ap_id(ap_id))
1030 def unblock(blocker, %{ap_id: ap_id}) do
1031 remove_from_block(blocker, ap_id)
1034 def mutes?(nil, _), do: false
1035 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1037 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1038 def muted_notifications?(nil, _), do: false
1040 def muted_notifications?(user, %{ap_id: ap_id}),
1041 do: Enum.member?(user.muted_notifications, ap_id)
1043 def blocks?(%User{} = user, %User{} = target) do
1044 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1047 def blocks?(nil, _), do: false
1049 def blocks_ap_id?(%User{} = user, %User{} = target) do
1050 Enum.member?(user.blocks, target.ap_id)
1053 def blocks_ap_id?(_, _), do: false
1055 def blocks_domain?(%User{} = user, %User{} = target) do
1056 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1057 %{host: host} = URI.parse(target.ap_id)
1058 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1061 def blocks_domain?(_, _), do: false
1063 def subscribed_to?(user, %{ap_id: ap_id}) do
1064 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1065 Enum.member?(target.subscribers, user.ap_id)
1069 @spec muted_users(User.t()) :: [User.t()]
1070 def muted_users(user) do
1071 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1075 @spec blocked_users(User.t()) :: [User.t()]
1076 def blocked_users(user) do
1077 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1081 @spec subscribers(User.t()) :: [User.t()]
1082 def subscribers(user) do
1083 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1087 def deactivate_async(user, status \\ true) do
1088 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1091 def deactivate(user, status \\ true)
1093 def deactivate(users, status) when is_list(users) do
1094 Repo.transaction(fn ->
1095 for user <- users, do: deactivate(user, status)
1099 def deactivate(%User{} = user, status) do
1100 with {:ok, user} <- set_activation_status(user, status) do
1103 |> Enum.filter(& &1.local)
1104 |> Enum.each(fn follower ->
1105 follower |> update_following_count() |> set_cache()
1108 # Only update local user counts, remote will be update during the next pull.
1111 |> Enum.filter(& &1.local)
1112 |> Enum.each(&update_follower_count/1)
1118 def update_notification_settings(%User{} = user, settings) do
1121 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1124 notification_settings =
1125 user.notification_settings
1126 |> Map.merge(settings)
1127 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1129 params = %{notification_settings: notification_settings}
1132 |> cast(params, [:notification_settings])
1133 |> validate_required([:notification_settings])
1134 |> update_and_set_cache()
1137 def delete(users) when is_list(users) do
1138 for user <- users, do: delete(user)
1141 def delete(%User{} = user) do
1142 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1145 def perform(:force_password_reset, user), do: force_password_reset(user)
1147 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1148 def perform(:delete, %User{} = user) do
1149 {:ok, _user} = ActivityPub.delete(user)
1151 # Remove all relationships
1154 |> Enum.each(fn follower ->
1155 ActivityPub.unfollow(follower, user)
1156 unfollow(follower, user)
1161 |> Enum.each(fn followed ->
1162 ActivityPub.unfollow(user, followed)
1163 unfollow(user, followed)
1166 delete_user_activities(user)
1167 invalidate_cache(user)
1171 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1172 def perform(:fetch_initial_posts, %User{} = user) do
1173 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1175 # Insert all the posts in reverse order, so they're in the right order on the timeline
1176 user.source_data["outbox"]
1177 |> Utils.fetch_ordered_collection(pages)
1179 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1182 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1184 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1185 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1186 when is_list(blocked_identifiers) do
1188 blocked_identifiers,
1189 fn blocked_identifier ->
1190 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1191 {:ok, blocker} <- block(blocker, blocked),
1192 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1196 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1203 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1204 def perform(:follow_import, %User{} = follower, followed_identifiers)
1205 when is_list(followed_identifiers) do
1207 followed_identifiers,
1208 fn followed_identifier ->
1209 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1210 {:ok, follower} <- maybe_direct_follow(follower, followed),
1211 {:ok, _} <- ActivityPub.follow(follower, followed) do
1215 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1222 @spec external_users_query() :: Ecto.Query.t()
1223 def external_users_query do
1231 @spec external_users(keyword()) :: [User.t()]
1232 def external_users(opts \\ []) do
1234 external_users_query()
1235 |> select([u], struct(u, [:id, :ap_id, :info]))
1239 do: where(query, [u], u.id > ^opts[:max_id]),
1244 do: limit(query, ^opts[:limit]),
1250 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1251 BackgroundWorker.enqueue("blocks_import", %{
1252 "blocker_id" => blocker.id,
1253 "blocked_identifiers" => blocked_identifiers
1257 def follow_import(%User{} = follower, followed_identifiers)
1258 when is_list(followed_identifiers) do
1259 BackgroundWorker.enqueue("follow_import", %{
1260 "follower_id" => follower.id,
1261 "followed_identifiers" => followed_identifiers
1265 def delete_user_activities(%User{ap_id: ap_id}) do
1267 |> Activity.Queries.by_actor()
1268 |> RepoStreamer.chunk_stream(50)
1269 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1273 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1275 |> Object.normalize()
1276 |> ActivityPub.delete()
1279 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1280 object = Object.normalize(activity)
1283 |> get_cached_by_ap_id()
1284 |> ActivityPub.unlike(object)
1287 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1288 object = Object.normalize(activity)
1291 |> get_cached_by_ap_id()
1292 |> ActivityPub.unannounce(object)
1295 defp delete_activity(_activity), do: "Doing nothing"
1297 def html_filter_policy(%User{no_rich_text: true}) do
1298 Pleroma.HTML.Scrubber.TwitterText
1301 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1303 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1305 def get_or_fetch_by_ap_id(ap_id) do
1306 user = get_cached_by_ap_id(ap_id)
1308 if !is_nil(user) and !needs_update?(user) do
1311 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1312 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1314 resp = fetch_by_ap_id(ap_id)
1316 if should_fetch_initial do
1317 with {:ok, %User{} = user} <- resp do
1318 fetch_initial_posts(user)
1327 Creates an internal service actor by URI if missing.
1328 Optionally takes nickname for addressing.
1330 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1331 with user when is_nil(user) <- get_cached_by_ap_id(uri) do
1338 follower_address: uri <> "/followers"
1347 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1350 |> :public_key.pem_decode()
1352 |> :public_key.pem_entry_decode()
1357 def public_key(_), do: {:error, "not found key"}
1359 def get_public_key_for_ap_id(ap_id) do
1360 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1361 {:ok, public_key} <- public_key(user) do
1368 defp blank?(""), do: nil
1369 defp blank?(n), do: n
1371 def insert_or_update_user(data) do
1373 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1374 |> remote_user_creation()
1375 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1379 def ap_enabled?(%User{local: true}), do: true
1380 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1381 def ap_enabled?(_), do: false
1383 @doc "Gets or fetch a user by uri or nickname."
1384 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1385 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1386 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1388 # wait a period of time and return newest version of the User structs
1389 # this is because we have synchronous follow APIs and need to simulate them
1390 # with an async handshake
1391 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1392 with %User{} = a <- get_cached_by_id(a.id),
1393 %User{} = b <- get_cached_by_id(b.id) do
1400 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1401 with :ok <- :timer.sleep(timeout),
1402 %User{} = a <- get_cached_by_id(a.id),
1403 %User{} = b <- get_cached_by_id(b.id) do
1410 def parse_bio(bio) when is_binary(bio) and bio != "" do
1412 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1416 def parse_bio(_), do: ""
1418 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1419 # TODO: get profile URLs other than user.ap_id
1420 profile_urls = [user.ap_id]
1423 |> CommonUtils.format_input("text/plain",
1424 mentions_format: :full,
1425 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1430 def parse_bio(_, _), do: ""
1432 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1433 Repo.transaction(fn ->
1434 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1438 def tag(nickname, tags) when is_binary(nickname),
1439 do: tag(get_by_nickname(nickname), tags)
1441 def tag(%User{} = user, tags),
1442 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1444 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1445 Repo.transaction(fn ->
1446 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1450 def untag(nickname, tags) when is_binary(nickname),
1451 do: untag(get_by_nickname(nickname), tags)
1453 def untag(%User{} = user, tags),
1454 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1456 defp update_tags(%User{} = user, new_tags) do
1457 {:ok, updated_user} =
1459 |> change(%{tags: new_tags})
1460 |> update_and_set_cache()
1465 defp normalize_tags(tags) do
1468 |> Enum.map(&String.downcase/1)
1471 defp local_nickname_regex do
1472 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1473 @extended_local_nickname_regex
1475 @strict_local_nickname_regex
1479 def local_nickname(nickname_or_mention) do
1482 |> String.split("@")
1486 def full_nickname(nickname_or_mention),
1487 do: String.trim_leading(nickname_or_mention, "@")
1489 def error_user(ap_id) do
1493 nickname: "erroruser@example.com",
1494 inserted_at: NaiveDateTime.utc_now()
1498 @spec all_superusers() :: [User.t()]
1499 def all_superusers do
1500 User.Query.build(%{super_users: true, local: true, deactivated: false})
1504 def showing_reblogs?(%User{} = user, %User{} = target) do
1505 target.ap_id not in user.muted_reblogs
1509 The function returns a query to get users with no activity for given interval of days.
1510 Inactive users are those who didn't read any notification, or had any activity where
1511 the user is the activity's actor, during `inactivity_threshold` days.
1512 Deactivated users will not appear in this list.
1516 iex> Pleroma.User.list_inactive_users()
1519 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1520 def list_inactive_users_query(inactivity_threshold \\ 7) do
1521 negative_inactivity_threshold = -inactivity_threshold
1522 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1523 # Subqueries are not supported in `where` clauses, join gets too complicated.
1524 has_read_notifications =
1525 from(n in Pleroma.Notification,
1526 where: n.seen == true,
1528 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1531 |> Pleroma.Repo.all()
1533 from(u in Pleroma.User,
1534 left_join: a in Pleroma.Activity,
1535 on: u.ap_id == a.actor,
1536 where: not is_nil(u.nickname),
1537 where: u.deactivated != ^true,
1538 where: u.id not in ^has_read_notifications,
1541 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1542 is_nil(max(a.inserted_at))
1547 Enable or disable email notifications for user
1551 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1552 Pleroma.User{email_notifications: %{"digest" => true}}
1554 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1555 Pleroma.User{email_notifications: %{"digest" => false}}
1557 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1558 {:ok, t()} | {:error, Ecto.Changeset.t()}
1559 def switch_email_notifications(user, type, status) do
1560 User.update_email_notifications(user, %{type => status})
1564 Set `last_digest_emailed_at` value for the user to current time
1566 @spec touch_last_digest_emailed_at(t()) :: t()
1567 def touch_last_digest_emailed_at(user) do
1568 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1570 {:ok, updated_user} =
1572 |> change(%{last_digest_emailed_at: now})
1573 |> update_and_set_cache()
1578 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1579 def toggle_confirmation(%User{} = user) do
1581 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1582 |> update_and_set_cache()
1585 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1589 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1590 # use instance-default
1591 config = Pleroma.Config.get([:assets, :mascots])
1592 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1593 mascot = Keyword.get(config, default_mascot)
1596 "id" => "default-mascot",
1597 "url" => mascot[:url],
1598 "preview_url" => mascot[:url],
1600 "mime_type" => mascot[:mime_type]
1605 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1607 def ensure_keys_present(%User{} = user) do
1608 with {:ok, pem} <- Keys.generate_rsa_pem() do
1610 |> cast(%{keys: pem}, [:keys])
1611 |> validate_required([:keys])
1612 |> update_and_set_cache()
1616 def get_ap_ids_by_nicknames(nicknames) do
1618 where: u.nickname in ^nicknames,
1624 defdelegate search(query, opts \\ []), to: User.Search
1626 defp put_password_hash(
1627 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1629 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1632 defp put_password_hash(changeset), do: changeset
1634 def is_internal_user?(%User{nickname: nil}), do: true
1635 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1636 def is_internal_user?(_), do: false
1638 # A hack because user delete activities have a fake id for whatever reason
1639 # TODO: Get rid of this
1640 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1642 def get_delivered_users_by_object_id(object_id) do
1644 inner_join: delivery in assoc(u, :deliveries),
1645 where: delivery.object_id == ^object_id
1650 def change_email(user, email) do
1652 |> cast(%{email: email}, [:email])
1653 |> validate_required([:email])
1654 |> unique_constraint(:email)
1655 |> validate_format(:email, @email_regex)
1656 |> update_and_set_cache()
1659 # Internal function; public one is `deactivate/2`
1660 defp set_activation_status(user, deactivated) do
1662 |> cast(%{deactivated: deactivated}, [:deactivated])
1663 |> update_and_set_cache()
1666 def update_banner(user, banner) do
1668 |> cast(%{banner: banner}, [:banner])
1669 |> update_and_set_cache()
1672 def update_background(user, background) do
1674 |> cast(%{background: background}, [:background])
1675 |> update_and_set_cache()
1678 def update_source_data(user, source_data) do
1680 |> cast(%{source_data: source_data}, [:source_data])
1681 |> update_and_set_cache()
1684 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1687 moderator: is_moderator
1691 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1692 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1693 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1694 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1697 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1698 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1702 def fields(%{fields: nil}), do: []
1704 def fields(%{fields: fields}), do: fields
1706 def validate_fields(changeset, remote? \\ false) do
1707 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1708 limit = Pleroma.Config.get([:instance, limit_name], 0)
1711 |> validate_length(:fields, max: limit)
1712 |> validate_change(:fields, fn :fields, fields ->
1713 if Enum.all?(fields, &valid_field?/1) do
1721 defp valid_field?(%{"name" => name, "value" => value}) do
1722 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1723 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1725 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1726 String.length(value) <= value_limit
1729 defp valid_field?(_), do: false
1731 defp truncate_field(%{"name" => name, "value" => value}) do
1733 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1736 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1738 %{"name" => name, "value" => value}
1741 def admin_api_update(user, params) do
1748 |> update_and_set_cache()
1751 def mascot_update(user, url) do
1753 |> cast(%{mascot: url}, [:mascot])
1754 |> validate_required([:mascot])
1755 |> update_and_set_cache()
1758 def mastodon_settings_update(user, settings) do
1760 |> cast(%{settings: settings}, [:settings])
1761 |> validate_required([:settings])
1762 |> update_and_set_cache()
1765 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1766 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1768 if need_confirmation? do
1770 confirmation_pending: true,
1771 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1775 confirmation_pending: false,
1776 confirmation_token: nil
1780 cast(user, params, [:confirmation_pending, :confirmation_token])
1783 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1784 if id not in user.pinned_activities do
1785 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1786 params = %{pinned_activities: user.pinned_activities ++ [id]}
1789 |> cast(params, [:pinned_activities])
1790 |> validate_length(:pinned_activities,
1791 max: max_pinned_statuses,
1792 message: "You have already pinned the maximum number of statuses"
1797 |> update_and_set_cache()
1800 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1801 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1804 |> cast(params, [:pinned_activities])
1805 |> update_and_set_cache()
1808 def update_email_notifications(user, settings) do
1809 email_notifications =
1810 user.email_notifications
1811 |> Map.merge(settings)
1812 |> Map.take(["digest"])
1814 params = %{email_notifications: email_notifications}
1815 fields = [:email_notifications]
1818 |> cast(params, fields)
1819 |> validate_required(fields)
1820 |> update_and_set_cache()
1823 defp set_subscribers(user, subscribers) do
1824 params = %{subscribers: subscribers}
1827 |> cast(params, [:subscribers])
1828 |> validate_required([:subscribers])
1829 |> update_and_set_cache()
1832 def add_to_subscribers(user, subscribed) do
1833 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1836 def remove_from_subscribers(user, subscribed) do
1837 set_subscribers(user, List.delete(user.subscribers, subscribed))
1840 defp set_domain_blocks(user, domain_blocks) do
1841 params = %{domain_blocks: domain_blocks}
1844 |> cast(params, [:domain_blocks])
1845 |> validate_required([:domain_blocks])
1846 |> update_and_set_cache()
1849 def block_domain(user, domain_blocked) do
1850 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1853 def unblock_domain(user, domain_blocked) do
1854 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1857 defp set_blocks(user, blocks) do
1858 params = %{blocks: blocks}
1861 |> cast(params, [:blocks])
1862 |> validate_required([:blocks])
1863 |> update_and_set_cache()
1866 def add_to_block(user, blocked) do
1867 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1870 def remove_from_block(user, blocked) do
1871 set_blocks(user, List.delete(user.blocks, blocked))
1874 defp set_mutes(user, mutes) do
1875 params = %{mutes: mutes}
1878 |> cast(params, [:mutes])
1879 |> validate_required([:mutes])
1880 |> update_and_set_cache()
1883 def add_to_mutes(user, muted, notifications?) do
1884 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1885 set_notification_mutes(
1887 Enum.uniq([muted | user.muted_notifications]),
1893 def remove_from_mutes(user, muted) do
1894 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1895 set_notification_mutes(
1897 List.delete(user.muted_notifications, muted),
1903 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1907 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1908 params = %{muted_notifications: muted_notifications}
1911 |> cast(params, [:muted_notifications])
1912 |> validate_required([:muted_notifications])
1913 |> update_and_set_cache()
1916 def add_reblog_mute(user, ap_id) do
1917 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1920 |> cast(params, [:muted_reblogs])
1921 |> update_and_set_cache()
1924 def remove_reblog_mute(user, ap_id) do
1925 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1928 |> cast(params, [:muted_reblogs])
1929 |> update_and_set_cache()
1932 def set_invisible(user, invisible) do
1933 params = %{invisible: invisible}
1936 |> cast(params, [:invisible])
1937 |> validate_required([:invisible])
1938 |> update_and_set_cache()