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(:skip_thread_containment, :boolean, default: false)
108 field(:notification_settings, :map,
112 "non_follows" => true,
113 "non_followers" => true
117 has_many(:notifications, Notification)
118 has_many(:registrations, Registration)
119 has_many(:deliveries, Delivery)
121 field(:info, :map, default: %{})
126 def auth_active?(%User{confirmation_pending: true}),
127 do: !Pleroma.Config.get([:instance, :account_activation_required])
129 def auth_active?(%User{}), do: true
131 def visible_for?(user, for_user \\ nil)
133 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
135 def visible_for?(%User{} = user, for_user) do
136 auth_active?(user) || superuser?(for_user)
139 def visible_for?(_, _), do: false
141 def superuser?(%User{local: true, is_admin: true}), do: true
142 def superuser?(%User{local: true, is_moderator: true}), do: true
143 def superuser?(_), do: false
145 def avatar_url(user, options \\ []) do
147 %{"url" => [%{"href" => href} | _]} -> href
148 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
152 def banner_url(user, options \\ []) do
154 %{"url" => [%{"href" => href} | _]} -> href
155 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
159 def profile_url(%User{source_data: %{"url" => url}}), do: url
160 def profile_url(%User{ap_id: ap_id}), do: ap_id
161 def profile_url(_), do: nil
163 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
165 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
166 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
168 @spec ap_following(User.t()) :: Sring.t()
169 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
170 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
172 def user_info(%User{} = user, args \\ %{}) do
174 Map.get(args, :following_count, user.following_count || following_count(user))
176 follower_count = Map.get(args, :follower_count, user.follower_count)
179 note_count: user.note_count,
181 confirmation_pending: user.confirmation_pending,
182 default_scope: user.default_scope
184 |> Map.put(:following_count, following_count)
185 |> Map.put(:follower_count, follower_count)
188 def follow_state(%User{} = user, %User{} = target) do
189 case Utils.fetch_latest_follow(user, target) do
190 %{data: %{"state" => state}} -> state
191 # Ideally this would be nil, but then Cachex does not commit the value
196 def get_cached_follow_state(user, target) do
197 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
198 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
201 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
202 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
203 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
206 def set_info_cache(user, args) do
207 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
210 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
211 def restrict_deactivated(query) do
212 from(u in query, where: u.deactivated != ^true)
215 def following_count(%User{following: []}), do: 0
217 def following_count(%User{} = user) do
219 |> get_friends_query()
220 |> Repo.aggregate(:count, :id)
231 :confirmation_pending,
232 :password_reset_pending,
239 :muted_notifications,
250 :hide_followers_count,
255 :unread_conversation_count,
257 :email_notifications,
260 :pleroma_settings_store,
264 :skip_thread_containment,
265 :notification_settings
268 def info_fields, do: @info_fields
270 defp truncate_fields_param(params) do
271 if Map.has_key?(params, :fields) do
272 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
278 defp truncate_if_exists(params, key, max_length) do
279 if Map.has_key?(params, key) and is_binary(params[key]) do
280 {value, _chopped} = String.split_at(params[key], max_length)
281 Map.put(params, key, value)
287 def remote_user_creation(params) do
288 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
289 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
293 |> Map.put(:info, params[:info] || %{})
294 |> truncate_if_exists(:name, name_limit)
295 |> truncate_if_exists(:bio, bio_limit)
296 |> truncate_fields_param()
316 :hide_followers_count,
324 |> validate_required([:name, :ap_id])
325 |> unique_constraint(:nickname)
326 |> validate_format(:nickname, @email_regex)
327 |> validate_length(:bio, max: bio_limit)
328 |> validate_length(:name, max: name_limit)
329 |> validate_fields(true)
331 case params[:source_data] do
332 %{"followers" => followers, "following" => following} ->
334 |> put_change(:follower_address, followers)
335 |> put_change(:following_address, following)
338 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
339 put_change(changeset, :follower_address, followers)
343 def update_changeset(struct, params \\ %{}) do
344 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
345 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
361 :hide_followers_count,
366 :skip_thread_containment,
369 :pleroma_settings_store,
373 |> unique_constraint(:nickname)
374 |> validate_format(:nickname, local_nickname_regex())
375 |> validate_length(:bio, max: bio_limit)
376 |> validate_length(:name, min: 1, max: name_limit)
377 |> validate_fields(false)
380 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
381 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
382 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
384 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
386 params = if remote?, do: truncate_fields_param(params), else: params
409 :hide_followers_count,
413 |> unique_constraint(:nickname)
414 |> validate_format(:nickname, local_nickname_regex())
415 |> validate_length(:bio, max: bio_limit)
416 |> validate_length(:name, max: name_limit)
417 |> validate_fields(remote?)
420 def password_update_changeset(struct, params) do
422 |> cast(params, [:password, :password_confirmation])
423 |> validate_required([:password, :password_confirmation])
424 |> validate_confirmation(:password)
425 |> put_password_hash()
426 |> put_change(:password_reset_pending, false)
429 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
430 def reset_password(%User{id: user_id} = user, data) do
433 |> Multi.update(:user, password_update_changeset(user, data))
434 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
435 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
437 case Repo.transaction(multi) do
438 {:ok, %{user: user} = _} -> set_cache(user)
439 {:error, _, changeset, _} -> {:error, changeset}
443 def update_password_reset_pending(user, value) do
446 |> put_change(:password_reset_pending, value)
447 |> update_and_set_cache()
450 def force_password_reset_async(user) do
451 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
454 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
455 def force_password_reset(user), do: update_password_reset_pending(user, true)
457 def register_changeset(struct, params \\ %{}, opts \\ []) do
458 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
459 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
462 if is_nil(opts[:need_confirmation]) do
463 Pleroma.Config.get([:instance, :account_activation_required])
465 opts[:need_confirmation]
469 |> confirmation_changeset(need_confirmation: need_confirmation?)
470 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
471 |> validate_required([:name, :nickname, :password, :password_confirmation])
472 |> validate_confirmation(:password)
473 |> unique_constraint(:email)
474 |> unique_constraint(:nickname)
475 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
476 |> validate_format(:nickname, local_nickname_regex())
477 |> validate_format(:email, @email_regex)
478 |> validate_length(:bio, max: bio_limit)
479 |> validate_length(:name, min: 1, max: name_limit)
480 |> maybe_validate_required_email(opts[:external])
483 |> unique_constraint(:ap_id)
484 |> put_following_and_follower_address()
487 def maybe_validate_required_email(changeset, true), do: changeset
488 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
490 defp put_ap_id(changeset) do
491 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
492 put_change(changeset, :ap_id, ap_id)
495 defp put_following_and_follower_address(changeset) do
496 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
499 |> put_change(:following, [followers])
500 |> put_change(:follower_address, followers)
503 defp autofollow_users(user) do
504 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
507 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
510 follow_all(user, autofollowed_users)
513 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
514 def register(%Ecto.Changeset{} = changeset) do
515 with {:ok, user} <- Repo.insert(changeset) do
516 post_register_action(user)
520 def post_register_action(%User{} = user) do
521 with {:ok, user} <- autofollow_users(user),
522 {:ok, user} <- set_cache(user),
523 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
524 {:ok, _} <- try_send_confirmation_email(user) do
529 def try_send_confirmation_email(%User{} = user) do
530 if user.confirmation_pending &&
531 Pleroma.Config.get([:instance, :account_activation_required]) do
533 |> Pleroma.Emails.UserEmail.account_confirmation_email()
534 |> Pleroma.Emails.Mailer.deliver_async()
542 def needs_update?(%User{local: true}), do: false
544 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
546 def needs_update?(%User{local: false} = user) do
547 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
550 def needs_update?(_), do: true
552 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
553 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true}) do
557 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
558 follow(follower, followed)
561 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
562 if not ap_enabled?(followed) do
563 follow(follower, followed)
569 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
570 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
571 def follow_all(follower, followeds) do
574 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
575 |> Enum.map(fn %{follower_address: fa} -> fa end)
579 where: u.id == ^follower.id,
584 "array(select distinct unnest (array_cat(?, ?)))",
593 {1, [follower]} = Repo.update_all(q, [])
595 Enum.each(followeds, &update_follower_count/1)
600 def follow(%User{} = follower, %User{} = followed) do
601 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
602 ap_followers = followed.follower_address
605 followed.deactivated ->
606 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
608 deny_follow_blocked and blocks?(followed, follower) ->
609 {:error, "Could not follow user: #{followed.nickname} blocked you."}
614 where: u.id == ^follower.id,
615 update: [push: [following: ^ap_followers]],
619 {1, [follower]} = Repo.update_all(q, [])
621 follower = maybe_update_following_count(follower)
623 {:ok, _} = update_follower_count(followed)
629 def unfollow(%User{} = follower, %User{} = followed) do
630 ap_followers = followed.follower_address
632 if following?(follower, followed) and follower.ap_id != followed.ap_id do
635 where: u.id == ^follower.id,
636 update: [pull: [following: ^ap_followers]],
640 {1, [follower]} = Repo.update_all(q, [])
642 follower = maybe_update_following_count(follower)
644 {:ok, followed} = update_follower_count(followed)
648 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
650 {:error, "Not subscribed!"}
654 @spec following?(User.t(), User.t()) :: boolean
655 def following?(%User{} = follower, %User{} = followed) do
656 Enum.member?(follower.following, followed.follower_address)
659 def locked?(%User{} = user) do
664 Repo.get_by(User, id: id)
667 def get_by_ap_id(ap_id) do
668 Repo.get_by(User, ap_id: ap_id)
671 def get_all_by_ap_id(ap_ids) do
672 from(u in __MODULE__,
673 where: u.ap_id in ^ap_ids
678 def get_all_by_ids(ids) do
679 from(u in __MODULE__, where: u.id in ^ids)
683 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
684 # of the ap_id and the domain and tries to get that user
685 def get_by_guessed_nickname(ap_id) do
686 domain = URI.parse(ap_id).host
687 name = List.last(String.split(ap_id, "/"))
688 nickname = "#{name}@#{domain}"
690 get_cached_by_nickname(nickname)
693 def set_cache({:ok, user}), do: set_cache(user)
694 def set_cache({:error, err}), do: {:error, err}
696 def set_cache(%User{} = user) do
697 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
698 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
699 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
703 def update_and_set_cache(struct, params) do
705 |> update_changeset(params)
706 |> update_and_set_cache()
709 def update_and_set_cache(changeset) do
710 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
715 def invalidate_cache(user) do
716 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
717 Cachex.del(:user_cache, "nickname:#{user.nickname}")
718 Cachex.del(:user_cache, "user_info:#{user.id}")
721 def get_cached_by_ap_id(ap_id) do
722 key = "ap_id:#{ap_id}"
723 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
726 def get_cached_by_id(id) do
730 Cachex.fetch!(:user_cache, key, fn _ ->
734 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
735 {:commit, user.ap_id}
741 get_cached_by_ap_id(ap_id)
744 def get_cached_by_nickname(nickname) do
745 key = "nickname:#{nickname}"
747 Cachex.fetch!(:user_cache, key, fn ->
748 case get_or_fetch_by_nickname(nickname) do
749 {:ok, user} -> {:commit, user}
750 {:error, _error} -> {:ignore, nil}
755 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
756 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
759 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
760 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
762 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
763 get_cached_by_nickname(nickname_or_id)
765 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
766 get_cached_by_nickname(nickname_or_id)
773 def get_by_nickname(nickname) do
774 Repo.get_by(User, nickname: nickname) ||
775 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
776 Repo.get_by(User, nickname: local_nickname(nickname))
780 def get_by_email(email), do: Repo.get_by(User, email: email)
782 def get_by_nickname_or_email(nickname_or_email) do
783 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
786 def get_cached_user_info(user) do
787 key = "user_info:#{user.id}"
788 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
791 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
793 def get_or_fetch_by_nickname(nickname) do
794 with %User{} = user <- get_by_nickname(nickname) do
798 with [_nick, _domain] <- String.split(nickname, "@"),
799 {:ok, user} <- fetch_by_nickname(nickname) do
800 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
801 fetch_initial_posts(user)
806 _e -> {:error, "not found " <> nickname}
811 @doc "Fetch some posts when the user has just been federated with"
812 def fetch_initial_posts(user) do
813 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
816 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
817 def get_followers_query(%User{} = user, nil) do
818 User.Query.build(%{followers: user, deactivated: false})
821 def get_followers_query(user, page) do
823 |> get_followers_query(nil)
824 |> User.Query.paginate(page, 20)
827 @spec get_followers_query(User.t()) :: Ecto.Query.t()
828 def get_followers_query(user), do: get_followers_query(user, nil)
830 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
831 def get_followers(user, page \\ nil) do
833 |> get_followers_query(page)
837 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
838 def get_external_followers(user, page \\ nil) do
840 |> get_followers_query(page)
841 |> User.Query.build(%{external: true})
845 def get_followers_ids(user, page \\ nil) do
847 |> get_followers_query(page)
852 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
853 def get_friends_query(%User{} = user, nil) do
854 User.Query.build(%{friends: user, deactivated: false})
857 def get_friends_query(user, page) do
859 |> get_friends_query(nil)
860 |> User.Query.paginate(page, 20)
863 @spec get_friends_query(User.t()) :: Ecto.Query.t()
864 def get_friends_query(user), do: get_friends_query(user, nil)
866 def get_friends(user, page \\ nil) do
868 |> get_friends_query(page)
872 def get_friends_ids(user, page \\ nil) do
874 |> get_friends_query(page)
879 @spec get_follow_requests(User.t()) :: {:ok, [User.t()]}
880 def get_follow_requests(%User{} = user) do
882 |> Activity.follow_requests_for_actor()
883 |> join(:inner, [a], u in User, on: a.actor == u.ap_id)
884 |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
885 |> group_by([a, u], u.id)
890 def increase_note_count(%User{} = user) do
892 |> where(id: ^user.id)
893 |> update([u], inc: [note_count: 1])
895 |> Repo.update_all([])
897 {1, [user]} -> set_cache(user)
902 def decrease_note_count(%User{} = user) do
904 |> where(id: ^user.id)
907 note_count: fragment("greatest(0, note_count - 1)")
911 |> Repo.update_all([])
913 {1, [user]} -> set_cache(user)
918 def update_note_count(%User{} = user, note_count \\ nil) do
923 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
929 |> cast(%{note_count: note_count}, [:note_count])
930 |> update_and_set_cache()
933 @spec maybe_fetch_follow_information(User.t()) :: User.t()
934 def maybe_fetch_follow_information(user) do
935 with {:ok, user} <- fetch_follow_information(user) do
939 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
945 def fetch_follow_information(user) do
946 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
948 |> follow_information_changeset(info)
949 |> update_and_set_cache()
953 defp follow_information_changeset(user, params) do
960 :hide_followers_count,
965 def update_follower_count(%User{} = user) do
966 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
967 follower_count_query =
968 User.Query.build(%{followers: user, deactivated: false})
969 |> select([u], %{count: count(u.id)})
972 |> where(id: ^user.id)
973 |> join(:inner, [u], s in subquery(follower_count_query))
975 set: [follower_count: s.count]
978 |> Repo.update_all([])
980 {1, [user]} -> set_cache(user)
984 {:ok, maybe_fetch_follow_information(user)}
988 @spec maybe_update_following_count(User.t()) :: User.t()
989 def maybe_update_following_count(%User{local: false} = user) do
990 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
991 maybe_fetch_follow_information(user)
997 def maybe_update_following_count(user), do: user
999 def set_unread_conversation_count(%User{local: true} = user) do
1000 unread_query = Participation.unread_conversation_count_for_user(user)
1003 |> join(:inner, [u], p in subquery(unread_query))
1005 set: [unread_conversation_count: p.count]
1007 |> where([u], u.id == ^user.id)
1009 |> Repo.update_all([])
1011 {1, [user]} -> set_cache(user)
1016 def set_unread_conversation_count(_), do: :noop
1018 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1020 Participation.unread_conversation_count_for_user(user)
1021 |> where([p], p.conversation_id == ^conversation.id)
1024 |> join(:inner, [u], p in subquery(unread_query))
1026 inc: [unread_conversation_count: 1]
1028 |> where([u], u.id == ^user.id)
1029 |> where([u, p], p.count == 0)
1031 |> Repo.update_all([])
1033 {1, [user]} -> set_cache(user)
1038 def increment_unread_conversation_count(_, _), do: :noop
1040 def remove_duplicated_following(%User{following: following} = user) do
1041 uniq_following = Enum.uniq(following)
1043 if length(following) == length(uniq_following) do
1047 |> update_changeset(%{following: uniq_following})
1048 |> update_and_set_cache()
1052 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1053 def get_users_from_set(ap_ids, local_only \\ true) do
1054 criteria = %{ap_id: ap_ids, deactivated: false}
1055 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1057 User.Query.build(criteria)
1061 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1062 def get_recipients_from_activity(%Activity{recipients: to}) do
1063 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1067 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
1068 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
1069 add_to_mutes(muter, ap_id, notifications?)
1072 def unmute(muter, %{ap_id: ap_id}) do
1073 remove_from_mutes(muter, ap_id)
1076 def subscribe(subscriber, %{ap_id: ap_id}) do
1077 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
1078 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1080 if blocks?(subscribed, subscriber) and deny_follow_blocked do
1081 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
1083 User.add_to_subscribers(subscribed, subscriber.ap_id)
1088 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
1089 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1090 User.remove_from_subscribers(user, unsubscriber.ap_id)
1094 def block(blocker, %User{ap_id: ap_id} = blocked) do
1095 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1097 if following?(blocker, blocked) do
1098 {:ok, blocker, _} = unfollow(blocker, blocked)
1104 # clear any requested follows as well
1106 case CommonAPI.reject_follow_request(blocked, blocker) do
1107 {:ok, %User{} = updated_blocked} -> updated_blocked
1112 if subscribed_to?(blocked, blocker) do
1113 {:ok, blocker} = unsubscribe(blocked, blocker)
1119 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1121 {:ok, blocker} = update_follower_count(blocker)
1123 add_to_block(blocker, ap_id)
1126 # helper to handle the block given only an actor's AP id
1127 def block(blocker, %{ap_id: ap_id}) do
1128 block(blocker, get_cached_by_ap_id(ap_id))
1131 def unblock(blocker, %{ap_id: ap_id}) do
1132 remove_from_block(blocker, ap_id)
1135 def mutes?(nil, _), do: false
1136 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1138 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1139 def muted_notifications?(nil, _), do: false
1141 def muted_notifications?(user, %{ap_id: ap_id}),
1142 do: Enum.member?(user.muted_notifications, ap_id)
1144 def blocks?(%User{} = user, %User{} = target) do
1145 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1148 def blocks?(nil, _), do: false
1150 def blocks_ap_id?(%User{} = user, %User{} = target) do
1151 Enum.member?(user.blocks, target.ap_id)
1154 def blocks_ap_id?(_, _), do: false
1156 def blocks_domain?(%User{} = user, %User{} = target) do
1157 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1158 %{host: host} = URI.parse(target.ap_id)
1159 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1162 def blocks_domain?(_, _), do: false
1164 def subscribed_to?(user, %{ap_id: ap_id}) do
1165 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1166 Enum.member?(target.subscribers, user.ap_id)
1170 @spec muted_users(User.t()) :: [User.t()]
1171 def muted_users(user) do
1172 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1176 @spec blocked_users(User.t()) :: [User.t()]
1177 def blocked_users(user) do
1178 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1182 @spec subscribers(User.t()) :: [User.t()]
1183 def subscribers(user) do
1184 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1188 def deactivate_async(user, status \\ true) do
1189 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1192 def deactivate(user, status \\ true)
1194 def deactivate(users, status) when is_list(users) do
1195 Repo.transaction(fn ->
1196 for user <- users, do: deactivate(user, status)
1200 def deactivate(%User{} = user, status) do
1201 with {:ok, user} <- set_activation_status(user, status) do
1202 Enum.each(get_followers(user), &invalidate_cache/1)
1203 Enum.each(get_friends(user), &update_follower_count/1)
1209 def update_notification_settings(%User{} = user, settings) do
1212 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1215 notification_settings =
1216 user.notification_settings
1217 |> Map.merge(settings)
1218 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1220 params = %{notification_settings: notification_settings}
1223 |> cast(params, [:notification_settings])
1224 |> validate_required([:notification_settings])
1225 |> update_and_set_cache()
1228 def delete(users) when is_list(users) do
1229 for user <- users, do: delete(user)
1232 def delete(%User{} = user) do
1233 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1236 def perform(:force_password_reset, user), do: force_password_reset(user)
1238 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1239 def perform(:delete, %User{} = user) do
1240 {:ok, _user} = ActivityPub.delete(user)
1242 # Remove all relationships
1245 |> Enum.each(fn follower ->
1246 ActivityPub.unfollow(follower, user)
1247 unfollow(follower, user)
1252 |> Enum.each(fn followed ->
1253 ActivityPub.unfollow(user, followed)
1254 unfollow(user, followed)
1257 delete_user_activities(user)
1258 invalidate_cache(user)
1262 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1263 def perform(:fetch_initial_posts, %User{} = user) do
1264 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1266 # Insert all the posts in reverse order, so they're in the right order on the timeline
1267 user.source_data["outbox"]
1268 |> Utils.fetch_ordered_collection(pages)
1270 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1273 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1275 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1276 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1277 when is_list(blocked_identifiers) do
1279 blocked_identifiers,
1280 fn blocked_identifier ->
1281 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1282 {:ok, blocker} <- block(blocker, blocked),
1283 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1287 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1294 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1295 def perform(:follow_import, %User{} = follower, followed_identifiers)
1296 when is_list(followed_identifiers) do
1298 followed_identifiers,
1299 fn followed_identifier ->
1300 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1301 {:ok, follower} <- maybe_direct_follow(follower, followed),
1302 {:ok, _} <- ActivityPub.follow(follower, followed) do
1306 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1313 @spec external_users_query() :: Ecto.Query.t()
1314 def external_users_query do
1322 @spec external_users(keyword()) :: [User.t()]
1323 def external_users(opts \\ []) do
1325 external_users_query()
1326 |> select([u], struct(u, [:id, :ap_id, :info]))
1330 do: where(query, [u], u.id > ^opts[:max_id]),
1335 do: limit(query, ^opts[:limit]),
1341 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1342 BackgroundWorker.enqueue("blocks_import", %{
1343 "blocker_id" => blocker.id,
1344 "blocked_identifiers" => blocked_identifiers
1348 def follow_import(%User{} = follower, followed_identifiers)
1349 when is_list(followed_identifiers) do
1350 BackgroundWorker.enqueue("follow_import", %{
1351 "follower_id" => follower.id,
1352 "followed_identifiers" => followed_identifiers
1356 def delete_user_activities(%User{ap_id: ap_id}) do
1358 |> Activity.Queries.by_actor()
1359 |> RepoStreamer.chunk_stream(50)
1360 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1364 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1366 |> Object.normalize()
1367 |> ActivityPub.delete()
1370 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1371 object = Object.normalize(activity)
1374 |> get_cached_by_ap_id()
1375 |> ActivityPub.unlike(object)
1378 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1379 object = Object.normalize(activity)
1382 |> get_cached_by_ap_id()
1383 |> ActivityPub.unannounce(object)
1386 defp delete_activity(_activity), do: "Doing nothing"
1388 def html_filter_policy(%User{no_rich_text: true}) do
1389 Pleroma.HTML.Scrubber.TwitterText
1392 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1394 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1396 def get_or_fetch_by_ap_id(ap_id) do
1397 user = get_cached_by_ap_id(ap_id)
1399 if !is_nil(user) and !needs_update?(user) do
1402 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1403 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1405 resp = fetch_by_ap_id(ap_id)
1407 if should_fetch_initial do
1408 with {:ok, %User{} = user} <- resp do
1409 fetch_initial_posts(user)
1417 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1418 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1419 with %User{} = user <- get_cached_by_ap_id(uri) do
1425 |> cast(%{}, [:ap_id, :nickname, :local])
1426 |> put_change(:ap_id, uri)
1427 |> put_change(:nickname, nickname)
1428 |> put_change(:local, true)
1429 |> put_change(:follower_address, uri <> "/followers")
1437 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1440 |> :public_key.pem_decode()
1442 |> :public_key.pem_entry_decode()
1447 def public_key(_), do: {:error, "not found key"}
1449 def get_public_key_for_ap_id(ap_id) do
1450 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1451 {:ok, public_key} <- public_key(user) do
1458 defp blank?(""), do: nil
1459 defp blank?(n), do: n
1461 def insert_or_update_user(data) do
1463 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1464 |> remote_user_creation()
1465 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1469 def ap_enabled?(%User{local: true}), do: true
1470 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1471 def ap_enabled?(_), do: false
1473 @doc "Gets or fetch a user by uri or nickname."
1474 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1475 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1476 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1478 # wait a period of time and return newest version of the User structs
1479 # this is because we have synchronous follow APIs and need to simulate them
1480 # with an async handshake
1481 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1482 with %User{} = a <- get_cached_by_id(a.id),
1483 %User{} = b <- get_cached_by_id(b.id) do
1490 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1491 with :ok <- :timer.sleep(timeout),
1492 %User{} = a <- get_cached_by_id(a.id),
1493 %User{} = b <- get_cached_by_id(b.id) do
1500 def parse_bio(bio) when is_binary(bio) and bio != "" do
1502 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1506 def parse_bio(_), do: ""
1508 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1509 # TODO: get profile URLs other than user.ap_id
1510 profile_urls = [user.ap_id]
1513 |> CommonUtils.format_input("text/plain",
1514 mentions_format: :full,
1515 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1520 def parse_bio(_, _), do: ""
1522 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1523 Repo.transaction(fn ->
1524 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1528 def tag(nickname, tags) when is_binary(nickname),
1529 do: tag(get_by_nickname(nickname), tags)
1531 def tag(%User{} = user, tags),
1532 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1534 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1535 Repo.transaction(fn ->
1536 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1540 def untag(nickname, tags) when is_binary(nickname),
1541 do: untag(get_by_nickname(nickname), tags)
1543 def untag(%User{} = user, tags),
1544 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1546 defp update_tags(%User{} = user, new_tags) do
1547 {:ok, updated_user} =
1549 |> change(%{tags: new_tags})
1550 |> update_and_set_cache()
1555 defp normalize_tags(tags) do
1558 |> Enum.map(&String.downcase/1)
1561 defp local_nickname_regex do
1562 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1563 @extended_local_nickname_regex
1565 @strict_local_nickname_regex
1569 def local_nickname(nickname_or_mention) do
1572 |> String.split("@")
1576 def full_nickname(nickname_or_mention),
1577 do: String.trim_leading(nickname_or_mention, "@")
1579 def error_user(ap_id) do
1583 nickname: "erroruser@example.com",
1584 inserted_at: NaiveDateTime.utc_now()
1588 @spec all_superusers() :: [User.t()]
1589 def all_superusers do
1590 User.Query.build(%{super_users: true, local: true, deactivated: false})
1594 def showing_reblogs?(%User{} = user, %User{} = target) do
1595 target.ap_id not in user.muted_reblogs
1599 The function returns a query to get users with no activity for given interval of days.
1600 Inactive users are those who didn't read any notification, or had any activity where
1601 the user is the activity's actor, during `inactivity_threshold` days.
1602 Deactivated users will not appear in this list.
1606 iex> Pleroma.User.list_inactive_users()
1609 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1610 def list_inactive_users_query(inactivity_threshold \\ 7) do
1611 negative_inactivity_threshold = -inactivity_threshold
1612 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1613 # Subqueries are not supported in `where` clauses, join gets too complicated.
1614 has_read_notifications =
1615 from(n in Pleroma.Notification,
1616 where: n.seen == true,
1618 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1621 |> Pleroma.Repo.all()
1623 from(u in Pleroma.User,
1624 left_join: a in Pleroma.Activity,
1625 on: u.ap_id == a.actor,
1626 where: not is_nil(u.nickname),
1627 where: u.deactivated != ^true,
1628 where: u.id not in ^has_read_notifications,
1631 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1632 is_nil(max(a.inserted_at))
1637 Enable or disable email notifications for user
1641 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1642 Pleroma.User{email_notifications: %{"digest" => true}}
1644 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1645 Pleroma.User{email_notifications: %{"digest" => false}}
1647 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1648 {:ok, t()} | {:error, Ecto.Changeset.t()}
1649 def switch_email_notifications(user, type, status) do
1650 User.update_email_notifications(user, %{type => status})
1654 Set `last_digest_emailed_at` value for the user to current time
1656 @spec touch_last_digest_emailed_at(t()) :: t()
1657 def touch_last_digest_emailed_at(user) do
1658 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1660 {:ok, updated_user} =
1662 |> change(%{last_digest_emailed_at: now})
1663 |> update_and_set_cache()
1668 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1669 def toggle_confirmation(%User{} = user) do
1671 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1672 |> update_and_set_cache()
1675 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1679 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1680 # use instance-default
1681 config = Pleroma.Config.get([:assets, :mascots])
1682 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1683 mascot = Keyword.get(config, default_mascot)
1686 "id" => "default-mascot",
1687 "url" => mascot[:url],
1688 "preview_url" => mascot[:url],
1690 "mime_type" => mascot[:mime_type]
1695 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1697 def ensure_keys_present(%User{} = user) do
1698 with {:ok, pem} <- Keys.generate_rsa_pem() do
1700 |> cast(%{keys: pem}, [:keys])
1701 |> validate_required([:keys])
1702 |> update_and_set_cache()
1706 def get_ap_ids_by_nicknames(nicknames) do
1708 where: u.nickname in ^nicknames,
1714 defdelegate search(query, opts \\ []), to: User.Search
1716 defp put_password_hash(
1717 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1719 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1722 defp put_password_hash(changeset), do: changeset
1724 def is_internal_user?(%User{nickname: nil}), do: true
1725 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1726 def is_internal_user?(_), do: false
1728 # A hack because user delete activities have a fake id for whatever reason
1729 # TODO: Get rid of this
1730 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1732 def get_delivered_users_by_object_id(object_id) do
1734 inner_join: delivery in assoc(u, :deliveries),
1735 where: delivery.object_id == ^object_id
1740 def change_email(user, email) do
1742 |> cast(%{email: email}, [:email])
1743 |> validate_required([:email])
1744 |> unique_constraint(:email)
1745 |> validate_format(:email, @email_regex)
1746 |> update_and_set_cache()
1749 # Internal function; public one is `deactivate/2`
1750 defp set_activation_status(user, deactivated) do
1752 |> cast(%{deactivated: deactivated}, [:deactivated])
1753 |> update_and_set_cache()
1756 def update_banner(user, banner) do
1758 |> cast(%{banner: banner}, [:banner])
1759 |> update_and_set_cache()
1762 def update_background(user, background) do
1764 |> cast(%{background: background}, [:background])
1765 |> update_and_set_cache()
1768 def update_source_data(user, source_data) do
1770 |> cast(%{source_data: source_data}, [:source_data])
1771 |> update_and_set_cache()
1774 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1777 moderator: is_moderator
1781 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1782 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1783 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1784 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1787 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1788 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1792 def fields(%{fields: nil}), do: []
1794 def fields(%{fields: fields}), do: fields
1796 def validate_fields(changeset, remote? \\ false) do
1797 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1798 limit = Pleroma.Config.get([:instance, limit_name], 0)
1801 |> validate_length(:fields, max: limit)
1802 |> validate_change(:fields, fn :fields, fields ->
1803 if Enum.all?(fields, &valid_field?/1) do
1811 defp valid_field?(%{"name" => name, "value" => value}) do
1812 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1813 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1815 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1816 String.length(value) <= value_limit
1819 defp valid_field?(_), do: false
1821 defp truncate_field(%{"name" => name, "value" => value}) do
1823 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1826 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1828 %{"name" => name, "value" => value}
1831 def admin_api_update(user, params) do
1838 |> update_and_set_cache()
1841 def mascot_update(user, url) do
1843 |> cast(%{mascot: url}, [:mascot])
1844 |> validate_required([:mascot])
1845 |> update_and_set_cache()
1848 def mastodon_settings_update(user, settings) do
1850 |> cast(%{settings: settings}, [:settings])
1851 |> validate_required([:settings])
1852 |> update_and_set_cache()
1855 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1856 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1858 if need_confirmation? do
1860 confirmation_pending: true,
1861 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1865 confirmation_pending: false,
1866 confirmation_token: nil
1870 cast(user, params, [:confirmation_pending, :confirmation_token])
1873 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1874 if id not in user.pinned_activities do
1875 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1876 params = %{pinned_activities: user.pinned_activities ++ [id]}
1879 |> cast(params, [:pinned_activities])
1880 |> validate_length(:pinned_activities,
1881 max: max_pinned_statuses,
1882 message: "You have already pinned the maximum number of statuses"
1887 |> update_and_set_cache()
1890 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1891 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1894 |> cast(params, [:pinned_activities])
1895 |> update_and_set_cache()
1898 def update_email_notifications(user, settings) do
1899 email_notifications =
1900 user.email_notifications
1901 |> Map.merge(settings)
1902 |> Map.take(["digest"])
1904 params = %{email_notifications: email_notifications}
1905 fields = [:email_notifications]
1908 |> cast(params, fields)
1909 |> validate_required(fields)
1910 |> update_and_set_cache()
1913 defp set_subscribers(user, subscribers) do
1914 params = %{subscribers: subscribers}
1917 |> cast(params, [:subscribers])
1918 |> validate_required([:subscribers])
1919 |> update_and_set_cache()
1922 def add_to_subscribers(user, subscribed) do
1923 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1926 def remove_from_subscribers(user, subscribed) do
1927 set_subscribers(user, List.delete(user.subscribers, subscribed))
1930 defp set_domain_blocks(user, domain_blocks) do
1931 params = %{domain_blocks: domain_blocks}
1934 |> cast(params, [:domain_blocks])
1935 |> validate_required([:domain_blocks])
1936 |> update_and_set_cache()
1939 def block_domain(user, domain_blocked) do
1940 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1943 def unblock_domain(user, domain_blocked) do
1944 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1947 defp set_blocks(user, blocks) do
1948 params = %{blocks: blocks}
1951 |> cast(params, [:blocks])
1952 |> validate_required([:blocks])
1953 |> update_and_set_cache()
1956 def add_to_block(user, blocked) do
1957 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1960 def remove_from_block(user, blocked) do
1961 set_blocks(user, List.delete(user.blocks, blocked))
1964 defp set_mutes(user, mutes) do
1965 params = %{mutes: mutes}
1968 |> cast(params, [:mutes])
1969 |> validate_required([:mutes])
1970 |> update_and_set_cache()
1973 def add_to_mutes(user, muted, notifications?) do
1974 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1975 set_notification_mutes(
1977 Enum.uniq([muted | user.muted_notifications]),
1983 def remove_from_mutes(user, muted) do
1984 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1985 set_notification_mutes(
1987 List.delete(user.muted_notifications, muted),
1993 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1997 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1998 params = %{muted_notifications: muted_notifications}
2001 |> cast(params, [:muted_notifications])
2002 |> validate_required([:muted_notifications])
2003 |> update_and_set_cache()
2006 def add_reblog_mute(user, ap_id) do
2007 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
2010 |> cast(params, [:muted_reblogs])
2011 |> update_and_set_cache()
2014 def remove_reblog_mute(user, ap_id) do
2015 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
2018 |> cast(params, [:muted_reblogs])
2019 |> update_and_set_cache()