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 # Should be filled in only for remote users
71 field(:following_count, :integer, default: nil)
72 field(:locked, :boolean, default: false)
73 field(:confirmation_pending, :boolean, default: false)
74 field(:password_reset_pending, :boolean, default: false)
75 field(:confirmation_token, :string, default: nil)
76 field(:default_scope, :string, default: "public")
77 field(:blocks, {:array, :string}, default: [])
78 field(:domain_blocks, {:array, :string}, default: [])
79 field(:mutes, {:array, :string}, default: [])
80 field(:muted_reblogs, {:array, :string}, default: [])
81 field(:muted_notifications, {:array, :string}, default: [])
82 field(:subscribers, {:array, :string}, default: [])
83 field(:deactivated, :boolean, default: false)
84 field(:no_rich_text, :boolean, default: false)
85 field(:ap_enabled, :boolean, default: false)
86 field(:is_moderator, :boolean, default: false)
87 field(:is_admin, :boolean, default: false)
88 field(:show_role, :boolean, default: true)
89 field(:settings, :map, default: nil)
90 field(:magic_key, :string, default: nil)
91 field(:uri, :string, default: nil)
92 field(:hide_followers_count, :boolean, default: false)
93 field(:hide_follows_count, :boolean, default: false)
94 field(:hide_followers, :boolean, default: false)
95 field(:hide_follows, :boolean, default: false)
96 field(:hide_favorites, :boolean, default: true)
97 field(:unread_conversation_count, :integer, default: 0)
98 field(:pinned_activities, {:array, :string}, default: [])
99 field(:email_notifications, :map, default: %{"digest" => false})
100 field(:mascot, :map, default: nil)
101 field(:emoji, {:array, :map}, default: [])
102 field(:pleroma_settings_store, :map, default: %{})
103 field(:fields, {:array, :map}, default: [])
104 field(:raw_fields, {:array, :map}, default: [])
105 field(:discoverable, :boolean, default: false)
106 field(:invisible, :boolean, default: false)
107 field(:skip_thread_containment, :boolean, default: false)
109 field(:notification_settings, :map,
113 "non_follows" => true,
114 "non_followers" => true
118 has_many(:notifications, Notification)
119 has_many(:registrations, Registration)
120 has_many(:deliveries, Delivery)
122 field(:info, :map, default: %{})
127 def auth_active?(%User{confirmation_pending: true}),
128 do: !Pleroma.Config.get([:instance, :account_activation_required])
130 def auth_active?(%User{}), do: true
132 def visible_for?(user, for_user \\ nil)
134 def visible_for?(%User{invisible: true}, _), do: false
136 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
138 def visible_for?(%User{} = user, for_user) do
139 auth_active?(user) || superuser?(for_user)
142 def visible_for?(_, _), do: false
144 def superuser?(%User{local: true, is_admin: true}), do: true
145 def superuser?(%User{local: true, is_moderator: true}), do: true
146 def superuser?(_), do: false
148 def invisible?(%User{invisible: true}), do: true
149 def invisible?(_), do: false
151 def avatar_url(user, options \\ []) do
153 %{"url" => [%{"href" => href} | _]} -> href
154 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
158 def banner_url(user, options \\ []) do
160 %{"url" => [%{"href" => href} | _]} -> href
161 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
165 def profile_url(%User{source_data: %{"url" => url}}), do: url
166 def profile_url(%User{ap_id: ap_id}), do: ap_id
167 def profile_url(_), do: nil
169 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
171 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
172 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
174 @spec ap_following(User.t()) :: Sring.t()
175 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
176 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
178 def user_info(%User{} = user, args \\ %{}) do
180 Map.get(args, :following_count, user.following_count || following_count(user))
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
190 |> Map.put(:following_count, following_count)
191 |> Map.put(:follower_count, follower_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(followeds, fn followed ->
526 blocks?(follower, followed) || blocks?(followed, follower)
529 Enum.each(followeds, &follow(follower, &1, "accept"))
531 Enum.each(followeds, &update_follower_count/1)
536 defdelegate following(user), to: FollowingRelationship
538 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
539 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
542 followed.deactivated ->
543 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
545 deny_follow_blocked and blocks?(followed, follower) ->
546 {:error, "Could not follow user: #{followed.nickname} blocked you."}
549 FollowingRelationship.follow(follower, followed, state)
551 follower = maybe_update_following_count(follower)
553 {:ok, _} = update_follower_count(followed)
559 def unfollow(%User{} = follower, %User{} = followed) do
560 if following?(follower, followed) and follower.ap_id != followed.ap_id do
561 FollowingRelationship.unfollow(follower, followed)
563 follower = maybe_update_following_count(follower)
565 {:ok, followed} = update_follower_count(followed)
569 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
571 {:error, "Not subscribed!"}
575 defdelegate following?(follower, followed), to: FollowingRelationship
577 def locked?(%User{} = user) do
582 Repo.get_by(User, id: id)
585 def get_by_ap_id(ap_id) do
586 Repo.get_by(User, ap_id: ap_id)
589 def get_all_by_ap_id(ap_ids) do
590 from(u in __MODULE__,
591 where: u.ap_id in ^ap_ids
596 def get_all_by_ids(ids) do
597 from(u in __MODULE__, where: u.id in ^ids)
601 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
602 # of the ap_id and the domain and tries to get that user
603 def get_by_guessed_nickname(ap_id) do
604 domain = URI.parse(ap_id).host
605 name = List.last(String.split(ap_id, "/"))
606 nickname = "#{name}@#{domain}"
608 get_cached_by_nickname(nickname)
611 def set_cache({:ok, user}), do: set_cache(user)
612 def set_cache({:error, err}), do: {:error, err}
614 def set_cache(%User{} = user) do
615 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
616 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
617 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
621 def update_and_set_cache(struct, params) do
623 |> update_changeset(params)
624 |> update_and_set_cache()
627 def update_and_set_cache(changeset) do
628 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
633 def invalidate_cache(user) do
634 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
635 Cachex.del(:user_cache, "nickname:#{user.nickname}")
636 Cachex.del(:user_cache, "user_info:#{user.id}")
639 def get_cached_by_ap_id(ap_id) do
640 key = "ap_id:#{ap_id}"
641 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
644 def get_cached_by_id(id) do
648 Cachex.fetch!(:user_cache, key, fn _ ->
652 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
653 {:commit, user.ap_id}
659 get_cached_by_ap_id(ap_id)
662 def get_cached_by_nickname(nickname) do
663 key = "nickname:#{nickname}"
665 Cachex.fetch!(:user_cache, key, fn ->
666 case get_or_fetch_by_nickname(nickname) do
667 {:ok, user} -> {:commit, user}
668 {:error, _error} -> {:ignore, nil}
673 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
674 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
677 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
678 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
680 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
681 get_cached_by_nickname(nickname_or_id)
683 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
684 get_cached_by_nickname(nickname_or_id)
691 def get_by_nickname(nickname) do
692 Repo.get_by(User, nickname: nickname) ||
693 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
694 Repo.get_by(User, nickname: local_nickname(nickname))
698 def get_by_email(email), do: Repo.get_by(User, email: email)
700 def get_by_nickname_or_email(nickname_or_email) do
701 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
704 def get_cached_user_info(user) do
705 key = "user_info:#{user.id}"
706 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
709 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
711 def get_or_fetch_by_nickname(nickname) do
712 with %User{} = user <- get_by_nickname(nickname) do
716 with [_nick, _domain] <- String.split(nickname, "@"),
717 {:ok, user} <- fetch_by_nickname(nickname) do
718 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
719 fetch_initial_posts(user)
724 _e -> {:error, "not found " <> nickname}
729 @doc "Fetch some posts when the user has just been federated with"
730 def fetch_initial_posts(user) do
731 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
734 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
735 def get_followers_query(%User{} = user, nil) do
736 User.Query.build(%{followers: user, deactivated: false})
739 def get_followers_query(user, page) do
741 |> get_followers_query(nil)
742 |> User.Query.paginate(page, 20)
745 @spec get_followers_query(User.t()) :: Ecto.Query.t()
746 def get_followers_query(user), do: get_followers_query(user, nil)
748 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
749 def get_followers(user, page \\ nil) do
751 |> get_followers_query(page)
755 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
756 def get_external_followers(user, page \\ nil) do
758 |> get_followers_query(page)
759 |> User.Query.build(%{external: true})
763 def get_followers_ids(user, page \\ nil) do
765 |> get_followers_query(page)
770 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
771 def get_friends_query(%User{} = user, nil) do
772 User.Query.build(%{friends: user, deactivated: false})
775 def get_friends_query(user, page) do
777 |> get_friends_query(nil)
778 |> User.Query.paginate(page, 20)
781 @spec get_friends_query(User.t()) :: Ecto.Query.t()
782 def get_friends_query(user), do: get_friends_query(user, nil)
784 def get_friends(user, page \\ nil) do
786 |> get_friends_query(page)
790 def get_friends_ids(user, page \\ nil) do
792 |> get_friends_query(page)
797 defdelegate get_follow_requests(user), to: FollowingRelationship
799 def increase_note_count(%User{} = user) do
801 |> where(id: ^user.id)
802 |> update([u], inc: [note_count: 1])
804 |> Repo.update_all([])
806 {1, [user]} -> set_cache(user)
811 def decrease_note_count(%User{} = user) do
813 |> where(id: ^user.id)
816 note_count: fragment("greatest(0, note_count - 1)")
820 |> Repo.update_all([])
822 {1, [user]} -> set_cache(user)
827 def update_note_count(%User{} = user, note_count \\ nil) do
832 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
838 |> cast(%{note_count: note_count}, [:note_count])
839 |> update_and_set_cache()
842 @spec maybe_fetch_follow_information(User.t()) :: User.t()
843 def maybe_fetch_follow_information(user) do
844 with {:ok, user} <- fetch_follow_information(user) do
848 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
854 def fetch_follow_information(user) do
855 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
857 |> follow_information_changeset(info)
858 |> update_and_set_cache()
862 defp follow_information_changeset(user, params) do
869 :hide_followers_count,
874 def update_follower_count(%User{} = user) do
875 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
876 follower_count_query =
877 User.Query.build(%{followers: user, deactivated: false})
878 |> select([u], %{count: count(u.id)})
881 |> where(id: ^user.id)
882 |> join(:inner, [u], s in subquery(follower_count_query))
884 set: [follower_count: s.count]
887 |> Repo.update_all([])
889 {1, [user]} -> set_cache(user)
893 {:ok, maybe_fetch_follow_information(user)}
897 @spec maybe_update_following_count(User.t()) :: User.t()
898 def maybe_update_following_count(%User{local: false} = user) do
899 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
900 maybe_fetch_follow_information(user)
906 def maybe_update_following_count(user), do: user
908 def set_unread_conversation_count(%User{local: true} = user) do
909 unread_query = Participation.unread_conversation_count_for_user(user)
912 |> join(:inner, [u], p in subquery(unread_query))
914 set: [unread_conversation_count: p.count]
916 |> where([u], u.id == ^user.id)
918 |> Repo.update_all([])
920 {1, [user]} -> set_cache(user)
925 def set_unread_conversation_count(user), do: {:ok, user}
927 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
929 Participation.unread_conversation_count_for_user(user)
930 |> where([p], p.conversation_id == ^conversation.id)
933 |> join(:inner, [u], p in subquery(unread_query))
935 inc: [unread_conversation_count: 1]
937 |> where([u], u.id == ^user.id)
938 |> where([u, p], p.count == 0)
940 |> Repo.update_all([])
942 {1, [user]} -> set_cache(user)
947 def increment_unread_conversation_count(_, user), do: {:ok, user}
949 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
950 def get_users_from_set(ap_ids, local_only \\ true) do
951 criteria = %{ap_id: ap_ids, deactivated: false}
952 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
954 User.Query.build(criteria)
958 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
959 def get_recipients_from_activity(%Activity{recipients: to}) do
960 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
964 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
965 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
966 add_to_mutes(muter, ap_id, notifications?)
969 def unmute(muter, %{ap_id: ap_id}) do
970 remove_from_mutes(muter, ap_id)
973 def subscribe(subscriber, %{ap_id: ap_id}) do
974 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
975 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
977 if blocks?(subscribed, subscriber) and deny_follow_blocked do
978 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
980 User.add_to_subscribers(subscribed, subscriber.ap_id)
985 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
986 with %User{} = user <- get_cached_by_ap_id(ap_id) do
987 User.remove_from_subscribers(user, unsubscriber.ap_id)
991 def block(blocker, %User{ap_id: ap_id} = blocked) do
992 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
994 if following?(blocker, blocked) do
995 {:ok, blocker, _} = unfollow(blocker, blocked)
1001 # clear any requested follows as well
1003 case CommonAPI.reject_follow_request(blocked, blocker) do
1004 {:ok, %User{} = updated_blocked} -> updated_blocked
1009 if subscribed_to?(blocked, blocker) do
1010 {:ok, blocker} = unsubscribe(blocked, blocker)
1016 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1018 {:ok, blocker} = update_follower_count(blocker)
1019 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1020 add_to_block(blocker, ap_id)
1023 # helper to handle the block given only an actor's AP id
1024 def block(blocker, %{ap_id: ap_id}) do
1025 block(blocker, get_cached_by_ap_id(ap_id))
1028 def unblock(blocker, %{ap_id: ap_id}) do
1029 remove_from_block(blocker, ap_id)
1032 def mutes?(nil, _), do: false
1033 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1035 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1036 def muted_notifications?(nil, _), do: false
1038 def muted_notifications?(user, %{ap_id: ap_id}),
1039 do: Enum.member?(user.muted_notifications, ap_id)
1041 def blocks?(%User{} = user, %User{} = target) do
1042 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1045 def blocks?(nil, _), do: false
1047 def blocks_ap_id?(%User{} = user, %User{} = target) do
1048 Enum.member?(user.blocks, target.ap_id)
1051 def blocks_ap_id?(_, _), do: false
1053 def blocks_domain?(%User{} = user, %User{} = target) do
1054 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1055 %{host: host} = URI.parse(target.ap_id)
1056 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1059 def blocks_domain?(_, _), do: false
1061 def subscribed_to?(user, %{ap_id: ap_id}) do
1062 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1063 Enum.member?(target.subscribers, user.ap_id)
1067 @spec muted_users(User.t()) :: [User.t()]
1068 def muted_users(user) do
1069 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1073 @spec blocked_users(User.t()) :: [User.t()]
1074 def blocked_users(user) do
1075 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1079 @spec subscribers(User.t()) :: [User.t()]
1080 def subscribers(user) do
1081 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1085 def deactivate_async(user, status \\ true) do
1086 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1089 def deactivate(user, status \\ true)
1091 def deactivate(users, status) when is_list(users) do
1092 Repo.transaction(fn ->
1093 for user <- users, do: deactivate(user, status)
1097 def deactivate(%User{} = user, status) do
1098 with {:ok, user} <- set_activation_status(user, status) do
1099 Enum.each(get_followers(user), &invalidate_cache/1)
1101 # Only update local user counts, remote will be update during the next pull.
1104 |> Enum.filter(& &1.local)
1105 |> Enum.each(&update_follower_count/1)
1111 def update_notification_settings(%User{} = user, settings) do
1114 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1117 notification_settings =
1118 user.notification_settings
1119 |> Map.merge(settings)
1120 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1122 params = %{notification_settings: notification_settings}
1125 |> cast(params, [:notification_settings])
1126 |> validate_required([:notification_settings])
1127 |> update_and_set_cache()
1130 def delete(users) when is_list(users) do
1131 for user <- users, do: delete(user)
1134 def delete(%User{} = user) do
1135 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1138 def perform(:force_password_reset, user), do: force_password_reset(user)
1140 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1141 def perform(:delete, %User{} = user) do
1142 {:ok, _user} = ActivityPub.delete(user)
1144 # Remove all relationships
1147 |> Enum.each(fn follower ->
1148 ActivityPub.unfollow(follower, user)
1149 unfollow(follower, user)
1154 |> Enum.each(fn followed ->
1155 ActivityPub.unfollow(user, followed)
1156 unfollow(user, followed)
1159 delete_user_activities(user)
1160 invalidate_cache(user)
1164 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1165 def perform(:fetch_initial_posts, %User{} = user) do
1166 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1168 # Insert all the posts in reverse order, so they're in the right order on the timeline
1169 user.source_data["outbox"]
1170 |> Utils.fetch_ordered_collection(pages)
1172 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1175 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1177 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1178 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1179 when is_list(blocked_identifiers) do
1181 blocked_identifiers,
1182 fn blocked_identifier ->
1183 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1184 {:ok, blocker} <- block(blocker, blocked),
1185 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1189 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1196 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1197 def perform(:follow_import, %User{} = follower, followed_identifiers)
1198 when is_list(followed_identifiers) do
1200 followed_identifiers,
1201 fn followed_identifier ->
1202 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1203 {:ok, follower} <- maybe_direct_follow(follower, followed),
1204 {:ok, _} <- ActivityPub.follow(follower, followed) do
1208 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1215 @spec external_users_query() :: Ecto.Query.t()
1216 def external_users_query do
1224 @spec external_users(keyword()) :: [User.t()]
1225 def external_users(opts \\ []) do
1227 external_users_query()
1228 |> select([u], struct(u, [:id, :ap_id, :info]))
1232 do: where(query, [u], u.id > ^opts[:max_id]),
1237 do: limit(query, ^opts[:limit]),
1243 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1244 BackgroundWorker.enqueue("blocks_import", %{
1245 "blocker_id" => blocker.id,
1246 "blocked_identifiers" => blocked_identifiers
1250 def follow_import(%User{} = follower, followed_identifiers)
1251 when is_list(followed_identifiers) do
1252 BackgroundWorker.enqueue("follow_import", %{
1253 "follower_id" => follower.id,
1254 "followed_identifiers" => followed_identifiers
1258 def delete_user_activities(%User{ap_id: ap_id}) do
1260 |> Activity.Queries.by_actor()
1261 |> RepoStreamer.chunk_stream(50)
1262 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1266 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1268 |> Object.normalize()
1269 |> ActivityPub.delete()
1272 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1273 object = Object.normalize(activity)
1276 |> get_cached_by_ap_id()
1277 |> ActivityPub.unlike(object)
1280 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1281 object = Object.normalize(activity)
1284 |> get_cached_by_ap_id()
1285 |> ActivityPub.unannounce(object)
1288 defp delete_activity(_activity), do: "Doing nothing"
1290 def html_filter_policy(%User{no_rich_text: true}) do
1291 Pleroma.HTML.Scrubber.TwitterText
1294 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1296 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1298 def get_or_fetch_by_ap_id(ap_id) do
1299 user = get_cached_by_ap_id(ap_id)
1301 if !is_nil(user) and !needs_update?(user) do
1304 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1305 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1307 resp = fetch_by_ap_id(ap_id)
1309 if should_fetch_initial do
1310 with {:ok, %User{} = user} <- resp do
1311 fetch_initial_posts(user)
1320 Creates an internal service actor by URI if missing.
1321 Optionally takes nickname for addressing.
1323 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1324 with user when is_nil(user) <- get_cached_by_ap_id(uri) do
1331 follower_address: uri <> "/followers"
1340 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1343 |> :public_key.pem_decode()
1345 |> :public_key.pem_entry_decode()
1350 def public_key(_), do: {:error, "not found key"}
1352 def get_public_key_for_ap_id(ap_id) do
1353 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1354 {:ok, public_key} <- public_key(user) do
1361 defp blank?(""), do: nil
1362 defp blank?(n), do: n
1364 def insert_or_update_user(data) do
1366 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1367 |> remote_user_creation()
1368 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1372 def ap_enabled?(%User{local: true}), do: true
1373 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1374 def ap_enabled?(_), do: false
1376 @doc "Gets or fetch a user by uri or nickname."
1377 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1378 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1379 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1381 # wait a period of time and return newest version of the User structs
1382 # this is because we have synchronous follow APIs and need to simulate them
1383 # with an async handshake
1384 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1385 with %User{} = a <- get_cached_by_id(a.id),
1386 %User{} = b <- get_cached_by_id(b.id) do
1393 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1394 with :ok <- :timer.sleep(timeout),
1395 %User{} = a <- get_cached_by_id(a.id),
1396 %User{} = b <- get_cached_by_id(b.id) do
1403 def parse_bio(bio) when is_binary(bio) and bio != "" do
1405 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1409 def parse_bio(_), do: ""
1411 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1412 # TODO: get profile URLs other than user.ap_id
1413 profile_urls = [user.ap_id]
1416 |> CommonUtils.format_input("text/plain",
1417 mentions_format: :full,
1418 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1423 def parse_bio(_, _), do: ""
1425 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1426 Repo.transaction(fn ->
1427 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1431 def tag(nickname, tags) when is_binary(nickname),
1432 do: tag(get_by_nickname(nickname), tags)
1434 def tag(%User{} = user, tags),
1435 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1437 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1438 Repo.transaction(fn ->
1439 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1443 def untag(nickname, tags) when is_binary(nickname),
1444 do: untag(get_by_nickname(nickname), tags)
1446 def untag(%User{} = user, tags),
1447 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1449 defp update_tags(%User{} = user, new_tags) do
1450 {:ok, updated_user} =
1452 |> change(%{tags: new_tags})
1453 |> update_and_set_cache()
1458 defp normalize_tags(tags) do
1461 |> Enum.map(&String.downcase/1)
1464 defp local_nickname_regex do
1465 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1466 @extended_local_nickname_regex
1468 @strict_local_nickname_regex
1472 def local_nickname(nickname_or_mention) do
1475 |> String.split("@")
1479 def full_nickname(nickname_or_mention),
1480 do: String.trim_leading(nickname_or_mention, "@")
1482 def error_user(ap_id) do
1486 nickname: "erroruser@example.com",
1487 inserted_at: NaiveDateTime.utc_now()
1491 @spec all_superusers() :: [User.t()]
1492 def all_superusers do
1493 User.Query.build(%{super_users: true, local: true, deactivated: false})
1497 def showing_reblogs?(%User{} = user, %User{} = target) do
1498 target.ap_id not in user.muted_reblogs
1502 The function returns a query to get users with no activity for given interval of days.
1503 Inactive users are those who didn't read any notification, or had any activity where
1504 the user is the activity's actor, during `inactivity_threshold` days.
1505 Deactivated users will not appear in this list.
1509 iex> Pleroma.User.list_inactive_users()
1512 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1513 def list_inactive_users_query(inactivity_threshold \\ 7) do
1514 negative_inactivity_threshold = -inactivity_threshold
1515 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1516 # Subqueries are not supported in `where` clauses, join gets too complicated.
1517 has_read_notifications =
1518 from(n in Pleroma.Notification,
1519 where: n.seen == true,
1521 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1524 |> Pleroma.Repo.all()
1526 from(u in Pleroma.User,
1527 left_join: a in Pleroma.Activity,
1528 on: u.ap_id == a.actor,
1529 where: not is_nil(u.nickname),
1530 where: u.deactivated != ^true,
1531 where: u.id not in ^has_read_notifications,
1534 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1535 is_nil(max(a.inserted_at))
1540 Enable or disable email notifications for user
1544 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1545 Pleroma.User{email_notifications: %{"digest" => true}}
1547 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1548 Pleroma.User{email_notifications: %{"digest" => false}}
1550 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1551 {:ok, t()} | {:error, Ecto.Changeset.t()}
1552 def switch_email_notifications(user, type, status) do
1553 User.update_email_notifications(user, %{type => status})
1557 Set `last_digest_emailed_at` value for the user to current time
1559 @spec touch_last_digest_emailed_at(t()) :: t()
1560 def touch_last_digest_emailed_at(user) do
1561 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1563 {:ok, updated_user} =
1565 |> change(%{last_digest_emailed_at: now})
1566 |> update_and_set_cache()
1571 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1572 def toggle_confirmation(%User{} = user) do
1574 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1575 |> update_and_set_cache()
1578 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1582 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1583 # use instance-default
1584 config = Pleroma.Config.get([:assets, :mascots])
1585 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1586 mascot = Keyword.get(config, default_mascot)
1589 "id" => "default-mascot",
1590 "url" => mascot[:url],
1591 "preview_url" => mascot[:url],
1593 "mime_type" => mascot[:mime_type]
1598 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1600 def ensure_keys_present(%User{} = user) do
1601 with {:ok, pem} <- Keys.generate_rsa_pem() do
1603 |> cast(%{keys: pem}, [:keys])
1604 |> validate_required([:keys])
1605 |> update_and_set_cache()
1609 def get_ap_ids_by_nicknames(nicknames) do
1611 where: u.nickname in ^nicknames,
1617 defdelegate search(query, opts \\ []), to: User.Search
1619 defp put_password_hash(
1620 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1622 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1625 defp put_password_hash(changeset), do: changeset
1627 def is_internal_user?(%User{nickname: nil}), do: true
1628 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1629 def is_internal_user?(_), do: false
1631 # A hack because user delete activities have a fake id for whatever reason
1632 # TODO: Get rid of this
1633 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1635 def get_delivered_users_by_object_id(object_id) do
1637 inner_join: delivery in assoc(u, :deliveries),
1638 where: delivery.object_id == ^object_id
1643 def change_email(user, email) do
1645 |> cast(%{email: email}, [:email])
1646 |> validate_required([:email])
1647 |> unique_constraint(:email)
1648 |> validate_format(:email, @email_regex)
1649 |> update_and_set_cache()
1652 # Internal function; public one is `deactivate/2`
1653 defp set_activation_status(user, deactivated) do
1655 |> cast(%{deactivated: deactivated}, [:deactivated])
1656 |> update_and_set_cache()
1659 def update_banner(user, banner) do
1661 |> cast(%{banner: banner}, [:banner])
1662 |> update_and_set_cache()
1665 def update_background(user, background) do
1667 |> cast(%{background: background}, [:background])
1668 |> update_and_set_cache()
1671 def update_source_data(user, source_data) do
1673 |> cast(%{source_data: source_data}, [:source_data])
1674 |> update_and_set_cache()
1677 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1680 moderator: is_moderator
1684 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1685 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1686 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1687 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1690 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1691 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1695 def fields(%{fields: nil}), do: []
1697 def fields(%{fields: fields}), do: fields
1699 def validate_fields(changeset, remote? \\ false) do
1700 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1701 limit = Pleroma.Config.get([:instance, limit_name], 0)
1704 |> validate_length(:fields, max: limit)
1705 |> validate_change(:fields, fn :fields, fields ->
1706 if Enum.all?(fields, &valid_field?/1) do
1714 defp valid_field?(%{"name" => name, "value" => value}) do
1715 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1716 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1718 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1719 String.length(value) <= value_limit
1722 defp valid_field?(_), do: false
1724 defp truncate_field(%{"name" => name, "value" => value}) do
1726 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1729 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1731 %{"name" => name, "value" => value}
1734 def admin_api_update(user, params) do
1741 |> update_and_set_cache()
1744 def mascot_update(user, url) do
1746 |> cast(%{mascot: url}, [:mascot])
1747 |> validate_required([:mascot])
1748 |> update_and_set_cache()
1751 def mastodon_settings_update(user, settings) do
1753 |> cast(%{settings: settings}, [:settings])
1754 |> validate_required([:settings])
1755 |> update_and_set_cache()
1758 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1759 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1761 if need_confirmation? do
1763 confirmation_pending: true,
1764 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1768 confirmation_pending: false,
1769 confirmation_token: nil
1773 cast(user, params, [:confirmation_pending, :confirmation_token])
1776 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1777 if id not in user.pinned_activities do
1778 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1779 params = %{pinned_activities: user.pinned_activities ++ [id]}
1782 |> cast(params, [:pinned_activities])
1783 |> validate_length(:pinned_activities,
1784 max: max_pinned_statuses,
1785 message: "You have already pinned the maximum number of statuses"
1790 |> update_and_set_cache()
1793 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1794 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1797 |> cast(params, [:pinned_activities])
1798 |> update_and_set_cache()
1801 def update_email_notifications(user, settings) do
1802 email_notifications =
1803 user.email_notifications
1804 |> Map.merge(settings)
1805 |> Map.take(["digest"])
1807 params = %{email_notifications: email_notifications}
1808 fields = [:email_notifications]
1811 |> cast(params, fields)
1812 |> validate_required(fields)
1813 |> update_and_set_cache()
1816 defp set_subscribers(user, subscribers) do
1817 params = %{subscribers: subscribers}
1820 |> cast(params, [:subscribers])
1821 |> validate_required([:subscribers])
1822 |> update_and_set_cache()
1825 def add_to_subscribers(user, subscribed) do
1826 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1829 def remove_from_subscribers(user, subscribed) do
1830 set_subscribers(user, List.delete(user.subscribers, subscribed))
1833 defp set_domain_blocks(user, domain_blocks) do
1834 params = %{domain_blocks: domain_blocks}
1837 |> cast(params, [:domain_blocks])
1838 |> validate_required([:domain_blocks])
1839 |> update_and_set_cache()
1842 def block_domain(user, domain_blocked) do
1843 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1846 def unblock_domain(user, domain_blocked) do
1847 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1850 defp set_blocks(user, blocks) do
1851 params = %{blocks: blocks}
1854 |> cast(params, [:blocks])
1855 |> validate_required([:blocks])
1856 |> update_and_set_cache()
1859 def add_to_block(user, blocked) do
1860 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1863 def remove_from_block(user, blocked) do
1864 set_blocks(user, List.delete(user.blocks, blocked))
1867 defp set_mutes(user, mutes) do
1868 params = %{mutes: mutes}
1871 |> cast(params, [:mutes])
1872 |> validate_required([:mutes])
1873 |> update_and_set_cache()
1876 def add_to_mutes(user, muted, notifications?) do
1877 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1878 set_notification_mutes(
1880 Enum.uniq([muted | user.muted_notifications]),
1886 def remove_from_mutes(user, muted) do
1887 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1888 set_notification_mutes(
1890 List.delete(user.muted_notifications, muted),
1896 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1900 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1901 params = %{muted_notifications: muted_notifications}
1904 |> cast(params, [:muted_notifications])
1905 |> validate_required([:muted_notifications])
1906 |> update_and_set_cache()
1909 def add_reblog_mute(user, ap_id) do
1910 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1913 |> cast(params, [:muted_reblogs])
1914 |> update_and_set_cache()
1917 def remove_reblog_mute(user, ap_id) do
1918 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1921 |> cast(params, [:muted_reblogs])
1922 |> update_and_set_cache()
1925 def set_invisible(user, invisible) do
1926 params = %{invisible: invisible}
1929 |> cast(params, [:invisible])
1930 |> validate_required([:invisible])
1931 |> update_and_set_cache()