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
17 alias Pleroma.Notification
19 alias Pleroma.Registration
21 alias Pleroma.RepoStreamer
24 alias Pleroma.Web.ActivityPub.ActivityPub
25 alias Pleroma.Web.ActivityPub.Utils
26 alias Pleroma.Web.CommonAPI
27 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
28 alias Pleroma.Web.OAuth
29 alias Pleroma.Web.RelMe
30 alias Pleroma.Workers.BackgroundWorker
34 @type t :: %__MODULE__{}
36 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
38 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
39 @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])?)*$/
41 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
42 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
46 field(:email, :string)
48 field(:nickname, :string)
49 field(:password_hash, :string)
50 field(:password, :string, virtual: true)
51 field(:password_confirmation, :string, virtual: true)
53 field(:following, {:array, :string}, default: [])
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(:topic, :string, default: nil)
93 field(:hub, :string, default: nil)
94 field(:salmon, :string, default: nil)
95 field(:hide_followers_count, :boolean, default: false)
96 field(:hide_follows_count, :boolean, default: false)
97 field(:hide_followers, :boolean, default: false)
98 field(:hide_follows, :boolean, default: false)
99 field(:hide_favorites, :boolean, default: true)
100 field(:unread_conversation_count, :integer, default: 0)
101 field(:pinned_activities, {:array, :string}, default: [])
102 field(:email_notifications, :map, default: %{"digest" => false})
103 field(:mascot, :map, default: nil)
104 field(:emoji, {:array, :map}, default: [])
105 field(:pleroma_settings_store, :map, default: %{})
106 field(:fields, {:array, :map}, default: nil)
107 field(:raw_fields, {:array, :map}, default: [])
108 field(:discoverable, :boolean, default: false)
109 field(:skip_thread_containment, :boolean, default: false)
111 field(:notification_settings, :map,
115 "non_follows" => true,
116 "non_followers" => true
120 has_many(:notifications, Notification)
121 has_many(:registrations, Registration)
122 has_many(:deliveries, Delivery)
124 field(:info, :map, default: %{})
129 def auth_active?(%User{confirmation_pending: true}),
130 do: !Pleroma.Config.get([:instance, :account_activation_required])
132 def auth_active?(%User{}), do: true
134 def visible_for?(user, for_user \\ nil)
136 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
138 def visible_for?(%User{} = user, for_user) do
139 auth_active?(user) || superuser?(for_user)
142 def visible_for?(_, _), do: false
144 def superuser?(%User{local: true, is_admin: true}), do: true
145 def superuser?(%User{local: true, is_moderator: true}), do: true
146 def superuser?(_), do: false
148 def avatar_url(user, options \\ []) do
150 %{"url" => [%{"href" => href} | _]} -> href
151 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
155 def banner_url(user, options \\ []) do
157 %{"url" => [%{"href" => href} | _]} -> href
158 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
162 def profile_url(%User{source_data: %{"url" => url}}), do: url
163 def profile_url(%User{ap_id: ap_id}), do: ap_id
164 def profile_url(_), do: nil
166 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
168 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
169 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
171 @spec ap_following(User.t()) :: Sring.t()
172 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
173 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
175 def user_info(%User{} = user, args \\ %{}) do
177 Map.get(args, :following_count, user.following_count || following_count(user))
179 follower_count = Map.get(args, :follower_count, user.follower_count)
182 note_count: user.note_count,
184 confirmation_pending: user.confirmation_pending,
185 default_scope: user.default_scope
187 |> Map.put(:following_count, following_count)
188 |> Map.put(:follower_count, follower_count)
191 def follow_state(%User{} = user, %User{} = target) do
192 case Utils.fetch_latest_follow(user, target) do
193 %{data: %{"state" => state}} -> state
194 # Ideally this would be nil, but then Cachex does not commit the value
199 def get_cached_follow_state(user, target) do
200 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
201 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
204 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
205 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
206 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
209 def set_info_cache(user, args) do
210 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
213 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
214 def restrict_deactivated(query) do
215 from(u in query, where: u.deactivated != ^true)
218 def following_count(%User{following: []}), do: 0
220 def following_count(%User{} = user) do
222 |> get_friends_query()
223 |> Repo.aggregate(:count, :id)
234 :confirmation_pending,
235 :password_reset_pending,
242 :muted_notifications,
256 :hide_followers_count,
261 :unread_conversation_count,
263 :email_notifications,
266 :pleroma_settings_store,
270 :skip_thread_containment,
271 :notification_settings
274 def info_fields, do: @info_fields
276 defp truncate_fields_param(params) do
277 if Map.has_key?(params, :fields) do
278 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
284 defp truncate_if_exists(params, key, max_length) do
285 if Map.has_key?(params, key) and is_binary(params[key]) do
286 {value, _chopped} = String.split_at(params[key], max_length)
287 Map.put(params, key, value)
293 def remote_user_creation(params) do
294 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
295 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
299 |> Map.put(:info, params[:info] || %{})
300 |> truncate_if_exists(:name, name_limit)
301 |> truncate_if_exists(:bio, bio_limit)
302 |> truncate_fields_param()
325 :hide_followers_count,
333 |> validate_required([:name, :ap_id])
334 |> unique_constraint(:nickname)
335 |> validate_format(:nickname, @email_regex)
336 |> validate_length(:bio, max: bio_limit)
337 |> validate_length(:name, max: name_limit)
338 |> validate_fields(true)
340 case params[:source_data] do
341 %{"followers" => followers, "following" => following} ->
343 |> put_change(:follower_address, followers)
344 |> put_change(:following_address, following)
347 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
348 put_change(changeset, :follower_address, followers)
352 def update_changeset(struct, params \\ %{}) do
353 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
354 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
370 :hide_followers_count,
375 :skip_thread_containment,
378 :pleroma_settings_store,
382 |> unique_constraint(:nickname)
383 |> validate_format(:nickname, local_nickname_regex())
384 |> validate_length(:bio, max: bio_limit)
385 |> validate_length(:name, min: 1, max: name_limit)
386 |> validate_fields(false)
389 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
390 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
391 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
393 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
395 params = if remote?, do: truncate_fields_param(params), else: params
418 :hide_followers_count,
422 |> unique_constraint(:nickname)
423 |> validate_format(:nickname, local_nickname_regex())
424 |> validate_length(:bio, max: bio_limit)
425 |> validate_length(:name, max: name_limit)
426 |> validate_fields(remote?)
429 def password_update_changeset(struct, params) do
431 |> cast(params, [:password, :password_confirmation])
432 |> validate_required([:password, :password_confirmation])
433 |> validate_confirmation(:password)
434 |> put_password_hash()
435 |> put_change(:password_reset_pending, false)
438 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
439 def reset_password(%User{id: user_id} = user, data) do
442 |> Multi.update(:user, password_update_changeset(user, data))
443 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
444 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
446 case Repo.transaction(multi) do
447 {:ok, %{user: user} = _} -> set_cache(user)
448 {:error, _, changeset, _} -> {:error, changeset}
452 def update_password_reset_pending(user, value) do
455 |> put_change(:password_reset_pending, value)
456 |> update_and_set_cache()
459 def force_password_reset_async(user) do
460 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
463 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
464 def force_password_reset(user), do: update_password_reset_pending(user, true)
466 def register_changeset(struct, params \\ %{}, opts \\ []) do
467 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
468 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
471 if is_nil(opts[:need_confirmation]) do
472 Pleroma.Config.get([:instance, :account_activation_required])
474 opts[:need_confirmation]
478 |> confirmation_changeset(need_confirmation: need_confirmation?)
479 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
480 |> validate_required([:name, :nickname, :password, :password_confirmation])
481 |> validate_confirmation(:password)
482 |> unique_constraint(:email)
483 |> unique_constraint(:nickname)
484 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
485 |> validate_format(:nickname, local_nickname_regex())
486 |> validate_format(:email, @email_regex)
487 |> validate_length(:bio, max: bio_limit)
488 |> validate_length(:name, min: 1, max: name_limit)
489 |> maybe_validate_required_email(opts[:external])
492 |> unique_constraint(:ap_id)
493 |> put_following_and_follower_address()
496 def maybe_validate_required_email(changeset, true), do: changeset
497 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
499 defp put_ap_id(changeset) do
500 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
501 put_change(changeset, :ap_id, ap_id)
504 defp put_following_and_follower_address(changeset) do
505 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
508 |> put_change(:following, [followers])
509 |> put_change(:follower_address, followers)
512 defp autofollow_users(user) do
513 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
516 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
519 follow_all(user, autofollowed_users)
522 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
523 def register(%Ecto.Changeset{} = changeset) do
524 with {:ok, user} <- Repo.insert(changeset) do
525 post_register_action(user)
529 def post_register_action(%User{} = user) do
530 with {:ok, user} <- autofollow_users(user),
531 {:ok, user} <- set_cache(user),
532 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
533 {:ok, _} <- try_send_confirmation_email(user) do
538 def try_send_confirmation_email(%User{} = user) do
539 if user.confirmation_pending &&
540 Pleroma.Config.get([:instance, :account_activation_required]) do
542 |> Pleroma.Emails.UserEmail.account_confirmation_email()
543 |> Pleroma.Emails.Mailer.deliver_async()
551 def needs_update?(%User{local: true}), do: false
553 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
555 def needs_update?(%User{local: false} = user) do
556 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
559 def needs_update?(_), do: true
561 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
562 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true}) do
566 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
567 follow(follower, followed)
570 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
571 if not ap_enabled?(followed) do
572 follow(follower, followed)
578 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
579 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
580 def follow_all(follower, followeds) do
583 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
584 |> Enum.map(fn %{follower_address: fa} -> fa end)
588 where: u.id == ^follower.id,
593 "array(select distinct unnest (array_cat(?, ?)))",
602 {1, [follower]} = Repo.update_all(q, [])
604 Enum.each(followeds, &update_follower_count/1)
609 def follow(%User{} = follower, %User{} = followed) do
610 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
611 ap_followers = followed.follower_address
614 followed.deactivated ->
615 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
617 deny_follow_blocked and blocks?(followed, follower) ->
618 {:error, "Could not follow user: #{followed.nickname} blocked you."}
623 where: u.id == ^follower.id,
624 update: [push: [following: ^ap_followers]],
628 {1, [follower]} = Repo.update_all(q, [])
630 follower = maybe_update_following_count(follower)
632 {:ok, _} = update_follower_count(followed)
638 def unfollow(%User{} = follower, %User{} = followed) do
639 ap_followers = followed.follower_address
641 if following?(follower, followed) and follower.ap_id != followed.ap_id do
644 where: u.id == ^follower.id,
645 update: [pull: [following: ^ap_followers]],
649 {1, [follower]} = Repo.update_all(q, [])
651 follower = maybe_update_following_count(follower)
653 {:ok, followed} = update_follower_count(followed)
657 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
659 {:error, "Not subscribed!"}
663 @spec following?(User.t(), User.t()) :: boolean
664 def following?(%User{} = follower, %User{} = followed) do
665 Enum.member?(follower.following, followed.follower_address)
668 def locked?(%User{} = user) do
673 Repo.get_by(User, id: id)
676 def get_by_ap_id(ap_id) do
677 Repo.get_by(User, ap_id: ap_id)
680 def get_all_by_ap_id(ap_ids) do
681 from(u in __MODULE__,
682 where: u.ap_id in ^ap_ids
687 def get_all_by_ids(ids) do
688 from(u in __MODULE__, where: u.id in ^ids)
692 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
693 # of the ap_id and the domain and tries to get that user
694 def get_by_guessed_nickname(ap_id) do
695 domain = URI.parse(ap_id).host
696 name = List.last(String.split(ap_id, "/"))
697 nickname = "#{name}@#{domain}"
699 get_cached_by_nickname(nickname)
702 def set_cache({:ok, user}), do: set_cache(user)
703 def set_cache({:error, err}), do: {:error, err}
705 def set_cache(%User{} = user) do
706 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
707 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
708 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
712 def update_and_set_cache(struct, params) do
714 |> update_changeset(params)
715 |> update_and_set_cache()
718 def update_and_set_cache(changeset) do
719 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
724 def invalidate_cache(user) do
725 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
726 Cachex.del(:user_cache, "nickname:#{user.nickname}")
727 Cachex.del(:user_cache, "user_info:#{user.id}")
730 def get_cached_by_ap_id(ap_id) do
731 key = "ap_id:#{ap_id}"
732 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
735 def get_cached_by_id(id) do
739 Cachex.fetch!(:user_cache, key, fn _ ->
743 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
744 {:commit, user.ap_id}
750 get_cached_by_ap_id(ap_id)
753 def get_cached_by_nickname(nickname) do
754 key = "nickname:#{nickname}"
756 Cachex.fetch!(:user_cache, key, fn ->
757 case get_or_fetch_by_nickname(nickname) do
758 {:ok, user} -> {:commit, user}
759 {:error, _error} -> {:ignore, nil}
764 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
765 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
768 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
769 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
771 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
772 get_cached_by_nickname(nickname_or_id)
774 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
775 get_cached_by_nickname(nickname_or_id)
782 def get_by_nickname(nickname) do
783 Repo.get_by(User, nickname: nickname) ||
784 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
785 Repo.get_by(User, nickname: local_nickname(nickname))
789 def get_by_email(email), do: Repo.get_by(User, email: email)
791 def get_by_nickname_or_email(nickname_or_email) do
792 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
795 def get_cached_user_info(user) do
796 key = "user_info:#{user.id}"
797 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
800 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
802 def get_or_fetch_by_nickname(nickname) do
803 with %User{} = user <- get_by_nickname(nickname) do
807 with [_nick, _domain] <- String.split(nickname, "@"),
808 {:ok, user} <- fetch_by_nickname(nickname) do
809 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
810 fetch_initial_posts(user)
815 _e -> {:error, "not found " <> nickname}
820 @doc "Fetch some posts when the user has just been federated with"
821 def fetch_initial_posts(user) do
822 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
825 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
826 def get_followers_query(%User{} = user, nil) do
827 User.Query.build(%{followers: user, deactivated: false})
830 def get_followers_query(user, page) do
832 |> get_followers_query(nil)
833 |> User.Query.paginate(page, 20)
836 @spec get_followers_query(User.t()) :: Ecto.Query.t()
837 def get_followers_query(user), do: get_followers_query(user, nil)
839 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
840 def get_followers(user, page \\ nil) do
842 |> get_followers_query(page)
846 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
847 def get_external_followers(user, page \\ nil) do
849 |> get_followers_query(page)
850 |> User.Query.build(%{external: true})
854 def get_followers_ids(user, page \\ nil) do
856 |> get_followers_query(page)
861 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
862 def get_friends_query(%User{} = user, nil) do
863 User.Query.build(%{friends: user, deactivated: false})
866 def get_friends_query(user, page) do
868 |> get_friends_query(nil)
869 |> User.Query.paginate(page, 20)
872 @spec get_friends_query(User.t()) :: Ecto.Query.t()
873 def get_friends_query(user), do: get_friends_query(user, nil)
875 def get_friends(user, page \\ nil) do
877 |> get_friends_query(page)
881 def get_friends_ids(user, page \\ nil) do
883 |> get_friends_query(page)
888 @spec get_follow_requests(User.t()) :: {:ok, [User.t()]}
889 def get_follow_requests(%User{} = user) do
891 |> Activity.follow_requests_for_actor()
892 |> join(:inner, [a], u in User, on: a.actor == u.ap_id)
893 |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
894 |> group_by([a, u], u.id)
899 def increase_note_count(%User{} = user) do
901 |> where(id: ^user.id)
902 |> update([u], inc: [note_count: 1])
904 |> Repo.update_all([])
906 {1, [user]} -> set_cache(user)
911 def decrease_note_count(%User{} = user) do
913 |> where(id: ^user.id)
916 note_count: fragment("greatest(0, note_count - 1)")
920 |> Repo.update_all([])
922 {1, [user]} -> set_cache(user)
927 def update_note_count(%User{} = user, note_count \\ nil) do
932 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
938 |> cast(%{note_count: note_count}, [:note_count])
939 |> update_and_set_cache()
942 @spec maybe_fetch_follow_information(User.t()) :: User.t()
943 def maybe_fetch_follow_information(user) do
944 with {:ok, user} <- fetch_follow_information(user) do
948 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
954 def fetch_follow_information(user) do
955 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
957 |> follow_information_changeset(info)
958 |> update_and_set_cache()
962 defp follow_information_changeset(user, params) do
969 :hide_followers_count,
974 def update_follower_count(%User{} = user) do
975 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
976 follower_count_query =
977 User.Query.build(%{followers: user, deactivated: false})
978 |> select([u], %{count: count(u.id)})
981 |> where(id: ^user.id)
982 |> join(:inner, [u], s in subquery(follower_count_query))
984 set: [follower_count: s.count]
987 |> Repo.update_all([])
989 {1, [user]} -> set_cache(user)
993 {:ok, maybe_fetch_follow_information(user)}
997 @spec maybe_update_following_count(User.t()) :: User.t()
998 def maybe_update_following_count(%User{local: false} = user) do
999 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1000 maybe_fetch_follow_information(user)
1006 def maybe_update_following_count(user), do: user
1008 def set_unread_conversation_count(%User{local: true} = user) do
1009 unread_query = Participation.unread_conversation_count_for_user(user)
1012 |> join(:inner, [u], p in subquery(unread_query))
1014 set: [unread_conversation_count: p.count]
1016 |> where([u], u.id == ^user.id)
1018 |> Repo.update_all([])
1020 {1, [user]} -> set_cache(user)
1025 def set_unread_conversation_count(_), do: :noop
1027 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1029 Participation.unread_conversation_count_for_user(user)
1030 |> where([p], p.conversation_id == ^conversation.id)
1033 |> join(:inner, [u], p in subquery(unread_query))
1035 inc: [unread_conversation_count: 1]
1037 |> where([u], u.id == ^user.id)
1038 |> where([u, p], p.count == 0)
1040 |> Repo.update_all([])
1042 {1, [user]} -> set_cache(user)
1047 def increment_unread_conversation_count(_, _), do: :noop
1049 def remove_duplicated_following(%User{following: following} = user) do
1050 uniq_following = Enum.uniq(following)
1052 if length(following) == length(uniq_following) do
1056 |> update_changeset(%{following: uniq_following})
1057 |> update_and_set_cache()
1061 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1062 def get_users_from_set(ap_ids, local_only \\ true) do
1063 criteria = %{ap_id: ap_ids, deactivated: false}
1064 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1066 User.Query.build(criteria)
1070 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1071 def get_recipients_from_activity(%Activity{recipients: to}) do
1072 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1076 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
1077 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
1078 add_to_mutes(muter, ap_id, notifications?)
1081 def unmute(muter, %{ap_id: ap_id}) do
1082 remove_from_mutes(muter, ap_id)
1085 def subscribe(subscriber, %{ap_id: ap_id}) do
1086 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
1087 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1089 if blocks?(subscribed, subscriber) and deny_follow_blocked do
1090 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
1092 User.add_to_subscribers(subscribed, subscriber.ap_id)
1097 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
1098 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1099 User.remove_from_subscribers(user, unsubscriber.ap_id)
1103 def block(blocker, %User{ap_id: ap_id} = blocked) do
1104 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1106 if following?(blocker, blocked) do
1107 {:ok, blocker, _} = unfollow(blocker, blocked)
1113 # clear any requested follows as well
1115 case CommonAPI.reject_follow_request(blocked, blocker) do
1116 {:ok, %User{} = updated_blocked} -> updated_blocked
1121 if subscribed_to?(blocked, blocker) do
1122 {:ok, blocker} = unsubscribe(blocked, blocker)
1128 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1130 {:ok, blocker} = update_follower_count(blocker)
1132 add_to_block(blocker, ap_id)
1135 # helper to handle the block given only an actor's AP id
1136 def block(blocker, %{ap_id: ap_id}) do
1137 block(blocker, get_cached_by_ap_id(ap_id))
1140 def unblock(blocker, %{ap_id: ap_id}) do
1141 remove_from_block(blocker, ap_id)
1144 def mutes?(nil, _), do: false
1145 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1147 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1148 def muted_notifications?(nil, _), do: false
1150 def muted_notifications?(user, %{ap_id: ap_id}),
1151 do: Enum.member?(user.muted_notifications, ap_id)
1153 def blocks?(%User{} = user, %User{} = target) do
1154 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1157 def blocks?(nil, _), do: false
1159 def blocks_ap_id?(%User{} = user, %User{} = target) do
1160 Enum.member?(user.blocks, target.ap_id)
1163 def blocks_ap_id?(_, _), do: false
1165 def blocks_domain?(%User{} = user, %User{} = target) do
1166 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1167 %{host: host} = URI.parse(target.ap_id)
1168 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1171 def blocks_domain?(_, _), do: false
1173 def subscribed_to?(user, %{ap_id: ap_id}) do
1174 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1175 Enum.member?(target.subscribers, user.ap_id)
1179 @spec muted_users(User.t()) :: [User.t()]
1180 def muted_users(user) do
1181 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1185 @spec blocked_users(User.t()) :: [User.t()]
1186 def blocked_users(user) do
1187 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1191 @spec subscribers(User.t()) :: [User.t()]
1192 def subscribers(user) do
1193 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1197 def deactivate_async(user, status \\ true) do
1198 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1201 def deactivate(user, status \\ true)
1203 def deactivate(users, status) when is_list(users) do
1204 Repo.transaction(fn ->
1205 for user <- users, do: deactivate(user, status)
1209 def deactivate(%User{} = user, status) do
1210 with {:ok, user} <- set_activation_status(user, status) do
1211 Enum.each(get_followers(user), &invalidate_cache/1)
1212 Enum.each(get_friends(user), &update_follower_count/1)
1218 def update_notification_settings(%User{} = user, settings) do
1221 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1224 notification_settings =
1225 user.notification_settings
1226 |> Map.merge(settings)
1227 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1229 params = %{notification_settings: notification_settings}
1232 |> cast(params, [:notification_settings])
1233 |> validate_required([:notification_settings])
1234 |> update_and_set_cache()
1237 def delete(users) when is_list(users) do
1238 for user <- users, do: delete(user)
1241 def delete(%User{} = user) do
1242 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1245 def perform(:force_password_reset, user), do: force_password_reset(user)
1247 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1248 def perform(:delete, %User{} = user) do
1249 {:ok, _user} = ActivityPub.delete(user)
1251 # Remove all relationships
1254 |> Enum.each(fn follower ->
1255 ActivityPub.unfollow(follower, user)
1256 unfollow(follower, user)
1261 |> Enum.each(fn followed ->
1262 ActivityPub.unfollow(user, followed)
1263 unfollow(user, followed)
1266 delete_user_activities(user)
1267 invalidate_cache(user)
1271 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1272 def perform(:fetch_initial_posts, %User{} = user) do
1273 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1275 # Insert all the posts in reverse order, so they're in the right order on the timeline
1276 user.source_data["outbox"]
1277 |> Utils.fetch_ordered_collection(pages)
1279 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1282 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1284 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1285 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1286 when is_list(blocked_identifiers) do
1288 blocked_identifiers,
1289 fn blocked_identifier ->
1290 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1291 {:ok, blocker} <- block(blocker, blocked),
1292 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1296 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1303 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1304 def perform(:follow_import, %User{} = follower, followed_identifiers)
1305 when is_list(followed_identifiers) do
1307 followed_identifiers,
1308 fn followed_identifier ->
1309 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1310 {:ok, follower} <- maybe_direct_follow(follower, followed),
1311 {:ok, _} <- ActivityPub.follow(follower, followed) do
1315 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1322 @spec external_users_query() :: Ecto.Query.t()
1323 def external_users_query do
1331 @spec external_users(keyword()) :: [User.t()]
1332 def external_users(opts \\ []) do
1334 external_users_query()
1335 |> select([u], struct(u, [:id, :ap_id, :info]))
1339 do: where(query, [u], u.id > ^opts[:max_id]),
1344 do: limit(query, ^opts[:limit]),
1350 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1351 BackgroundWorker.enqueue("blocks_import", %{
1352 "blocker_id" => blocker.id,
1353 "blocked_identifiers" => blocked_identifiers
1357 def follow_import(%User{} = follower, followed_identifiers)
1358 when is_list(followed_identifiers) do
1359 BackgroundWorker.enqueue("follow_import", %{
1360 "follower_id" => follower.id,
1361 "followed_identifiers" => followed_identifiers
1365 def delete_user_activities(%User{ap_id: ap_id}) do
1367 |> Activity.Queries.by_actor()
1368 |> RepoStreamer.chunk_stream(50)
1369 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1373 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1375 |> Object.normalize()
1376 |> ActivityPub.delete()
1379 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1380 object = Object.normalize(activity)
1383 |> get_cached_by_ap_id()
1384 |> ActivityPub.unlike(object)
1387 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1388 object = Object.normalize(activity)
1391 |> get_cached_by_ap_id()
1392 |> ActivityPub.unannounce(object)
1395 defp delete_activity(_activity), do: "Doing nothing"
1397 def html_filter_policy(%User{no_rich_text: true}) do
1398 Pleroma.HTML.Scrubber.TwitterText
1401 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1403 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1405 def get_or_fetch_by_ap_id(ap_id) do
1406 user = get_cached_by_ap_id(ap_id)
1408 if !is_nil(user) and !needs_update?(user) do
1411 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1412 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1414 resp = fetch_by_ap_id(ap_id)
1416 if should_fetch_initial do
1417 with {:ok, %User{} = user} <- resp do
1418 fetch_initial_posts(user)
1426 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1427 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1428 with %User{} = user <- get_cached_by_ap_id(uri) do
1434 |> cast(%{}, [:ap_id, :nickname, :local])
1435 |> put_change(:ap_id, uri)
1436 |> put_change(:nickname, nickname)
1437 |> put_change(:local, true)
1438 |> put_change(:follower_address, uri <> "/followers")
1446 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1449 |> :public_key.pem_decode()
1451 |> :public_key.pem_entry_decode()
1456 def public_key_from_info(_), do: {:error, "not found key"}
1458 def get_public_key_for_ap_id(ap_id) do
1459 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1460 {:ok, public_key} <- public_key(user) do
1467 defp blank?(""), do: nil
1468 defp blank?(n), do: n
1470 def insert_or_update_user(data) do
1472 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1473 |> remote_user_creation()
1474 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1478 def ap_enabled?(%User{local: true}), do: true
1479 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1480 def ap_enabled?(_), do: false
1482 @doc "Gets or fetch a user by uri or nickname."
1483 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1484 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1485 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1487 # wait a period of time and return newest version of the User structs
1488 # this is because we have synchronous follow APIs and need to simulate them
1489 # with an async handshake
1490 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1491 with %User{} = a <- get_cached_by_id(a.id),
1492 %User{} = b <- get_cached_by_id(b.id) do
1499 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1500 with :ok <- :timer.sleep(timeout),
1501 %User{} = a <- get_cached_by_id(a.id),
1502 %User{} = b <- get_cached_by_id(b.id) do
1509 def parse_bio(bio) when is_binary(bio) and bio != "" do
1511 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1515 def parse_bio(_), do: ""
1517 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1518 # TODO: get profile URLs other than user.ap_id
1519 profile_urls = [user.ap_id]
1522 |> CommonUtils.format_input("text/plain",
1523 mentions_format: :full,
1524 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1529 def parse_bio(_, _), do: ""
1531 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1532 Repo.transaction(fn ->
1533 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1537 def tag(nickname, tags) when is_binary(nickname),
1538 do: tag(get_by_nickname(nickname), tags)
1540 def tag(%User{} = user, tags),
1541 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1543 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1544 Repo.transaction(fn ->
1545 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1549 def untag(nickname, tags) when is_binary(nickname),
1550 do: untag(get_by_nickname(nickname), tags)
1552 def untag(%User{} = user, tags),
1553 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1555 defp update_tags(%User{} = user, new_tags) do
1556 {:ok, updated_user} =
1558 |> change(%{tags: new_tags})
1559 |> update_and_set_cache()
1564 defp normalize_tags(tags) do
1567 |> Enum.map(&String.downcase/1)
1570 defp local_nickname_regex do
1571 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1572 @extended_local_nickname_regex
1574 @strict_local_nickname_regex
1578 def local_nickname(nickname_or_mention) do
1581 |> String.split("@")
1585 def full_nickname(nickname_or_mention),
1586 do: String.trim_leading(nickname_or_mention, "@")
1588 def error_user(ap_id) do
1592 nickname: "erroruser@example.com",
1593 inserted_at: NaiveDateTime.utc_now()
1597 @spec all_superusers() :: [User.t()]
1598 def all_superusers do
1599 User.Query.build(%{super_users: true, local: true, deactivated: false})
1603 def showing_reblogs?(%User{} = user, %User{} = target) do
1604 target.ap_id not in user.muted_reblogs
1608 The function returns a query to get users with no activity for given interval of days.
1609 Inactive users are those who didn't read any notification, or had any activity where
1610 the user is the activity's actor, during `inactivity_threshold` days.
1611 Deactivated users will not appear in this list.
1615 iex> Pleroma.User.list_inactive_users()
1618 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1619 def list_inactive_users_query(inactivity_threshold \\ 7) do
1620 negative_inactivity_threshold = -inactivity_threshold
1621 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1622 # Subqueries are not supported in `where` clauses, join gets too complicated.
1623 has_read_notifications =
1624 from(n in Pleroma.Notification,
1625 where: n.seen == true,
1627 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1630 |> Pleroma.Repo.all()
1632 from(u in Pleroma.User,
1633 left_join: a in Pleroma.Activity,
1634 on: u.ap_id == a.actor,
1635 where: not is_nil(u.nickname),
1636 where: u.deactivated != ^true,
1637 where: u.id not in ^has_read_notifications,
1640 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1641 is_nil(max(a.inserted_at))
1646 Enable or disable email notifications for user
1650 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1651 Pleroma.User{email_notifications: %{"digest" => true}}
1653 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1654 Pleroma.User{email_notifications: %{"digest" => false}}
1656 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1657 {:ok, t()} | {:error, Ecto.Changeset.t()}
1658 def switch_email_notifications(user, type, status) do
1659 User.update_email_notifications(user, %{type => status})
1663 Set `last_digest_emailed_at` value for the user to current time
1665 @spec touch_last_digest_emailed_at(t()) :: t()
1666 def touch_last_digest_emailed_at(user) do
1667 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1669 {:ok, updated_user} =
1671 |> change(%{last_digest_emailed_at: now})
1672 |> update_and_set_cache()
1677 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1678 def toggle_confirmation(%User{} = user) do
1680 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1681 |> update_and_set_cache()
1684 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1688 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1689 # use instance-default
1690 config = Pleroma.Config.get([:assets, :mascots])
1691 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1692 mascot = Keyword.get(config, default_mascot)
1695 "id" => "default-mascot",
1696 "url" => mascot[:url],
1697 "preview_url" => mascot[:url],
1699 "mime_type" => mascot[:mime_type]
1704 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1706 def ensure_keys_present(%User{} = user) do
1707 with {:ok, pem} <- Keys.generate_rsa_pem() do
1709 |> cast(%{keys: pem}, [:keys])
1710 |> validate_required([:keys])
1711 |> update_and_set_cache()
1715 def get_ap_ids_by_nicknames(nicknames) do
1717 where: u.nickname in ^nicknames,
1723 defdelegate search(query, opts \\ []), to: User.Search
1725 defp put_password_hash(
1726 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1728 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1731 defp put_password_hash(changeset), do: changeset
1733 def is_internal_user?(%User{nickname: nil}), do: true
1734 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1735 def is_internal_user?(_), do: false
1737 # A hack because user delete activities have a fake id for whatever reason
1738 # TODO: Get rid of this
1739 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1741 def get_delivered_users_by_object_id(object_id) do
1743 inner_join: delivery in assoc(u, :deliveries),
1744 where: delivery.object_id == ^object_id
1749 def change_email(user, email) do
1751 |> cast(%{email: email}, [:email])
1752 |> validate_required([:email])
1753 |> unique_constraint(:email)
1754 |> validate_format(:email, @email_regex)
1755 |> update_and_set_cache()
1758 # Internal function; public one is `deactivate/2`
1759 defp set_activation_status(user, deactivated) do
1761 |> cast(%{deactivated: deactivated}, [:deactivated])
1762 |> update_and_set_cache()
1765 def update_banner(user, banner) do
1767 |> cast(%{banner: banner}, [:banner])
1768 |> update_and_set_cache()
1771 def update_background(user, background) do
1773 |> cast(%{background: background}, [:background])
1774 |> update_and_set_cache()
1777 def update_source_data(user, source_data) do
1779 |> cast(%{source_data: source_data}, [:source_data])
1780 |> update_and_set_cache()
1783 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1786 moderator: is_moderator
1790 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1791 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1792 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1793 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1796 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1797 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1801 def fields(%{fields: nil}), do: []
1803 def fields(%{fields: fields}), do: fields
1805 def validate_fields(changeset, remote? \\ false) do
1806 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1807 limit = Pleroma.Config.get([:instance, limit_name], 0)
1810 |> validate_length(:fields, max: limit)
1811 |> validate_change(:fields, fn :fields, fields ->
1812 if Enum.all?(fields, &valid_field?/1) do
1820 defp valid_field?(%{"name" => name, "value" => value}) do
1821 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1822 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1824 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1825 String.length(value) <= value_limit
1828 defp valid_field?(_), do: false
1830 defp truncate_field(%{"name" => name, "value" => value}) do
1832 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1835 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1837 %{"name" => name, "value" => value}
1840 def admin_api_update(user, params) do
1847 |> update_and_set_cache()
1850 def mascot_update(user, url) do
1852 |> cast(%{mascot: url}, [:mascot])
1853 |> validate_required([:mascot])
1854 |> update_and_set_cache()
1857 def mastodon_settings_update(user, settings) do
1859 |> cast(%{settings: settings}, [:settings])
1860 |> validate_required([:settings])
1861 |> update_and_set_cache()
1864 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1865 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1867 if need_confirmation? do
1869 confirmation_pending: true,
1870 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1874 confirmation_pending: false,
1875 confirmation_token: nil
1879 cast(user, params, [:confirmation_pending, :confirmation_token])
1882 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1883 if id not in user.pinned_activities do
1884 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1885 params = %{pinned_activities: user.pinned_activities ++ [id]}
1888 |> cast(params, [:pinned_activities])
1889 |> validate_length(:pinned_activities,
1890 max: max_pinned_statuses,
1891 message: "You have already pinned the maximum number of statuses"
1896 |> update_and_set_cache()
1899 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1900 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1903 |> cast(params, [:pinned_activities])
1904 |> update_and_set_cache()
1907 def update_email_notifications(user, settings) do
1908 email_notifications =
1909 user.email_notifications
1910 |> Map.merge(settings)
1911 |> Map.take(["digest"])
1913 params = %{email_notifications: email_notifications}
1914 fields = [:email_notifications]
1917 |> cast(params, fields)
1918 |> validate_required(fields)
1919 |> update_and_set_cache()
1922 defp set_subscribers(user, subscribers) do
1923 params = %{subscribers: subscribers}
1926 |> cast(params, [:subscribers])
1927 |> validate_required([:subscribers])
1928 |> update_and_set_cache()
1931 def add_to_subscribers(user, subscribed) do
1932 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1935 def remove_from_subscribers(user, subscribed) do
1936 set_subscribers(user, List.delete(user.subscribers, subscribed))
1939 defp set_domain_blocks(user, domain_blocks) do
1940 params = %{domain_blocks: domain_blocks}
1943 |> cast(params, [:domain_blocks])
1944 |> validate_required([:domain_blocks])
1945 |> update_and_set_cache()
1948 def block_domain(user, domain_blocked) do
1949 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1952 def unblock_domain(user, domain_blocked) do
1953 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1956 defp set_blocks(user, blocks) do
1957 params = %{blocks: blocks}
1960 |> cast(params, [:blocks])
1961 |> validate_required([:blocks])
1962 |> update_and_set_cache()
1965 def add_to_block(user, blocked) do
1966 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1969 def remove_from_block(user, blocked) do
1970 set_blocks(user, List.delete(user.blocks, blocked))
1973 defp set_mutes(user, mutes) do
1974 params = %{mutes: mutes}
1977 |> cast(params, [:mutes])
1978 |> validate_required([:mutes])
1979 |> update_and_set_cache()
1982 def add_to_mutes(user, muted, notifications?) do
1983 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1984 set_notification_mutes(
1986 Enum.uniq([muted | user.muted_notifications]),
1992 def remove_from_mutes(user, muted) do
1993 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1994 set_notification_mutes(
1996 List.delete(user.muted_notifications, muted),
2002 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
2006 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
2007 params = %{muted_notifications: muted_notifications}
2010 |> cast(params, [:muted_notifications])
2011 |> validate_required([:muted_notifications])
2012 |> update_and_set_cache()
2015 def add_reblog_mute(user, ap_id) do
2016 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
2019 |> cast(params, [:muted_reblogs])
2020 |> update_and_set_cache()
2023 def remove_reblog_mute(user, ap_id) do
2024 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
2027 |> cast(params, [:muted_reblogs])
2028 |> update_and_set_cache()