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(: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: nil)
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 def following_count(%User{following: []}), do: 0
221 def following_count(%User{} = user) do
223 |> get_friends_query()
224 |> Repo.aggregate(:count, :id)
235 :confirmation_pending,
236 :password_reset_pending,
243 :muted_notifications,
254 :hide_followers_count,
259 :unread_conversation_count,
261 :email_notifications,
264 :pleroma_settings_store,
269 :skip_thread_containment,
270 :notification_settings
273 def info_fields, do: @info_fields
275 defp truncate_fields_param(params) do
276 if Map.has_key?(params, :fields) do
277 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
283 defp truncate_if_exists(params, key, max_length) do
284 if Map.has_key?(params, key) and is_binary(params[key]) do
285 {value, _chopped} = String.split_at(params[key], max_length)
286 Map.put(params, key, value)
292 def remote_user_creation(params) do
293 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
294 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
298 |> Map.put(:info, params[:info] || %{})
299 |> truncate_if_exists(:name, name_limit)
300 |> truncate_if_exists(:bio, bio_limit)
301 |> truncate_fields_param()
321 :hide_followers_count,
330 |> validate_required([:name, :ap_id])
331 |> unique_constraint(:nickname)
332 |> validate_format(:nickname, @email_regex)
333 |> validate_length(:bio, max: bio_limit)
334 |> validate_length(:name, max: name_limit)
335 |> validate_fields(true)
337 case params[:source_data] do
338 %{"followers" => followers, "following" => following} ->
340 |> put_change(:follower_address, followers)
341 |> put_change(:following_address, following)
344 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
345 put_change(changeset, :follower_address, followers)
349 def update_changeset(struct, params \\ %{}) do
350 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
351 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
367 :hide_followers_count,
372 :skip_thread_containment,
375 :pleroma_settings_store,
379 |> unique_constraint(:nickname)
380 |> validate_format(:nickname, local_nickname_regex())
381 |> validate_length(:bio, max: bio_limit)
382 |> validate_length(:name, min: 1, max: name_limit)
383 |> validate_fields(false)
386 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
387 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
388 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
390 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
392 params = if remote?, do: truncate_fields_param(params), else: params
415 :hide_followers_count,
419 |> unique_constraint(:nickname)
420 |> validate_format(:nickname, local_nickname_regex())
421 |> validate_length(:bio, max: bio_limit)
422 |> validate_length(:name, max: name_limit)
423 |> validate_fields(remote?)
426 def password_update_changeset(struct, params) do
428 |> cast(params, [:password, :password_confirmation])
429 |> validate_required([:password, :password_confirmation])
430 |> validate_confirmation(:password)
431 |> put_password_hash()
432 |> put_change(:password_reset_pending, false)
435 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
436 def reset_password(%User{id: user_id} = user, data) do
439 |> Multi.update(:user, password_update_changeset(user, data))
440 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
441 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
443 case Repo.transaction(multi) do
444 {:ok, %{user: user} = _} -> set_cache(user)
445 {:error, _, changeset, _} -> {:error, changeset}
449 def update_password_reset_pending(user, value) do
452 |> put_change(:password_reset_pending, value)
453 |> update_and_set_cache()
456 def force_password_reset_async(user) do
457 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
460 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
461 def force_password_reset(user), do: update_password_reset_pending(user, true)
463 def register_changeset(struct, params \\ %{}, opts \\ []) do
464 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
465 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
468 if is_nil(opts[:need_confirmation]) do
469 Pleroma.Config.get([:instance, :account_activation_required])
471 opts[:need_confirmation]
475 |> confirmation_changeset(need_confirmation: need_confirmation?)
476 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
477 |> validate_required([:name, :nickname, :password, :password_confirmation])
478 |> validate_confirmation(:password)
479 |> unique_constraint(:email)
480 |> unique_constraint(:nickname)
481 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
482 |> validate_format(:nickname, local_nickname_regex())
483 |> validate_format(:email, @email_regex)
484 |> validate_length(:bio, max: bio_limit)
485 |> validate_length(:name, min: 1, max: name_limit)
486 |> maybe_validate_required_email(opts[:external])
489 |> unique_constraint(:ap_id)
490 |> put_following_and_follower_address()
493 def maybe_validate_required_email(changeset, true), do: changeset
494 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
496 defp put_ap_id(changeset) do
497 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
498 put_change(changeset, :ap_id, ap_id)
501 defp put_following_and_follower_address(changeset) do
502 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
505 |> put_change(:following, [followers])
506 |> put_change(:follower_address, followers)
509 defp autofollow_users(user) do
510 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
513 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
516 follow_all(user, autofollowed_users)
519 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
520 def register(%Ecto.Changeset{} = changeset) do
521 with {:ok, user} <- Repo.insert(changeset) do
522 post_register_action(user)
526 def post_register_action(%User{} = user) do
527 with {:ok, user} <- autofollow_users(user),
528 {:ok, user} <- set_cache(user),
529 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
530 {:ok, _} <- try_send_confirmation_email(user) do
535 def try_send_confirmation_email(%User{} = user) do
536 if user.confirmation_pending &&
537 Pleroma.Config.get([:instance, :account_activation_required]) do
539 |> Pleroma.Emails.UserEmail.account_confirmation_email()
540 |> Pleroma.Emails.Mailer.deliver_async()
548 def needs_update?(%User{local: true}), do: false
550 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
552 def needs_update?(%User{local: false} = user) do
553 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
556 def needs_update?(_), do: true
558 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
559 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true}) do
563 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
564 follow(follower, followed)
567 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
568 if not ap_enabled?(followed) do
569 follow(follower, followed)
575 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
576 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
577 def follow_all(follower, followeds) do
580 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
581 |> Enum.map(fn %{follower_address: fa} -> fa end)
585 where: u.id == ^follower.id,
590 "array(select distinct unnest (array_cat(?, ?)))",
599 {1, [follower]} = Repo.update_all(q, [])
601 Enum.each(followeds, &update_follower_count/1)
606 def follow(%User{} = follower, %User{} = followed) do
607 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
608 ap_followers = followed.follower_address
611 followed.deactivated ->
612 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
614 deny_follow_blocked and blocks?(followed, follower) ->
615 {:error, "Could not follow user: #{followed.nickname} blocked you."}
620 where: u.id == ^follower.id,
621 update: [push: [following: ^ap_followers]],
625 {1, [follower]} = Repo.update_all(q, [])
627 follower = maybe_update_following_count(follower)
629 {:ok, _} = update_follower_count(followed)
635 def unfollow(%User{} = follower, %User{} = followed) do
636 ap_followers = followed.follower_address
638 if following?(follower, followed) and follower.ap_id != followed.ap_id do
641 where: u.id == ^follower.id,
642 update: [pull: [following: ^ap_followers]],
646 {1, [follower]} = Repo.update_all(q, [])
648 follower = maybe_update_following_count(follower)
650 {:ok, followed} = update_follower_count(followed)
654 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
656 {:error, "Not subscribed!"}
660 @spec following?(User.t(), User.t()) :: boolean
661 def following?(%User{} = follower, %User{} = followed) do
662 Enum.member?(follower.following, followed.follower_address)
665 def locked?(%User{} = user) do
670 Repo.get_by(User, id: id)
673 def get_by_ap_id(ap_id) do
674 Repo.get_by(User, ap_id: ap_id)
677 def get_all_by_ap_id(ap_ids) do
678 from(u in __MODULE__,
679 where: u.ap_id in ^ap_ids
684 def get_all_by_ids(ids) do
685 from(u in __MODULE__, where: u.id in ^ids)
689 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
690 # of the ap_id and the domain and tries to get that user
691 def get_by_guessed_nickname(ap_id) do
692 domain = URI.parse(ap_id).host
693 name = List.last(String.split(ap_id, "/"))
694 nickname = "#{name}@#{domain}"
696 get_cached_by_nickname(nickname)
699 def set_cache({:ok, user}), do: set_cache(user)
700 def set_cache({:error, err}), do: {:error, err}
702 def set_cache(%User{} = user) do
703 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
704 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
705 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
709 def update_and_set_cache(struct, params) do
711 |> update_changeset(params)
712 |> update_and_set_cache()
715 def update_and_set_cache(changeset) do
716 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
721 def invalidate_cache(user) do
722 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
723 Cachex.del(:user_cache, "nickname:#{user.nickname}")
724 Cachex.del(:user_cache, "user_info:#{user.id}")
727 def get_cached_by_ap_id(ap_id) do
728 key = "ap_id:#{ap_id}"
729 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
732 def get_cached_by_id(id) do
736 Cachex.fetch!(:user_cache, key, fn _ ->
740 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
741 {:commit, user.ap_id}
747 get_cached_by_ap_id(ap_id)
750 def get_cached_by_nickname(nickname) do
751 key = "nickname:#{nickname}"
753 Cachex.fetch!(:user_cache, key, fn ->
754 case get_or_fetch_by_nickname(nickname) do
755 {:ok, user} -> {:commit, user}
756 {:error, _error} -> {:ignore, nil}
761 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
762 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
765 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
766 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
768 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
769 get_cached_by_nickname(nickname_or_id)
771 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
772 get_cached_by_nickname(nickname_or_id)
779 def get_by_nickname(nickname) do
780 Repo.get_by(User, nickname: nickname) ||
781 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
782 Repo.get_by(User, nickname: local_nickname(nickname))
786 def get_by_email(email), do: Repo.get_by(User, email: email)
788 def get_by_nickname_or_email(nickname_or_email) do
789 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
792 def get_cached_user_info(user) do
793 key = "user_info:#{user.id}"
794 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
797 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
799 def get_or_fetch_by_nickname(nickname) do
800 with %User{} = user <- get_by_nickname(nickname) do
804 with [_nick, _domain] <- String.split(nickname, "@"),
805 {:ok, user} <- fetch_by_nickname(nickname) do
806 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
807 fetch_initial_posts(user)
812 _e -> {:error, "not found " <> nickname}
817 @doc "Fetch some posts when the user has just been federated with"
818 def fetch_initial_posts(user) do
819 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
822 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
823 def get_followers_query(%User{} = user, nil) do
824 User.Query.build(%{followers: user, deactivated: false})
827 def get_followers_query(user, page) do
829 |> get_followers_query(nil)
830 |> User.Query.paginate(page, 20)
833 @spec get_followers_query(User.t()) :: Ecto.Query.t()
834 def get_followers_query(user), do: get_followers_query(user, nil)
836 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
837 def get_followers(user, page \\ nil) do
839 |> get_followers_query(page)
843 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
844 def get_external_followers(user, page \\ nil) do
846 |> get_followers_query(page)
847 |> User.Query.build(%{external: true})
851 def get_followers_ids(user, page \\ nil) do
853 |> get_followers_query(page)
858 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
859 def get_friends_query(%User{} = user, nil) do
860 User.Query.build(%{friends: user, deactivated: false})
863 def get_friends_query(user, page) do
865 |> get_friends_query(nil)
866 |> User.Query.paginate(page, 20)
869 @spec get_friends_query(User.t()) :: Ecto.Query.t()
870 def get_friends_query(user), do: get_friends_query(user, nil)
872 def get_friends(user, page \\ nil) do
874 |> get_friends_query(page)
878 def get_friends_ids(user, page \\ nil) do
880 |> get_friends_query(page)
885 @spec get_follow_requests(User.t()) :: {:ok, [User.t()]}
886 def get_follow_requests(%User{} = user) do
888 |> Activity.follow_requests_for_actor()
889 |> join(:inner, [a], u in User, on: a.actor == u.ap_id)
890 |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
891 |> group_by([a, u], u.id)
896 def increase_note_count(%User{} = user) do
898 |> where(id: ^user.id)
899 |> update([u], inc: [note_count: 1])
901 |> Repo.update_all([])
903 {1, [user]} -> set_cache(user)
908 def decrease_note_count(%User{} = user) do
910 |> where(id: ^user.id)
913 note_count: fragment("greatest(0, note_count - 1)")
917 |> Repo.update_all([])
919 {1, [user]} -> set_cache(user)
924 def update_note_count(%User{} = user, note_count \\ nil) do
929 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
935 |> cast(%{note_count: note_count}, [:note_count])
936 |> update_and_set_cache()
939 @spec maybe_fetch_follow_information(User.t()) :: User.t()
940 def maybe_fetch_follow_information(user) do
941 with {:ok, user} <- fetch_follow_information(user) do
945 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
951 def fetch_follow_information(user) do
952 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
954 |> follow_information_changeset(info)
955 |> update_and_set_cache()
959 defp follow_information_changeset(user, params) do
966 :hide_followers_count,
971 def update_follower_count(%User{} = user) do
972 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
973 follower_count_query =
974 User.Query.build(%{followers: user, deactivated: false})
975 |> select([u], %{count: count(u.id)})
978 |> where(id: ^user.id)
979 |> join(:inner, [u], s in subquery(follower_count_query))
981 set: [follower_count: s.count]
984 |> Repo.update_all([])
986 {1, [user]} -> set_cache(user)
990 {:ok, maybe_fetch_follow_information(user)}
994 @spec maybe_update_following_count(User.t()) :: User.t()
995 def maybe_update_following_count(%User{local: false} = user) do
996 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
997 maybe_fetch_follow_information(user)
1003 def maybe_update_following_count(user), do: user
1005 def set_unread_conversation_count(%User{local: true} = user) do
1006 unread_query = Participation.unread_conversation_count_for_user(user)
1009 |> join(:inner, [u], p in subquery(unread_query))
1011 set: [unread_conversation_count: p.count]
1013 |> where([u], u.id == ^user.id)
1015 |> Repo.update_all([])
1017 {1, [user]} -> set_cache(user)
1022 def set_unread_conversation_count(_), do: :noop
1024 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1026 Participation.unread_conversation_count_for_user(user)
1027 |> where([p], p.conversation_id == ^conversation.id)
1030 |> join(:inner, [u], p in subquery(unread_query))
1032 inc: [unread_conversation_count: 1]
1034 |> where([u], u.id == ^user.id)
1035 |> where([u, p], p.count == 0)
1037 |> Repo.update_all([])
1039 {1, [user]} -> set_cache(user)
1044 def increment_unread_conversation_count(_, _), do: :noop
1046 def remove_duplicated_following(%User{following: following} = user) do
1047 uniq_following = Enum.uniq(following)
1049 if length(following) == length(uniq_following) do
1053 |> update_changeset(%{following: uniq_following})
1054 |> update_and_set_cache()
1058 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1059 def get_users_from_set(ap_ids, local_only \\ true) do
1060 criteria = %{ap_id: ap_ids, deactivated: false}
1061 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1063 User.Query.build(criteria)
1067 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1068 def get_recipients_from_activity(%Activity{recipients: to}) do
1069 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1073 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
1074 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
1075 add_to_mutes(muter, ap_id, notifications?)
1078 def unmute(muter, %{ap_id: ap_id}) do
1079 remove_from_mutes(muter, ap_id)
1082 def subscribe(subscriber, %{ap_id: ap_id}) do
1083 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
1084 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1086 if blocks?(subscribed, subscriber) and deny_follow_blocked do
1087 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
1089 User.add_to_subscribers(subscribed, subscriber.ap_id)
1094 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
1095 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1096 User.remove_from_subscribers(user, unsubscriber.ap_id)
1100 def block(blocker, %User{ap_id: ap_id} = blocked) do
1101 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1103 if following?(blocker, blocked) do
1104 {:ok, blocker, _} = unfollow(blocker, blocked)
1110 # clear any requested follows as well
1112 case CommonAPI.reject_follow_request(blocked, blocker) do
1113 {:ok, %User{} = updated_blocked} -> updated_blocked
1118 if subscribed_to?(blocked, blocker) do
1119 {:ok, blocker} = unsubscribe(blocked, blocker)
1125 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1127 {:ok, blocker} = update_follower_count(blocker)
1129 add_to_block(blocker, ap_id)
1132 # helper to handle the block given only an actor's AP id
1133 def block(blocker, %{ap_id: ap_id}) do
1134 block(blocker, get_cached_by_ap_id(ap_id))
1137 def unblock(blocker, %{ap_id: ap_id}) do
1138 remove_from_block(blocker, ap_id)
1141 def mutes?(nil, _), do: false
1142 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1144 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1145 def muted_notifications?(nil, _), do: false
1147 def muted_notifications?(user, %{ap_id: ap_id}),
1148 do: Enum.member?(user.muted_notifications, ap_id)
1150 def blocks?(%User{} = user, %User{} = target) do
1151 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1154 def blocks?(nil, _), do: false
1156 def blocks_ap_id?(%User{} = user, %User{} = target) do
1157 Enum.member?(user.blocks, target.ap_id)
1160 def blocks_ap_id?(_, _), do: false
1162 def blocks_domain?(%User{} = user, %User{} = target) do
1163 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1164 %{host: host} = URI.parse(target.ap_id)
1165 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1168 def blocks_domain?(_, _), do: false
1170 def subscribed_to?(user, %{ap_id: ap_id}) do
1171 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1172 Enum.member?(target.subscribers, user.ap_id)
1176 @spec muted_users(User.t()) :: [User.t()]
1177 def muted_users(user) do
1178 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1182 @spec blocked_users(User.t()) :: [User.t()]
1183 def blocked_users(user) do
1184 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1188 @spec subscribers(User.t()) :: [User.t()]
1189 def subscribers(user) do
1190 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1194 def deactivate_async(user, status \\ true) do
1195 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1198 def deactivate(user, status \\ true)
1200 def deactivate(users, status) when is_list(users) do
1201 Repo.transaction(fn ->
1202 for user <- users, do: deactivate(user, status)
1206 def deactivate(%User{} = user, status) do
1207 with {:ok, user} <- set_activation_status(user, status) do
1208 Enum.each(get_followers(user), &invalidate_cache/1)
1209 Enum.each(get_friends(user), &update_follower_count/1)
1215 def update_notification_settings(%User{} = user, settings) do
1218 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1221 notification_settings =
1222 user.notification_settings
1223 |> Map.merge(settings)
1224 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1226 params = %{notification_settings: notification_settings}
1229 |> cast(params, [:notification_settings])
1230 |> validate_required([:notification_settings])
1231 |> update_and_set_cache()
1234 def delete(users) when is_list(users) do
1235 for user <- users, do: delete(user)
1238 def delete(%User{} = user) do
1239 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1242 def perform(:force_password_reset, user), do: force_password_reset(user)
1244 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1245 def perform(:delete, %User{} = user) do
1246 {:ok, _user} = ActivityPub.delete(user)
1248 # Remove all relationships
1251 |> Enum.each(fn follower ->
1252 ActivityPub.unfollow(follower, user)
1253 unfollow(follower, user)
1258 |> Enum.each(fn followed ->
1259 ActivityPub.unfollow(user, followed)
1260 unfollow(user, followed)
1263 delete_user_activities(user)
1264 invalidate_cache(user)
1268 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1269 def perform(:fetch_initial_posts, %User{} = user) do
1270 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1272 # Insert all the posts in reverse order, so they're in the right order on the timeline
1273 user.source_data["outbox"]
1274 |> Utils.fetch_ordered_collection(pages)
1276 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1279 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1281 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1282 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1283 when is_list(blocked_identifiers) do
1285 blocked_identifiers,
1286 fn blocked_identifier ->
1287 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1288 {:ok, blocker} <- block(blocker, blocked),
1289 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1293 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1300 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1301 def perform(:follow_import, %User{} = follower, followed_identifiers)
1302 when is_list(followed_identifiers) do
1304 followed_identifiers,
1305 fn followed_identifier ->
1306 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1307 {:ok, follower} <- maybe_direct_follow(follower, followed),
1308 {:ok, _} <- ActivityPub.follow(follower, followed) do
1312 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1319 @spec external_users_query() :: Ecto.Query.t()
1320 def external_users_query do
1328 @spec external_users(keyword()) :: [User.t()]
1329 def external_users(opts \\ []) do
1331 external_users_query()
1332 |> select([u], struct(u, [:id, :ap_id, :info]))
1336 do: where(query, [u], u.id > ^opts[:max_id]),
1341 do: limit(query, ^opts[:limit]),
1347 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1348 BackgroundWorker.enqueue("blocks_import", %{
1349 "blocker_id" => blocker.id,
1350 "blocked_identifiers" => blocked_identifiers
1354 def follow_import(%User{} = follower, followed_identifiers)
1355 when is_list(followed_identifiers) do
1356 BackgroundWorker.enqueue("follow_import", %{
1357 "follower_id" => follower.id,
1358 "followed_identifiers" => followed_identifiers
1362 def delete_user_activities(%User{ap_id: ap_id}) do
1364 |> Activity.Queries.by_actor()
1365 |> RepoStreamer.chunk_stream(50)
1366 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1370 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1372 |> Object.normalize()
1373 |> ActivityPub.delete()
1376 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1377 object = Object.normalize(activity)
1380 |> get_cached_by_ap_id()
1381 |> ActivityPub.unlike(object)
1384 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1385 object = Object.normalize(activity)
1388 |> get_cached_by_ap_id()
1389 |> ActivityPub.unannounce(object)
1392 defp delete_activity(_activity), do: "Doing nothing"
1394 def html_filter_policy(%User{no_rich_text: true}) do
1395 Pleroma.HTML.Scrubber.TwitterText
1398 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1400 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1402 def get_or_fetch_by_ap_id(ap_id) do
1403 user = get_cached_by_ap_id(ap_id)
1405 if !is_nil(user) and !needs_update?(user) do
1408 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1409 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1411 resp = fetch_by_ap_id(ap_id)
1413 if should_fetch_initial do
1414 with {:ok, %User{} = user} <- resp do
1415 fetch_initial_posts(user)
1423 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1424 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1425 with %User{} = user <- get_cached_by_ap_id(uri) do
1431 |> cast(%{}, [:ap_id, :nickname, :local])
1432 |> put_change(:ap_id, uri)
1433 |> put_change(:nickname, nickname)
1434 |> put_change(:local, true)
1435 |> put_change(:follower_address, uri <> "/followers")
1443 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1446 |> :public_key.pem_decode()
1448 |> :public_key.pem_entry_decode()
1453 def public_key(_), do: {:error, "not found key"}
1455 def get_public_key_for_ap_id(ap_id) do
1456 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1457 {:ok, public_key} <- public_key(user) do
1464 defp blank?(""), do: nil
1465 defp blank?(n), do: n
1467 def insert_or_update_user(data) do
1469 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1470 |> remote_user_creation()
1471 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1475 def ap_enabled?(%User{local: true}), do: true
1476 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1477 def ap_enabled?(_), do: false
1479 @doc "Gets or fetch a user by uri or nickname."
1480 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1481 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1482 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1484 # wait a period of time and return newest version of the User structs
1485 # this is because we have synchronous follow APIs and need to simulate them
1486 # with an async handshake
1487 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1488 with %User{} = a <- get_cached_by_id(a.id),
1489 %User{} = b <- get_cached_by_id(b.id) do
1496 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1497 with :ok <- :timer.sleep(timeout),
1498 %User{} = a <- get_cached_by_id(a.id),
1499 %User{} = b <- get_cached_by_id(b.id) do
1506 def parse_bio(bio) when is_binary(bio) and bio != "" do
1508 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1512 def parse_bio(_), do: ""
1514 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1515 # TODO: get profile URLs other than user.ap_id
1516 profile_urls = [user.ap_id]
1519 |> CommonUtils.format_input("text/plain",
1520 mentions_format: :full,
1521 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1526 def parse_bio(_, _), do: ""
1528 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1529 Repo.transaction(fn ->
1530 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1534 def tag(nickname, tags) when is_binary(nickname),
1535 do: tag(get_by_nickname(nickname), tags)
1537 def tag(%User{} = user, tags),
1538 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1540 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1541 Repo.transaction(fn ->
1542 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1546 def untag(nickname, tags) when is_binary(nickname),
1547 do: untag(get_by_nickname(nickname), tags)
1549 def untag(%User{} = user, tags),
1550 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1552 defp update_tags(%User{} = user, new_tags) do
1553 {:ok, updated_user} =
1555 |> change(%{tags: new_tags})
1556 |> update_and_set_cache()
1561 defp normalize_tags(tags) do
1564 |> Enum.map(&String.downcase/1)
1567 defp local_nickname_regex do
1568 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1569 @extended_local_nickname_regex
1571 @strict_local_nickname_regex
1575 def local_nickname(nickname_or_mention) do
1578 |> String.split("@")
1582 def full_nickname(nickname_or_mention),
1583 do: String.trim_leading(nickname_or_mention, "@")
1585 def error_user(ap_id) do
1589 nickname: "erroruser@example.com",
1590 inserted_at: NaiveDateTime.utc_now()
1594 @spec all_superusers() :: [User.t()]
1595 def all_superusers do
1596 User.Query.build(%{super_users: true, local: true, deactivated: false})
1600 def showing_reblogs?(%User{} = user, %User{} = target) do
1601 target.ap_id not in user.muted_reblogs
1605 The function returns a query to get users with no activity for given interval of days.
1606 Inactive users are those who didn't read any notification, or had any activity where
1607 the user is the activity's actor, during `inactivity_threshold` days.
1608 Deactivated users will not appear in this list.
1612 iex> Pleroma.User.list_inactive_users()
1615 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1616 def list_inactive_users_query(inactivity_threshold \\ 7) do
1617 negative_inactivity_threshold = -inactivity_threshold
1618 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1619 # Subqueries are not supported in `where` clauses, join gets too complicated.
1620 has_read_notifications =
1621 from(n in Pleroma.Notification,
1622 where: n.seen == true,
1624 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1627 |> Pleroma.Repo.all()
1629 from(u in Pleroma.User,
1630 left_join: a in Pleroma.Activity,
1631 on: u.ap_id == a.actor,
1632 where: not is_nil(u.nickname),
1633 where: u.deactivated != ^true,
1634 where: u.id not in ^has_read_notifications,
1637 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1638 is_nil(max(a.inserted_at))
1643 Enable or disable email notifications for user
1647 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1648 Pleroma.User{email_notifications: %{"digest" => true}}
1650 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1651 Pleroma.User{email_notifications: %{"digest" => false}}
1653 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1654 {:ok, t()} | {:error, Ecto.Changeset.t()}
1655 def switch_email_notifications(user, type, status) do
1656 User.update_email_notifications(user, %{type => status})
1660 Set `last_digest_emailed_at` value for the user to current time
1662 @spec touch_last_digest_emailed_at(t()) :: t()
1663 def touch_last_digest_emailed_at(user) do
1664 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1666 {:ok, updated_user} =
1668 |> change(%{last_digest_emailed_at: now})
1669 |> update_and_set_cache()
1674 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1675 def toggle_confirmation(%User{} = user) do
1677 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1678 |> update_and_set_cache()
1681 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1685 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1686 # use instance-default
1687 config = Pleroma.Config.get([:assets, :mascots])
1688 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1689 mascot = Keyword.get(config, default_mascot)
1692 "id" => "default-mascot",
1693 "url" => mascot[:url],
1694 "preview_url" => mascot[:url],
1696 "mime_type" => mascot[:mime_type]
1701 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1703 def ensure_keys_present(%User{} = user) do
1704 with {:ok, pem} <- Keys.generate_rsa_pem() do
1706 |> cast(%{keys: pem}, [:keys])
1707 |> validate_required([:keys])
1708 |> update_and_set_cache()
1712 def get_ap_ids_by_nicknames(nicknames) do
1714 where: u.nickname in ^nicknames,
1720 defdelegate search(query, opts \\ []), to: User.Search
1722 defp put_password_hash(
1723 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1725 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1728 defp put_password_hash(changeset), do: changeset
1730 def is_internal_user?(%User{nickname: nil}), do: true
1731 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1732 def is_internal_user?(_), do: false
1734 # A hack because user delete activities have a fake id for whatever reason
1735 # TODO: Get rid of this
1736 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1738 def get_delivered_users_by_object_id(object_id) do
1740 inner_join: delivery in assoc(u, :deliveries),
1741 where: delivery.object_id == ^object_id
1746 def change_email(user, email) do
1748 |> cast(%{email: email}, [:email])
1749 |> validate_required([:email])
1750 |> unique_constraint(:email)
1751 |> validate_format(:email, @email_regex)
1752 |> update_and_set_cache()
1755 # Internal function; public one is `deactivate/2`
1756 defp set_activation_status(user, deactivated) do
1758 |> cast(%{deactivated: deactivated}, [:deactivated])
1759 |> update_and_set_cache()
1762 def update_banner(user, banner) do
1764 |> cast(%{banner: banner}, [:banner])
1765 |> update_and_set_cache()
1768 def update_background(user, background) do
1770 |> cast(%{background: background}, [:background])
1771 |> update_and_set_cache()
1774 def update_source_data(user, source_data) do
1776 |> cast(%{source_data: source_data}, [:source_data])
1777 |> update_and_set_cache()
1780 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1783 moderator: is_moderator
1787 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1788 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1789 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1790 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1793 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1794 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1798 def fields(%{fields: nil}), do: []
1800 def fields(%{fields: fields}), do: fields
1802 def validate_fields(changeset, remote? \\ false) do
1803 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1804 limit = Pleroma.Config.get([:instance, limit_name], 0)
1807 |> validate_length(:fields, max: limit)
1808 |> validate_change(:fields, fn :fields, fields ->
1809 if Enum.all?(fields, &valid_field?/1) do
1817 defp valid_field?(%{"name" => name, "value" => value}) do
1818 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1819 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1821 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1822 String.length(value) <= value_limit
1825 defp valid_field?(_), do: false
1827 defp truncate_field(%{"name" => name, "value" => value}) do
1829 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1832 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1834 %{"name" => name, "value" => value}
1837 def admin_api_update(user, params) do
1844 |> update_and_set_cache()
1847 def mascot_update(user, url) do
1849 |> cast(%{mascot: url}, [:mascot])
1850 |> validate_required([:mascot])
1851 |> update_and_set_cache()
1854 def mastodon_settings_update(user, settings) do
1856 |> cast(%{settings: settings}, [:settings])
1857 |> validate_required([:settings])
1858 |> update_and_set_cache()
1861 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1862 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1864 if need_confirmation? do
1866 confirmation_pending: true,
1867 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1871 confirmation_pending: false,
1872 confirmation_token: nil
1876 cast(user, params, [:confirmation_pending, :confirmation_token])
1879 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1880 if id not in user.pinned_activities do
1881 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1882 params = %{pinned_activities: user.pinned_activities ++ [id]}
1885 |> cast(params, [:pinned_activities])
1886 |> validate_length(:pinned_activities,
1887 max: max_pinned_statuses,
1888 message: "You have already pinned the maximum number of statuses"
1893 |> update_and_set_cache()
1896 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1897 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1900 |> cast(params, [:pinned_activities])
1901 |> update_and_set_cache()
1904 def update_email_notifications(user, settings) do
1905 email_notifications =
1906 user.email_notifications
1907 |> Map.merge(settings)
1908 |> Map.take(["digest"])
1910 params = %{email_notifications: email_notifications}
1911 fields = [:email_notifications]
1914 |> cast(params, fields)
1915 |> validate_required(fields)
1916 |> update_and_set_cache()
1919 defp set_subscribers(user, subscribers) do
1920 params = %{subscribers: subscribers}
1923 |> cast(params, [:subscribers])
1924 |> validate_required([:subscribers])
1925 |> update_and_set_cache()
1928 def add_to_subscribers(user, subscribed) do
1929 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1932 def remove_from_subscribers(user, subscribed) do
1933 set_subscribers(user, List.delete(user.subscribers, subscribed))
1936 defp set_domain_blocks(user, domain_blocks) do
1937 params = %{domain_blocks: domain_blocks}
1940 |> cast(params, [:domain_blocks])
1941 |> validate_required([:domain_blocks])
1942 |> update_and_set_cache()
1945 def block_domain(user, domain_blocked) do
1946 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1949 def unblock_domain(user, domain_blocked) do
1950 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1953 defp set_blocks(user, blocks) do
1954 params = %{blocks: blocks}
1957 |> cast(params, [:blocks])
1958 |> validate_required([:blocks])
1959 |> update_and_set_cache()
1962 def add_to_block(user, blocked) do
1963 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1966 def remove_from_block(user, blocked) do
1967 set_blocks(user, List.delete(user.blocks, blocked))
1970 defp set_mutes(user, mutes) do
1971 params = %{mutes: mutes}
1974 |> cast(params, [:mutes])
1975 |> validate_required([:mutes])
1976 |> update_and_set_cache()
1979 def add_to_mutes(user, muted, notifications?) do
1980 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1981 set_notification_mutes(
1983 Enum.uniq([muted | user.muted_notifications]),
1989 def remove_from_mutes(user, muted) do
1990 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1991 set_notification_mutes(
1993 List.delete(user.muted_notifications, muted),
1999 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
2003 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
2004 params = %{muted_notifications: muted_notifications}
2007 |> cast(params, [:muted_notifications])
2008 |> validate_required([:muted_notifications])
2009 |> update_and_set_cache()
2012 def add_reblog_mute(user, ap_id) do
2013 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
2016 |> cast(params, [:muted_reblogs])
2017 |> update_and_set_cache()
2020 def remove_reblog_mute(user, ap_id) do
2021 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
2024 |> cast(params, [:muted_reblogs])
2025 |> update_and_set_cache()
2028 def set_invisible(user, invisible) do
2029 params = %{invisible: invisible}
2032 |> cast(params, [:invisible])
2033 |> validate_required([:invisible])
2034 |> update_and_set_cache()