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{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
136 def visible_for?(%User{} = user, for_user) do
137 auth_active?(user) || superuser?(for_user)
140 def visible_for?(_, _), do: false
142 def superuser?(%User{local: true, is_admin: true}), do: true
143 def superuser?(%User{local: true, is_moderator: true}), do: true
144 def superuser?(_), do: false
146 def invisible?(%User{invisible: true}), do: true
147 def invisible?(_), do: false
149 def avatar_url(user, options \\ []) do
151 %{"url" => [%{"href" => href} | _]} -> href
152 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
156 def banner_url(user, options \\ []) do
158 %{"url" => [%{"href" => href} | _]} -> href
159 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
163 def profile_url(%User{source_data: %{"url" => url}}), do: url
164 def profile_url(%User{ap_id: ap_id}), do: ap_id
165 def profile_url(_), do: nil
167 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
169 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
170 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
172 @spec ap_following(User.t()) :: Sring.t()
173 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
174 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
176 def user_info(%User{} = user, args \\ %{}) do
178 Map.get(args, :following_count, user.following_count || following_count(user))
180 follower_count = Map.get(args, :follower_count, user.follower_count)
183 note_count: user.note_count,
185 confirmation_pending: user.confirmation_pending,
186 default_scope: user.default_scope
188 |> Map.put(:following_count, following_count)
189 |> Map.put(:follower_count, follower_count)
192 def follow_state(%User{} = user, %User{} = target) do
193 case Utils.fetch_latest_follow(user, target) do
194 %{data: %{"state" => state}} -> state
195 # Ideally this would be nil, but then Cachex does not commit the value
200 def get_cached_follow_state(user, target) do
201 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
202 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
205 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
206 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
207 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
210 def set_info_cache(user, args) do
211 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
214 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
215 def restrict_deactivated(query) do
216 from(u in query, where: u.deactivated != ^true)
219 defdelegate following_count(user), to: FollowingRelationship
221 defp truncate_fields_param(params) do
222 if Map.has_key?(params, :fields) do
223 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
229 defp truncate_if_exists(params, key, max_length) do
230 if Map.has_key?(params, key) and is_binary(params[key]) do
231 {value, _chopped} = String.split_at(params[key], max_length)
232 Map.put(params, key, value)
238 def remote_user_creation(params) do
239 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
240 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
244 |> Map.put(:info, params[:info] || %{})
245 |> truncate_if_exists(:name, name_limit)
246 |> truncate_if_exists(:bio, bio_limit)
247 |> truncate_fields_param()
267 :hide_followers_count,
276 |> validate_required([:name, :ap_id])
277 |> unique_constraint(:nickname)
278 |> validate_format(:nickname, @email_regex)
279 |> validate_length(:bio, max: bio_limit)
280 |> validate_length(:name, max: name_limit)
281 |> validate_fields(true)
283 case params[:source_data] do
284 %{"followers" => followers, "following" => following} ->
286 |> put_change(:follower_address, followers)
287 |> put_change(:following_address, following)
290 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
291 put_change(changeset, :follower_address, followers)
295 def update_changeset(struct, params \\ %{}) do
296 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
297 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
312 :hide_followers_count,
317 :skip_thread_containment,
320 :pleroma_settings_store,
324 |> unique_constraint(:nickname)
325 |> validate_format(:nickname, local_nickname_regex())
326 |> validate_length(:bio, max: bio_limit)
327 |> validate_length(:name, min: 1, max: name_limit)
328 |> validate_fields(false)
331 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
332 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
333 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
335 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
337 params = if remote?, do: truncate_fields_param(params), else: params
360 :hide_followers_count,
364 |> unique_constraint(:nickname)
365 |> validate_format(:nickname, local_nickname_regex())
366 |> validate_length(:bio, max: bio_limit)
367 |> validate_length(:name, max: name_limit)
368 |> validate_fields(remote?)
371 def password_update_changeset(struct, params) do
373 |> cast(params, [:password, :password_confirmation])
374 |> validate_required([:password, :password_confirmation])
375 |> validate_confirmation(:password)
376 |> put_password_hash()
377 |> put_change(:password_reset_pending, false)
380 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
381 def reset_password(%User{id: user_id} = user, data) do
384 |> Multi.update(:user, password_update_changeset(user, data))
385 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
386 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
388 case Repo.transaction(multi) do
389 {:ok, %{user: user} = _} -> set_cache(user)
390 {:error, _, changeset, _} -> {:error, changeset}
394 def update_password_reset_pending(user, value) do
397 |> put_change(:password_reset_pending, value)
398 |> update_and_set_cache()
401 def force_password_reset_async(user) do
402 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
405 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
406 def force_password_reset(user), do: update_password_reset_pending(user, true)
408 def register_changeset(struct, params \\ %{}, opts \\ []) do
409 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
410 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
413 if is_nil(opts[:need_confirmation]) do
414 Pleroma.Config.get([:instance, :account_activation_required])
416 opts[:need_confirmation]
420 |> confirmation_changeset(need_confirmation: need_confirmation?)
421 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
422 |> validate_required([:name, :nickname, :password, :password_confirmation])
423 |> validate_confirmation(:password)
424 |> unique_constraint(:email)
425 |> unique_constraint(:nickname)
426 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
427 |> validate_format(:nickname, local_nickname_regex())
428 |> validate_format(:email, @email_regex)
429 |> validate_length(:bio, max: bio_limit)
430 |> validate_length(:name, min: 1, max: name_limit)
431 |> maybe_validate_required_email(opts[:external])
434 |> unique_constraint(:ap_id)
435 |> put_following_and_follower_address()
438 def maybe_validate_required_email(changeset, true), do: changeset
439 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
441 defp put_ap_id(changeset) do
442 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
443 put_change(changeset, :ap_id, ap_id)
446 defp put_following_and_follower_address(changeset) do
447 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
450 |> put_change(:follower_address, followers)
453 defp autofollow_users(user) do
454 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
457 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
460 follow_all(user, autofollowed_users)
463 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
464 def register(%Ecto.Changeset{} = changeset) do
465 with {:ok, user} <- Repo.insert(changeset) do
466 post_register_action(user)
470 def post_register_action(%User{} = user) do
471 with {:ok, user} <- autofollow_users(user),
472 {:ok, user} <- set_cache(user),
473 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
474 {:ok, _} <- try_send_confirmation_email(user) do
479 def try_send_confirmation_email(%User{} = user) do
480 if user.confirmation_pending &&
481 Pleroma.Config.get([:instance, :account_activation_required]) do
483 |> Pleroma.Emails.UserEmail.account_confirmation_email()
484 |> Pleroma.Emails.Mailer.deliver_async()
492 def needs_update?(%User{local: true}), do: false
494 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
496 def needs_update?(%User{local: false} = user) do
497 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
500 def needs_update?(_), do: true
502 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
503 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
504 follow(follower, followed, "pending")
507 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
508 follow(follower, followed)
511 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
512 if not ap_enabled?(followed) do
513 follow(follower, followed)
519 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
520 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
521 def follow_all(follower, followeds) do
523 Enum.reject(followeds, fn followed ->
524 blocks?(follower, followed) || blocks?(followed, follower)
527 Enum.each(followeds, &follow(follower, &1, "accept"))
529 Enum.each(followeds, &update_follower_count/1)
534 defdelegate following(user), to: FollowingRelationship
536 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
537 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
540 followed.deactivated ->
541 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
543 deny_follow_blocked and blocks?(followed, follower) ->
544 {:error, "Could not follow user: #{followed.nickname} blocked you."}
547 FollowingRelationship.follow(follower, followed, state)
549 follower = maybe_update_following_count(follower)
551 {:ok, _} = update_follower_count(followed)
557 def unfollow(%User{} = follower, %User{} = followed) do
558 if following?(follower, followed) and follower.ap_id != followed.ap_id do
559 FollowingRelationship.unfollow(follower, followed)
561 follower = maybe_update_following_count(follower)
563 {:ok, followed} = update_follower_count(followed)
567 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
569 {:error, "Not subscribed!"}
573 defdelegate following?(follower, followed), to: FollowingRelationship
575 def locked?(%User{} = user) do
580 Repo.get_by(User, id: id)
583 def get_by_ap_id(ap_id) do
584 Repo.get_by(User, ap_id: ap_id)
587 def get_all_by_ap_id(ap_ids) do
588 from(u in __MODULE__,
589 where: u.ap_id in ^ap_ids
594 def get_all_by_ids(ids) do
595 from(u in __MODULE__, where: u.id in ^ids)
599 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
600 # of the ap_id and the domain and tries to get that user
601 def get_by_guessed_nickname(ap_id) do
602 domain = URI.parse(ap_id).host
603 name = List.last(String.split(ap_id, "/"))
604 nickname = "#{name}@#{domain}"
606 get_cached_by_nickname(nickname)
609 def set_cache({:ok, user}), do: set_cache(user)
610 def set_cache({:error, err}), do: {:error, err}
612 def set_cache(%User{} = user) do
613 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
614 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
615 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
619 def update_and_set_cache(struct, params) do
621 |> update_changeset(params)
622 |> update_and_set_cache()
625 def update_and_set_cache(changeset) do
626 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
631 def invalidate_cache(user) do
632 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
633 Cachex.del(:user_cache, "nickname:#{user.nickname}")
634 Cachex.del(:user_cache, "user_info:#{user.id}")
637 def get_cached_by_ap_id(ap_id) do
638 key = "ap_id:#{ap_id}"
639 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
642 def get_cached_by_id(id) do
646 Cachex.fetch!(:user_cache, key, fn _ ->
650 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
651 {:commit, user.ap_id}
657 get_cached_by_ap_id(ap_id)
660 def get_cached_by_nickname(nickname) do
661 key = "nickname:#{nickname}"
663 Cachex.fetch!(:user_cache, key, fn ->
664 case get_or_fetch_by_nickname(nickname) do
665 {:ok, user} -> {:commit, user}
666 {:error, _error} -> {:ignore, nil}
671 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
672 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
675 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
676 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
678 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
679 get_cached_by_nickname(nickname_or_id)
681 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
682 get_cached_by_nickname(nickname_or_id)
689 def get_by_nickname(nickname) do
690 Repo.get_by(User, nickname: nickname) ||
691 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
692 Repo.get_by(User, nickname: local_nickname(nickname))
696 def get_by_email(email), do: Repo.get_by(User, email: email)
698 def get_by_nickname_or_email(nickname_or_email) do
699 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
702 def get_cached_user_info(user) do
703 key = "user_info:#{user.id}"
704 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
707 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
709 def get_or_fetch_by_nickname(nickname) do
710 with %User{} = user <- get_by_nickname(nickname) do
714 with [_nick, _domain] <- String.split(nickname, "@"),
715 {:ok, user} <- fetch_by_nickname(nickname) do
716 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
717 fetch_initial_posts(user)
722 _e -> {:error, "not found " <> nickname}
727 @doc "Fetch some posts when the user has just been federated with"
728 def fetch_initial_posts(user) do
729 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
732 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
733 def get_followers_query(%User{} = user, nil) do
734 User.Query.build(%{followers: user, deactivated: false})
737 def get_followers_query(user, page) do
739 |> get_followers_query(nil)
740 |> User.Query.paginate(page, 20)
743 @spec get_followers_query(User.t()) :: Ecto.Query.t()
744 def get_followers_query(user), do: get_followers_query(user, nil)
746 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
747 def get_followers(user, page \\ nil) do
749 |> get_followers_query(page)
753 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
754 def get_external_followers(user, page \\ nil) do
756 |> get_followers_query(page)
757 |> User.Query.build(%{external: true})
761 def get_followers_ids(user, page \\ nil) do
763 |> get_followers_query(page)
768 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
769 def get_friends_query(%User{} = user, nil) do
770 User.Query.build(%{friends: user, deactivated: false})
773 def get_friends_query(user, page) do
775 |> get_friends_query(nil)
776 |> User.Query.paginate(page, 20)
779 @spec get_friends_query(User.t()) :: Ecto.Query.t()
780 def get_friends_query(user), do: get_friends_query(user, nil)
782 def get_friends(user, page \\ nil) do
784 |> get_friends_query(page)
788 def get_friends_ids(user, page \\ nil) do
790 |> get_friends_query(page)
795 defdelegate get_follow_requests(user), to: FollowingRelationship
797 def increase_note_count(%User{} = user) do
799 |> where(id: ^user.id)
800 |> update([u], inc: [note_count: 1])
802 |> Repo.update_all([])
804 {1, [user]} -> set_cache(user)
809 def decrease_note_count(%User{} = user) do
811 |> where(id: ^user.id)
814 note_count: fragment("greatest(0, note_count - 1)")
818 |> Repo.update_all([])
820 {1, [user]} -> set_cache(user)
825 def update_note_count(%User{} = user, note_count \\ nil) do
830 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
836 |> cast(%{note_count: note_count}, [:note_count])
837 |> update_and_set_cache()
840 @spec maybe_fetch_follow_information(User.t()) :: User.t()
841 def maybe_fetch_follow_information(user) do
842 with {:ok, user} <- fetch_follow_information(user) do
846 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
852 def fetch_follow_information(user) do
853 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
855 |> follow_information_changeset(info)
856 |> update_and_set_cache()
860 defp follow_information_changeset(user, params) do
867 :hide_followers_count,
872 def update_follower_count(%User{} = user) do
873 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
874 follower_count_query =
875 User.Query.build(%{followers: user, deactivated: false})
876 |> select([u], %{count: count(u.id)})
879 |> where(id: ^user.id)
880 |> join(:inner, [u], s in subquery(follower_count_query))
882 set: [follower_count: s.count]
885 |> Repo.update_all([])
887 {1, [user]} -> set_cache(user)
891 {:ok, maybe_fetch_follow_information(user)}
895 @spec maybe_update_following_count(User.t()) :: User.t()
896 def maybe_update_following_count(%User{local: false} = user) do
897 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
898 maybe_fetch_follow_information(user)
904 def maybe_update_following_count(user), do: user
906 def set_unread_conversation_count(%User{local: true} = user) do
907 unread_query = Participation.unread_conversation_count_for_user(user)
910 |> join(:inner, [u], p in subquery(unread_query))
912 set: [unread_conversation_count: p.count]
914 |> where([u], u.id == ^user.id)
916 |> Repo.update_all([])
918 {1, [user]} -> set_cache(user)
923 def set_unread_conversation_count(user), do: {:ok, user}
925 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
927 Participation.unread_conversation_count_for_user(user)
928 |> where([p], p.conversation_id == ^conversation.id)
931 |> join(:inner, [u], p in subquery(unread_query))
933 inc: [unread_conversation_count: 1]
935 |> where([u], u.id == ^user.id)
936 |> where([u, p], p.count == 0)
938 |> Repo.update_all([])
940 {1, [user]} -> set_cache(user)
945 def increment_unread_conversation_count(_, user), do: {:ok, user}
947 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
948 def get_users_from_set(ap_ids, local_only \\ true) do
949 criteria = %{ap_id: ap_ids, deactivated: false}
950 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
952 User.Query.build(criteria)
956 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
957 def get_recipients_from_activity(%Activity{recipients: to}) do
958 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
962 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
963 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
964 add_to_mutes(muter, ap_id, notifications?)
967 def unmute(muter, %{ap_id: ap_id}) do
968 remove_from_mutes(muter, ap_id)
971 def subscribe(subscriber, %{ap_id: ap_id}) do
972 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
973 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
975 if blocks?(subscribed, subscriber) and deny_follow_blocked do
976 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
978 User.add_to_subscribers(subscribed, subscriber.ap_id)
983 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
984 with %User{} = user <- get_cached_by_ap_id(ap_id) do
985 User.remove_from_subscribers(user, unsubscriber.ap_id)
989 def block(blocker, %User{ap_id: ap_id} = blocked) do
990 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
992 if following?(blocker, blocked) do
993 {:ok, blocker, _} = unfollow(blocker, blocked)
999 # clear any requested follows as well
1001 case CommonAPI.reject_follow_request(blocked, blocker) do
1002 {:ok, %User{} = updated_blocked} -> updated_blocked
1007 if subscribed_to?(blocked, blocker) do
1008 {:ok, blocker} = unsubscribe(blocked, blocker)
1014 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1016 {:ok, blocker} = update_follower_count(blocker)
1017 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1018 add_to_block(blocker, ap_id)
1021 # helper to handle the block given only an actor's AP id
1022 def block(blocker, %{ap_id: ap_id}) do
1023 block(blocker, get_cached_by_ap_id(ap_id))
1026 def unblock(blocker, %{ap_id: ap_id}) do
1027 remove_from_block(blocker, ap_id)
1030 def mutes?(nil, _), do: false
1031 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1033 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1034 def muted_notifications?(nil, _), do: false
1036 def muted_notifications?(user, %{ap_id: ap_id}),
1037 do: Enum.member?(user.muted_notifications, ap_id)
1039 def blocks?(%User{} = user, %User{} = target) do
1040 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1043 def blocks?(nil, _), do: false
1045 def blocks_ap_id?(%User{} = user, %User{} = target) do
1046 Enum.member?(user.blocks, target.ap_id)
1049 def blocks_ap_id?(_, _), do: false
1051 def blocks_domain?(%User{} = user, %User{} = target) do
1052 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1053 %{host: host} = URI.parse(target.ap_id)
1054 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1057 def blocks_domain?(_, _), do: false
1059 def subscribed_to?(user, %{ap_id: ap_id}) do
1060 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1061 Enum.member?(target.subscribers, user.ap_id)
1065 @spec muted_users(User.t()) :: [User.t()]
1066 def muted_users(user) do
1067 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1071 @spec blocked_users(User.t()) :: [User.t()]
1072 def blocked_users(user) do
1073 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1077 @spec subscribers(User.t()) :: [User.t()]
1078 def subscribers(user) do
1079 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1083 def deactivate_async(user, status \\ true) do
1084 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1087 def deactivate(user, status \\ true)
1089 def deactivate(users, status) when is_list(users) do
1090 Repo.transaction(fn ->
1091 for user <- users, do: deactivate(user, status)
1095 def deactivate(%User{} = user, status) do
1096 with {:ok, user} <- set_activation_status(user, status) do
1097 Enum.each(get_followers(user), &invalidate_cache/1)
1098 Enum.each(get_friends(user), &update_follower_count/1)
1104 def update_notification_settings(%User{} = user, settings) do
1107 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1110 notification_settings =
1111 user.notification_settings
1112 |> Map.merge(settings)
1113 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1115 params = %{notification_settings: notification_settings}
1118 |> cast(params, [:notification_settings])
1119 |> validate_required([:notification_settings])
1120 |> update_and_set_cache()
1123 def delete(users) when is_list(users) do
1124 for user <- users, do: delete(user)
1127 def delete(%User{} = user) do
1128 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1131 def perform(:force_password_reset, user), do: force_password_reset(user)
1133 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1134 def perform(:delete, %User{} = user) do
1135 {:ok, _user} = ActivityPub.delete(user)
1137 # Remove all relationships
1140 |> Enum.each(fn follower ->
1141 ActivityPub.unfollow(follower, user)
1142 unfollow(follower, user)
1147 |> Enum.each(fn followed ->
1148 ActivityPub.unfollow(user, followed)
1149 unfollow(user, followed)
1152 delete_user_activities(user)
1153 invalidate_cache(user)
1157 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1158 def perform(:fetch_initial_posts, %User{} = user) do
1159 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1161 # Insert all the posts in reverse order, so they're in the right order on the timeline
1162 user.source_data["outbox"]
1163 |> Utils.fetch_ordered_collection(pages)
1165 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1168 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1170 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1171 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1172 when is_list(blocked_identifiers) do
1174 blocked_identifiers,
1175 fn blocked_identifier ->
1176 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1177 {:ok, blocker} <- block(blocker, blocked),
1178 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1182 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1189 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1190 def perform(:follow_import, %User{} = follower, followed_identifiers)
1191 when is_list(followed_identifiers) do
1193 followed_identifiers,
1194 fn followed_identifier ->
1195 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1196 {:ok, follower} <- maybe_direct_follow(follower, followed),
1197 {:ok, _} <- ActivityPub.follow(follower, followed) do
1201 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1208 @spec external_users_query() :: Ecto.Query.t()
1209 def external_users_query do
1217 @spec external_users(keyword()) :: [User.t()]
1218 def external_users(opts \\ []) do
1220 external_users_query()
1221 |> select([u], struct(u, [:id, :ap_id, :info]))
1225 do: where(query, [u], u.id > ^opts[:max_id]),
1230 do: limit(query, ^opts[:limit]),
1236 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1237 BackgroundWorker.enqueue("blocks_import", %{
1238 "blocker_id" => blocker.id,
1239 "blocked_identifiers" => blocked_identifiers
1243 def follow_import(%User{} = follower, followed_identifiers)
1244 when is_list(followed_identifiers) do
1245 BackgroundWorker.enqueue("follow_import", %{
1246 "follower_id" => follower.id,
1247 "followed_identifiers" => followed_identifiers
1251 def delete_user_activities(%User{ap_id: ap_id}) do
1253 |> Activity.Queries.by_actor()
1254 |> RepoStreamer.chunk_stream(50)
1255 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1259 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1261 |> Object.normalize()
1262 |> ActivityPub.delete()
1265 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1266 object = Object.normalize(activity)
1269 |> get_cached_by_ap_id()
1270 |> ActivityPub.unlike(object)
1273 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1274 object = Object.normalize(activity)
1277 |> get_cached_by_ap_id()
1278 |> ActivityPub.unannounce(object)
1281 defp delete_activity(_activity), do: "Doing nothing"
1283 def html_filter_policy(%User{no_rich_text: true}) do
1284 Pleroma.HTML.Scrubber.TwitterText
1287 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1289 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1291 def get_or_fetch_by_ap_id(ap_id) do
1292 user = get_cached_by_ap_id(ap_id)
1294 if !is_nil(user) and !needs_update?(user) do
1297 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1298 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1300 resp = fetch_by_ap_id(ap_id)
1302 if should_fetch_initial do
1303 with {:ok, %User{} = user} <- resp do
1304 fetch_initial_posts(user)
1312 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1313 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1314 with %User{} = user <- get_cached_by_ap_id(uri) do
1320 |> cast(%{}, [:ap_id, :nickname, :local])
1321 |> put_change(:ap_id, uri)
1322 |> put_change(:nickname, nickname)
1323 |> put_change(:local, true)
1324 |> put_change(:follower_address, uri <> "/followers")
1332 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1335 |> :public_key.pem_decode()
1337 |> :public_key.pem_entry_decode()
1342 def public_key(_), do: {:error, "not found key"}
1344 def get_public_key_for_ap_id(ap_id) do
1345 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1346 {:ok, public_key} <- public_key(user) do
1353 defp blank?(""), do: nil
1354 defp blank?(n), do: n
1356 def insert_or_update_user(data) do
1358 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1359 |> remote_user_creation()
1360 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1364 def ap_enabled?(%User{local: true}), do: true
1365 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1366 def ap_enabled?(_), do: false
1368 @doc "Gets or fetch a user by uri or nickname."
1369 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1370 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1371 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1373 # wait a period of time and return newest version of the User structs
1374 # this is because we have synchronous follow APIs and need to simulate them
1375 # with an async handshake
1376 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1377 with %User{} = a <- get_cached_by_id(a.id),
1378 %User{} = b <- get_cached_by_id(b.id) do
1385 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1386 with :ok <- :timer.sleep(timeout),
1387 %User{} = a <- get_cached_by_id(a.id),
1388 %User{} = b <- get_cached_by_id(b.id) do
1395 def parse_bio(bio) when is_binary(bio) and bio != "" do
1397 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1401 def parse_bio(_), do: ""
1403 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1404 # TODO: get profile URLs other than user.ap_id
1405 profile_urls = [user.ap_id]
1408 |> CommonUtils.format_input("text/plain",
1409 mentions_format: :full,
1410 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1415 def parse_bio(_, _), do: ""
1417 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1418 Repo.transaction(fn ->
1419 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1423 def tag(nickname, tags) when is_binary(nickname),
1424 do: tag(get_by_nickname(nickname), tags)
1426 def tag(%User{} = user, tags),
1427 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1429 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1430 Repo.transaction(fn ->
1431 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1435 def untag(nickname, tags) when is_binary(nickname),
1436 do: untag(get_by_nickname(nickname), tags)
1438 def untag(%User{} = user, tags),
1439 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1441 defp update_tags(%User{} = user, new_tags) do
1442 {:ok, updated_user} =
1444 |> change(%{tags: new_tags})
1445 |> update_and_set_cache()
1450 defp normalize_tags(tags) do
1453 |> Enum.map(&String.downcase/1)
1456 defp local_nickname_regex do
1457 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1458 @extended_local_nickname_regex
1460 @strict_local_nickname_regex
1464 def local_nickname(nickname_or_mention) do
1467 |> String.split("@")
1471 def full_nickname(nickname_or_mention),
1472 do: String.trim_leading(nickname_or_mention, "@")
1474 def error_user(ap_id) do
1478 nickname: "erroruser@example.com",
1479 inserted_at: NaiveDateTime.utc_now()
1483 @spec all_superusers() :: [User.t()]
1484 def all_superusers do
1485 User.Query.build(%{super_users: true, local: true, deactivated: false})
1489 def showing_reblogs?(%User{} = user, %User{} = target) do
1490 target.ap_id not in user.muted_reblogs
1494 The function returns a query to get users with no activity for given interval of days.
1495 Inactive users are those who didn't read any notification, or had any activity where
1496 the user is the activity's actor, during `inactivity_threshold` days.
1497 Deactivated users will not appear in this list.
1501 iex> Pleroma.User.list_inactive_users()
1504 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1505 def list_inactive_users_query(inactivity_threshold \\ 7) do
1506 negative_inactivity_threshold = -inactivity_threshold
1507 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1508 # Subqueries are not supported in `where` clauses, join gets too complicated.
1509 has_read_notifications =
1510 from(n in Pleroma.Notification,
1511 where: n.seen == true,
1513 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1516 |> Pleroma.Repo.all()
1518 from(u in Pleroma.User,
1519 left_join: a in Pleroma.Activity,
1520 on: u.ap_id == a.actor,
1521 where: not is_nil(u.nickname),
1522 where: u.deactivated != ^true,
1523 where: u.id not in ^has_read_notifications,
1526 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1527 is_nil(max(a.inserted_at))
1532 Enable or disable email notifications for user
1536 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1537 Pleroma.User{email_notifications: %{"digest" => true}}
1539 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1540 Pleroma.User{email_notifications: %{"digest" => false}}
1542 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1543 {:ok, t()} | {:error, Ecto.Changeset.t()}
1544 def switch_email_notifications(user, type, status) do
1545 User.update_email_notifications(user, %{type => status})
1549 Set `last_digest_emailed_at` value for the user to current time
1551 @spec touch_last_digest_emailed_at(t()) :: t()
1552 def touch_last_digest_emailed_at(user) do
1553 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1555 {:ok, updated_user} =
1557 |> change(%{last_digest_emailed_at: now})
1558 |> update_and_set_cache()
1563 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1564 def toggle_confirmation(%User{} = user) do
1566 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1567 |> update_and_set_cache()
1570 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1574 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1575 # use instance-default
1576 config = Pleroma.Config.get([:assets, :mascots])
1577 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1578 mascot = Keyword.get(config, default_mascot)
1581 "id" => "default-mascot",
1582 "url" => mascot[:url],
1583 "preview_url" => mascot[:url],
1585 "mime_type" => mascot[:mime_type]
1590 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1592 def ensure_keys_present(%User{} = user) do
1593 with {:ok, pem} <- Keys.generate_rsa_pem() do
1595 |> cast(%{keys: pem}, [:keys])
1596 |> validate_required([:keys])
1597 |> update_and_set_cache()
1601 def get_ap_ids_by_nicknames(nicknames) do
1603 where: u.nickname in ^nicknames,
1609 defdelegate search(query, opts \\ []), to: User.Search
1611 defp put_password_hash(
1612 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1614 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1617 defp put_password_hash(changeset), do: changeset
1619 def is_internal_user?(%User{nickname: nil}), do: true
1620 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1621 def is_internal_user?(_), do: false
1623 # A hack because user delete activities have a fake id for whatever reason
1624 # TODO: Get rid of this
1625 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1627 def get_delivered_users_by_object_id(object_id) do
1629 inner_join: delivery in assoc(u, :deliveries),
1630 where: delivery.object_id == ^object_id
1635 def change_email(user, email) do
1637 |> cast(%{email: email}, [:email])
1638 |> validate_required([:email])
1639 |> unique_constraint(:email)
1640 |> validate_format(:email, @email_regex)
1641 |> update_and_set_cache()
1644 # Internal function; public one is `deactivate/2`
1645 defp set_activation_status(user, deactivated) do
1647 |> cast(%{deactivated: deactivated}, [:deactivated])
1648 |> update_and_set_cache()
1651 def update_banner(user, banner) do
1653 |> cast(%{banner: banner}, [:banner])
1654 |> update_and_set_cache()
1657 def update_background(user, background) do
1659 |> cast(%{background: background}, [:background])
1660 |> update_and_set_cache()
1663 def update_source_data(user, source_data) do
1665 |> cast(%{source_data: source_data}, [:source_data])
1666 |> update_and_set_cache()
1669 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1672 moderator: is_moderator
1676 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1677 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1678 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1679 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1682 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1683 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1687 def fields(%{fields: nil}), do: []
1689 def fields(%{fields: fields}), do: fields
1691 def validate_fields(changeset, remote? \\ false) do
1692 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1693 limit = Pleroma.Config.get([:instance, limit_name], 0)
1696 |> validate_length(:fields, max: limit)
1697 |> validate_change(:fields, fn :fields, fields ->
1698 if Enum.all?(fields, &valid_field?/1) do
1706 defp valid_field?(%{"name" => name, "value" => value}) do
1707 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1708 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1710 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1711 String.length(value) <= value_limit
1714 defp valid_field?(_), do: false
1716 defp truncate_field(%{"name" => name, "value" => value}) do
1718 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1721 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1723 %{"name" => name, "value" => value}
1726 def admin_api_update(user, params) do
1733 |> update_and_set_cache()
1736 def mascot_update(user, url) do
1738 |> cast(%{mascot: url}, [:mascot])
1739 |> validate_required([:mascot])
1740 |> update_and_set_cache()
1743 def mastodon_settings_update(user, settings) do
1745 |> cast(%{settings: settings}, [:settings])
1746 |> validate_required([:settings])
1747 |> update_and_set_cache()
1750 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1751 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1753 if need_confirmation? do
1755 confirmation_pending: true,
1756 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1760 confirmation_pending: false,
1761 confirmation_token: nil
1765 cast(user, params, [:confirmation_pending, :confirmation_token])
1768 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1769 if id not in user.pinned_activities do
1770 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1771 params = %{pinned_activities: user.pinned_activities ++ [id]}
1774 |> cast(params, [:pinned_activities])
1775 |> validate_length(:pinned_activities,
1776 max: max_pinned_statuses,
1777 message: "You have already pinned the maximum number of statuses"
1782 |> update_and_set_cache()
1785 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1786 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1789 |> cast(params, [:pinned_activities])
1790 |> update_and_set_cache()
1793 def update_email_notifications(user, settings) do
1794 email_notifications =
1795 user.email_notifications
1796 |> Map.merge(settings)
1797 |> Map.take(["digest"])
1799 params = %{email_notifications: email_notifications}
1800 fields = [:email_notifications]
1803 |> cast(params, fields)
1804 |> validate_required(fields)
1805 |> update_and_set_cache()
1808 defp set_subscribers(user, subscribers) do
1809 params = %{subscribers: subscribers}
1812 |> cast(params, [:subscribers])
1813 |> validate_required([:subscribers])
1814 |> update_and_set_cache()
1817 def add_to_subscribers(user, subscribed) do
1818 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1821 def remove_from_subscribers(user, subscribed) do
1822 set_subscribers(user, List.delete(user.subscribers, subscribed))
1825 defp set_domain_blocks(user, domain_blocks) do
1826 params = %{domain_blocks: domain_blocks}
1829 |> cast(params, [:domain_blocks])
1830 |> validate_required([:domain_blocks])
1831 |> update_and_set_cache()
1834 def block_domain(user, domain_blocked) do
1835 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1838 def unblock_domain(user, domain_blocked) do
1839 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1842 defp set_blocks(user, blocks) do
1843 params = %{blocks: blocks}
1846 |> cast(params, [:blocks])
1847 |> validate_required([:blocks])
1848 |> update_and_set_cache()
1851 def add_to_block(user, blocked) do
1852 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1855 def remove_from_block(user, blocked) do
1856 set_blocks(user, List.delete(user.blocks, blocked))
1859 defp set_mutes(user, mutes) do
1860 params = %{mutes: mutes}
1863 |> cast(params, [:mutes])
1864 |> validate_required([:mutes])
1865 |> update_and_set_cache()
1868 def add_to_mutes(user, muted, notifications?) do
1869 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1870 set_notification_mutes(
1872 Enum.uniq([muted | user.muted_notifications]),
1878 def remove_from_mutes(user, muted) do
1879 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1880 set_notification_mutes(
1882 List.delete(user.muted_notifications, muted),
1888 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1892 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1893 params = %{muted_notifications: muted_notifications}
1896 |> cast(params, [:muted_notifications])
1897 |> validate_required([:muted_notifications])
1898 |> update_and_set_cache()
1901 def add_reblog_mute(user, ap_id) do
1902 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1905 |> cast(params, [:muted_reblogs])
1906 |> update_and_set_cache()
1909 def remove_reblog_mute(user, ap_id) do
1910 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1913 |> cast(params, [:muted_reblogs])
1914 |> update_and_set_cache()
1917 def set_invisible(user, invisible) do
1918 params = %{invisible: invisible}
1921 |> cast(params, [:invisible])
1922 |> validate_required([:invisible])
1923 |> update_and_set_cache()