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)
1099 # Only update local user counts, remote will be update during the next pull.
1102 |> Enum.filter(& &1.local)
1103 |> Enum.each(&update_follower_count/1)
1109 def update_notification_settings(%User{} = user, settings) do
1112 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1115 notification_settings =
1116 user.notification_settings
1117 |> Map.merge(settings)
1118 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1120 params = %{notification_settings: notification_settings}
1123 |> cast(params, [:notification_settings])
1124 |> validate_required([:notification_settings])
1125 |> update_and_set_cache()
1128 def delete(users) when is_list(users) do
1129 for user <- users, do: delete(user)
1132 def delete(%User{} = user) do
1133 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1136 def perform(:force_password_reset, user), do: force_password_reset(user)
1138 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1139 def perform(:delete, %User{} = user) do
1140 {:ok, _user} = ActivityPub.delete(user)
1142 # Remove all relationships
1145 |> Enum.each(fn follower ->
1146 ActivityPub.unfollow(follower, user)
1147 unfollow(follower, user)
1152 |> Enum.each(fn followed ->
1153 ActivityPub.unfollow(user, followed)
1154 unfollow(user, followed)
1157 delete_user_activities(user)
1158 invalidate_cache(user)
1162 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1163 def perform(:fetch_initial_posts, %User{} = user) do
1164 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1166 # Insert all the posts in reverse order, so they're in the right order on the timeline
1167 user.source_data["outbox"]
1168 |> Utils.fetch_ordered_collection(pages)
1170 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1173 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1175 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1176 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1177 when is_list(blocked_identifiers) do
1179 blocked_identifiers,
1180 fn blocked_identifier ->
1181 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1182 {:ok, blocker} <- block(blocker, blocked),
1183 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1187 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1194 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1195 def perform(:follow_import, %User{} = follower, followed_identifiers)
1196 when is_list(followed_identifiers) do
1198 followed_identifiers,
1199 fn followed_identifier ->
1200 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1201 {:ok, follower} <- maybe_direct_follow(follower, followed),
1202 {:ok, _} <- ActivityPub.follow(follower, followed) do
1206 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1213 @spec external_users_query() :: Ecto.Query.t()
1214 def external_users_query do
1222 @spec external_users(keyword()) :: [User.t()]
1223 def external_users(opts \\ []) do
1225 external_users_query()
1226 |> select([u], struct(u, [:id, :ap_id, :info]))
1230 do: where(query, [u], u.id > ^opts[:max_id]),
1235 do: limit(query, ^opts[:limit]),
1241 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1242 BackgroundWorker.enqueue("blocks_import", %{
1243 "blocker_id" => blocker.id,
1244 "blocked_identifiers" => blocked_identifiers
1248 def follow_import(%User{} = follower, followed_identifiers)
1249 when is_list(followed_identifiers) do
1250 BackgroundWorker.enqueue("follow_import", %{
1251 "follower_id" => follower.id,
1252 "followed_identifiers" => followed_identifiers
1256 def delete_user_activities(%User{ap_id: ap_id}) do
1258 |> Activity.Queries.by_actor()
1259 |> RepoStreamer.chunk_stream(50)
1260 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1264 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1266 |> Object.normalize()
1267 |> ActivityPub.delete()
1270 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1271 object = Object.normalize(activity)
1274 |> get_cached_by_ap_id()
1275 |> ActivityPub.unlike(object)
1278 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1279 object = Object.normalize(activity)
1282 |> get_cached_by_ap_id()
1283 |> ActivityPub.unannounce(object)
1286 defp delete_activity(_activity), do: "Doing nothing"
1288 def html_filter_policy(%User{no_rich_text: true}) do
1289 Pleroma.HTML.Scrubber.TwitterText
1292 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1294 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1296 def get_or_fetch_by_ap_id(ap_id) do
1297 user = get_cached_by_ap_id(ap_id)
1299 if !is_nil(user) and !needs_update?(user) do
1302 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1303 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1305 resp = fetch_by_ap_id(ap_id)
1307 if should_fetch_initial do
1308 with {:ok, %User{} = user} <- resp do
1309 fetch_initial_posts(user)
1317 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1318 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1319 with %User{} = user <- get_cached_by_ap_id(uri) do
1325 |> cast(%{}, [:ap_id, :nickname, :local])
1326 |> put_change(:ap_id, uri)
1327 |> put_change(:nickname, nickname)
1328 |> put_change(:local, true)
1329 |> put_change(:follower_address, uri <> "/followers")
1337 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1340 |> :public_key.pem_decode()
1342 |> :public_key.pem_entry_decode()
1347 def public_key(_), do: {:error, "not found key"}
1349 def get_public_key_for_ap_id(ap_id) do
1350 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1351 {:ok, public_key} <- public_key(user) do
1358 defp blank?(""), do: nil
1359 defp blank?(n), do: n
1361 def insert_or_update_user(data) do
1363 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1364 |> remote_user_creation()
1365 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1369 def ap_enabled?(%User{local: true}), do: true
1370 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1371 def ap_enabled?(_), do: false
1373 @doc "Gets or fetch a user by uri or nickname."
1374 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1375 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1376 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1378 # wait a period of time and return newest version of the User structs
1379 # this is because we have synchronous follow APIs and need to simulate them
1380 # with an async handshake
1381 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1382 with %User{} = a <- get_cached_by_id(a.id),
1383 %User{} = b <- get_cached_by_id(b.id) do
1390 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1391 with :ok <- :timer.sleep(timeout),
1392 %User{} = a <- get_cached_by_id(a.id),
1393 %User{} = b <- get_cached_by_id(b.id) do
1400 def parse_bio(bio) when is_binary(bio) and bio != "" do
1402 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1406 def parse_bio(_), do: ""
1408 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1409 # TODO: get profile URLs other than user.ap_id
1410 profile_urls = [user.ap_id]
1413 |> CommonUtils.format_input("text/plain",
1414 mentions_format: :full,
1415 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1420 def parse_bio(_, _), do: ""
1422 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1423 Repo.transaction(fn ->
1424 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1428 def tag(nickname, tags) when is_binary(nickname),
1429 do: tag(get_by_nickname(nickname), tags)
1431 def tag(%User{} = user, tags),
1432 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1434 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1435 Repo.transaction(fn ->
1436 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1440 def untag(nickname, tags) when is_binary(nickname),
1441 do: untag(get_by_nickname(nickname), tags)
1443 def untag(%User{} = user, tags),
1444 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1446 defp update_tags(%User{} = user, new_tags) do
1447 {:ok, updated_user} =
1449 |> change(%{tags: new_tags})
1450 |> update_and_set_cache()
1455 defp normalize_tags(tags) do
1458 |> Enum.map(&String.downcase/1)
1461 defp local_nickname_regex do
1462 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1463 @extended_local_nickname_regex
1465 @strict_local_nickname_regex
1469 def local_nickname(nickname_or_mention) do
1472 |> String.split("@")
1476 def full_nickname(nickname_or_mention),
1477 do: String.trim_leading(nickname_or_mention, "@")
1479 def error_user(ap_id) do
1483 nickname: "erroruser@example.com",
1484 inserted_at: NaiveDateTime.utc_now()
1488 @spec all_superusers() :: [User.t()]
1489 def all_superusers do
1490 User.Query.build(%{super_users: true, local: true, deactivated: false})
1494 def showing_reblogs?(%User{} = user, %User{} = target) do
1495 target.ap_id not in user.muted_reblogs
1499 The function returns a query to get users with no activity for given interval of days.
1500 Inactive users are those who didn't read any notification, or had any activity where
1501 the user is the activity's actor, during `inactivity_threshold` days.
1502 Deactivated users will not appear in this list.
1506 iex> Pleroma.User.list_inactive_users()
1509 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1510 def list_inactive_users_query(inactivity_threshold \\ 7) do
1511 negative_inactivity_threshold = -inactivity_threshold
1512 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1513 # Subqueries are not supported in `where` clauses, join gets too complicated.
1514 has_read_notifications =
1515 from(n in Pleroma.Notification,
1516 where: n.seen == true,
1518 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1521 |> Pleroma.Repo.all()
1523 from(u in Pleroma.User,
1524 left_join: a in Pleroma.Activity,
1525 on: u.ap_id == a.actor,
1526 where: not is_nil(u.nickname),
1527 where: u.deactivated != ^true,
1528 where: u.id not in ^has_read_notifications,
1531 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1532 is_nil(max(a.inserted_at))
1537 Enable or disable email notifications for user
1541 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1542 Pleroma.User{email_notifications: %{"digest" => true}}
1544 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1545 Pleroma.User{email_notifications: %{"digest" => false}}
1547 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1548 {:ok, t()} | {:error, Ecto.Changeset.t()}
1549 def switch_email_notifications(user, type, status) do
1550 User.update_email_notifications(user, %{type => status})
1554 Set `last_digest_emailed_at` value for the user to current time
1556 @spec touch_last_digest_emailed_at(t()) :: t()
1557 def touch_last_digest_emailed_at(user) do
1558 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1560 {:ok, updated_user} =
1562 |> change(%{last_digest_emailed_at: now})
1563 |> update_and_set_cache()
1568 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1569 def toggle_confirmation(%User{} = user) do
1571 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1572 |> update_and_set_cache()
1575 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1579 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1580 # use instance-default
1581 config = Pleroma.Config.get([:assets, :mascots])
1582 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1583 mascot = Keyword.get(config, default_mascot)
1586 "id" => "default-mascot",
1587 "url" => mascot[:url],
1588 "preview_url" => mascot[:url],
1590 "mime_type" => mascot[:mime_type]
1595 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1597 def ensure_keys_present(%User{} = user) do
1598 with {:ok, pem} <- Keys.generate_rsa_pem() do
1600 |> cast(%{keys: pem}, [:keys])
1601 |> validate_required([:keys])
1602 |> update_and_set_cache()
1606 def get_ap_ids_by_nicknames(nicknames) do
1608 where: u.nickname in ^nicknames,
1614 defdelegate search(query, opts \\ []), to: User.Search
1616 defp put_password_hash(
1617 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1619 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1622 defp put_password_hash(changeset), do: changeset
1624 def is_internal_user?(%User{nickname: nil}), do: true
1625 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1626 def is_internal_user?(_), do: false
1628 # A hack because user delete activities have a fake id for whatever reason
1629 # TODO: Get rid of this
1630 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1632 def get_delivered_users_by_object_id(object_id) do
1634 inner_join: delivery in assoc(u, :deliveries),
1635 where: delivery.object_id == ^object_id
1640 def change_email(user, email) do
1642 |> cast(%{email: email}, [:email])
1643 |> validate_required([:email])
1644 |> unique_constraint(:email)
1645 |> validate_format(:email, @email_regex)
1646 |> update_and_set_cache()
1649 # Internal function; public one is `deactivate/2`
1650 defp set_activation_status(user, deactivated) do
1652 |> cast(%{deactivated: deactivated}, [:deactivated])
1653 |> update_and_set_cache()
1656 def update_banner(user, banner) do
1658 |> cast(%{banner: banner}, [:banner])
1659 |> update_and_set_cache()
1662 def update_background(user, background) do
1664 |> cast(%{background: background}, [:background])
1665 |> update_and_set_cache()
1668 def update_source_data(user, source_data) do
1670 |> cast(%{source_data: source_data}, [:source_data])
1671 |> update_and_set_cache()
1674 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1677 moderator: is_moderator
1681 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1682 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1683 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1684 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1687 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1688 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1692 def fields(%{fields: nil}), do: []
1694 def fields(%{fields: fields}), do: fields
1696 def validate_fields(changeset, remote? \\ false) do
1697 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1698 limit = Pleroma.Config.get([:instance, limit_name], 0)
1701 |> validate_length(:fields, max: limit)
1702 |> validate_change(:fields, fn :fields, fields ->
1703 if Enum.all?(fields, &valid_field?/1) do
1711 defp valid_field?(%{"name" => name, "value" => value}) do
1712 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1713 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1715 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1716 String.length(value) <= value_limit
1719 defp valid_field?(_), do: false
1721 defp truncate_field(%{"name" => name, "value" => value}) do
1723 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1726 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1728 %{"name" => name, "value" => value}
1731 def admin_api_update(user, params) do
1738 |> update_and_set_cache()
1741 def mascot_update(user, url) do
1743 |> cast(%{mascot: url}, [:mascot])
1744 |> validate_required([:mascot])
1745 |> update_and_set_cache()
1748 def mastodon_settings_update(user, settings) do
1750 |> cast(%{settings: settings}, [:settings])
1751 |> validate_required([:settings])
1752 |> update_and_set_cache()
1755 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1756 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1758 if need_confirmation? do
1760 confirmation_pending: true,
1761 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1765 confirmation_pending: false,
1766 confirmation_token: nil
1770 cast(user, params, [:confirmation_pending, :confirmation_token])
1773 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1774 if id not in user.pinned_activities do
1775 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1776 params = %{pinned_activities: user.pinned_activities ++ [id]}
1779 |> cast(params, [:pinned_activities])
1780 |> validate_length(:pinned_activities,
1781 max: max_pinned_statuses,
1782 message: "You have already pinned the maximum number of statuses"
1787 |> update_and_set_cache()
1790 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1791 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1794 |> cast(params, [:pinned_activities])
1795 |> update_and_set_cache()
1798 def update_email_notifications(user, settings) do
1799 email_notifications =
1800 user.email_notifications
1801 |> Map.merge(settings)
1802 |> Map.take(["digest"])
1804 params = %{email_notifications: email_notifications}
1805 fields = [:email_notifications]
1808 |> cast(params, fields)
1809 |> validate_required(fields)
1810 |> update_and_set_cache()
1813 defp set_subscribers(user, subscribers) do
1814 params = %{subscribers: subscribers}
1817 |> cast(params, [:subscribers])
1818 |> validate_required([:subscribers])
1819 |> update_and_set_cache()
1822 def add_to_subscribers(user, subscribed) do
1823 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1826 def remove_from_subscribers(user, subscribed) do
1827 set_subscribers(user, List.delete(user.subscribers, subscribed))
1830 defp set_domain_blocks(user, domain_blocks) do
1831 params = %{domain_blocks: domain_blocks}
1834 |> cast(params, [:domain_blocks])
1835 |> validate_required([:domain_blocks])
1836 |> update_and_set_cache()
1839 def block_domain(user, domain_blocked) do
1840 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1843 def unblock_domain(user, domain_blocked) do
1844 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1847 defp set_blocks(user, blocks) do
1848 params = %{blocks: blocks}
1851 |> cast(params, [:blocks])
1852 |> validate_required([:blocks])
1853 |> update_and_set_cache()
1856 def add_to_block(user, blocked) do
1857 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1860 def remove_from_block(user, blocked) do
1861 set_blocks(user, List.delete(user.blocks, blocked))
1864 defp set_mutes(user, mutes) do
1865 params = %{mutes: mutes}
1868 |> cast(params, [:mutes])
1869 |> validate_required([:mutes])
1870 |> update_and_set_cache()
1873 def add_to_mutes(user, muted, notifications?) do
1874 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1875 set_notification_mutes(
1877 Enum.uniq([muted | user.muted_notifications]),
1883 def remove_from_mutes(user, muted) do
1884 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1885 set_notification_mutes(
1887 List.delete(user.muted_notifications, muted),
1893 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1897 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1898 params = %{muted_notifications: muted_notifications}
1901 |> cast(params, [:muted_notifications])
1902 |> validate_required([:muted_notifications])
1903 |> update_and_set_cache()
1906 def add_reblog_mute(user, ap_id) do
1907 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1910 |> cast(params, [:muted_reblogs])
1911 |> update_and_set_cache()
1914 def remove_reblog_mute(user, ap_id) do
1915 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1918 |> cast(params, [:muted_reblogs])
1919 |> update_and_set_cache()
1922 def set_invisible(user, invisible) do
1923 params = %{invisible: invisible}
1926 |> cast(params, [:invisible])
1927 |> validate_required([:invisible])
1928 |> update_and_set_cache()