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: [])
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)
227 defp truncate_fields_param(params) do
228 if Map.has_key?(params, :fields) do
229 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
235 defp truncate_if_exists(params, key, max_length) do
236 if Map.has_key?(params, key) and is_binary(params[key]) do
237 {value, _chopped} = String.split_at(params[key], max_length)
238 Map.put(params, key, value)
244 def remote_user_creation(params) do
245 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
246 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
250 |> Map.put(:info, params[:info] || %{})
251 |> truncate_if_exists(:name, name_limit)
252 |> truncate_if_exists(:bio, bio_limit)
253 |> truncate_fields_param()
273 :hide_followers_count,
282 |> validate_required([:name, :ap_id])
283 |> unique_constraint(:nickname)
284 |> validate_format(:nickname, @email_regex)
285 |> validate_length(:bio, max: bio_limit)
286 |> validate_length(:name, max: name_limit)
287 |> validate_fields(true)
289 case params[:source_data] do
290 %{"followers" => followers, "following" => following} ->
292 |> put_change(:follower_address, followers)
293 |> put_change(:following_address, following)
296 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
297 put_change(changeset, :follower_address, followers)
301 def update_changeset(struct, params \\ %{}) do
302 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
303 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
319 :hide_followers_count,
324 :skip_thread_containment,
327 :pleroma_settings_store,
331 |> unique_constraint(:nickname)
332 |> validate_format(:nickname, local_nickname_regex())
333 |> validate_length(:bio, max: bio_limit)
334 |> validate_length(:name, min: 1, max: name_limit)
335 |> validate_fields(false)
338 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
339 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
340 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
342 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
344 params = if remote?, do: truncate_fields_param(params), else: params
367 :hide_followers_count,
371 |> unique_constraint(:nickname)
372 |> validate_format(:nickname, local_nickname_regex())
373 |> validate_length(:bio, max: bio_limit)
374 |> validate_length(:name, max: name_limit)
375 |> validate_fields(remote?)
378 def password_update_changeset(struct, params) do
380 |> cast(params, [:password, :password_confirmation])
381 |> validate_required([:password, :password_confirmation])
382 |> validate_confirmation(:password)
383 |> put_password_hash()
384 |> put_change(:password_reset_pending, false)
387 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
388 def reset_password(%User{id: user_id} = user, data) do
391 |> Multi.update(:user, password_update_changeset(user, data))
392 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
393 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
395 case Repo.transaction(multi) do
396 {:ok, %{user: user} = _} -> set_cache(user)
397 {:error, _, changeset, _} -> {:error, changeset}
401 def update_password_reset_pending(user, value) do
404 |> put_change(:password_reset_pending, value)
405 |> update_and_set_cache()
408 def force_password_reset_async(user) do
409 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
412 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
413 def force_password_reset(user), do: update_password_reset_pending(user, true)
415 def register_changeset(struct, params \\ %{}, opts \\ []) do
416 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
417 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
420 if is_nil(opts[:need_confirmation]) do
421 Pleroma.Config.get([:instance, :account_activation_required])
423 opts[:need_confirmation]
427 |> confirmation_changeset(need_confirmation: need_confirmation?)
428 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
429 |> validate_required([:name, :nickname, :password, :password_confirmation])
430 |> validate_confirmation(:password)
431 |> unique_constraint(:email)
432 |> unique_constraint(:nickname)
433 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
434 |> validate_format(:nickname, local_nickname_regex())
435 |> validate_format(:email, @email_regex)
436 |> validate_length(:bio, max: bio_limit)
437 |> validate_length(:name, min: 1, max: name_limit)
438 |> maybe_validate_required_email(opts[:external])
441 |> unique_constraint(:ap_id)
442 |> put_following_and_follower_address()
445 def maybe_validate_required_email(changeset, true), do: changeset
446 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
448 defp put_ap_id(changeset) do
449 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
450 put_change(changeset, :ap_id, ap_id)
453 defp put_following_and_follower_address(changeset) do
454 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
457 |> put_change(:following, [followers])
458 |> put_change(:follower_address, followers)
461 defp autofollow_users(user) do
462 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
465 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
468 follow_all(user, autofollowed_users)
471 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
472 def register(%Ecto.Changeset{} = changeset) do
473 with {:ok, user} <- Repo.insert(changeset) do
474 post_register_action(user)
478 def post_register_action(%User{} = user) do
479 with {:ok, user} <- autofollow_users(user),
480 {:ok, user} <- set_cache(user),
481 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
482 {:ok, _} <- try_send_confirmation_email(user) do
487 def try_send_confirmation_email(%User{} = user) do
488 if user.confirmation_pending &&
489 Pleroma.Config.get([:instance, :account_activation_required]) do
491 |> Pleroma.Emails.UserEmail.account_confirmation_email()
492 |> Pleroma.Emails.Mailer.deliver_async()
500 def needs_update?(%User{local: true}), do: false
502 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
504 def needs_update?(%User{local: false} = user) do
505 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
508 def needs_update?(_), do: true
510 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
511 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true}) do
515 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
516 follow(follower, followed)
519 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
520 if not ap_enabled?(followed) do
521 follow(follower, followed)
527 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
528 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
529 def follow_all(follower, followeds) do
532 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
533 |> Enum.map(fn %{follower_address: fa} -> fa end)
537 where: u.id == ^follower.id,
542 "array(select distinct unnest (array_cat(?, ?)))",
551 {1, [follower]} = Repo.update_all(q, [])
553 Enum.each(followeds, &update_follower_count/1)
558 def follow(%User{} = follower, %User{} = followed) do
559 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
560 ap_followers = followed.follower_address
563 followed.deactivated ->
564 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
566 deny_follow_blocked and blocks?(followed, follower) ->
567 {:error, "Could not follow user: #{followed.nickname} blocked you."}
572 where: u.id == ^follower.id,
573 update: [push: [following: ^ap_followers]],
577 {1, [follower]} = Repo.update_all(q, [])
579 follower = maybe_update_following_count(follower)
581 {:ok, _} = update_follower_count(followed)
587 def unfollow(%User{} = follower, %User{} = followed) do
588 ap_followers = followed.follower_address
590 if following?(follower, followed) and follower.ap_id != followed.ap_id do
593 where: u.id == ^follower.id,
594 update: [pull: [following: ^ap_followers]],
598 {1, [follower]} = Repo.update_all(q, [])
600 follower = maybe_update_following_count(follower)
602 {:ok, followed} = update_follower_count(followed)
606 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
608 {:error, "Not subscribed!"}
612 @spec following?(User.t(), User.t()) :: boolean
613 def following?(%User{} = follower, %User{} = followed) do
614 Enum.member?(follower.following, followed.follower_address)
617 def locked?(%User{} = user) do
622 Repo.get_by(User, id: id)
625 def get_by_ap_id(ap_id) do
626 Repo.get_by(User, ap_id: ap_id)
629 def get_all_by_ap_id(ap_ids) do
630 from(u in __MODULE__,
631 where: u.ap_id in ^ap_ids
636 def get_all_by_ids(ids) do
637 from(u in __MODULE__, where: u.id in ^ids)
641 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
642 # of the ap_id and the domain and tries to get that user
643 def get_by_guessed_nickname(ap_id) do
644 domain = URI.parse(ap_id).host
645 name = List.last(String.split(ap_id, "/"))
646 nickname = "#{name}@#{domain}"
648 get_cached_by_nickname(nickname)
651 def set_cache({:ok, user}), do: set_cache(user)
652 def set_cache({:error, err}), do: {:error, err}
654 def set_cache(%User{} = user) do
655 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
656 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
657 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
661 def update_and_set_cache(struct, params) do
663 |> update_changeset(params)
664 |> update_and_set_cache()
667 def update_and_set_cache(changeset) do
668 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
673 def invalidate_cache(user) do
674 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
675 Cachex.del(:user_cache, "nickname:#{user.nickname}")
676 Cachex.del(:user_cache, "user_info:#{user.id}")
679 def get_cached_by_ap_id(ap_id) do
680 key = "ap_id:#{ap_id}"
681 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
684 def get_cached_by_id(id) do
688 Cachex.fetch!(:user_cache, key, fn _ ->
692 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
693 {:commit, user.ap_id}
699 get_cached_by_ap_id(ap_id)
702 def get_cached_by_nickname(nickname) do
703 key = "nickname:#{nickname}"
705 Cachex.fetch!(:user_cache, key, fn ->
706 case get_or_fetch_by_nickname(nickname) do
707 {:ok, user} -> {:commit, user}
708 {:error, _error} -> {:ignore, nil}
713 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
714 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
717 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
718 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
720 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
721 get_cached_by_nickname(nickname_or_id)
723 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
724 get_cached_by_nickname(nickname_or_id)
731 def get_by_nickname(nickname) do
732 Repo.get_by(User, nickname: nickname) ||
733 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
734 Repo.get_by(User, nickname: local_nickname(nickname))
738 def get_by_email(email), do: Repo.get_by(User, email: email)
740 def get_by_nickname_or_email(nickname_or_email) do
741 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
744 def get_cached_user_info(user) do
745 key = "user_info:#{user.id}"
746 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
749 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
751 def get_or_fetch_by_nickname(nickname) do
752 with %User{} = user <- get_by_nickname(nickname) do
756 with [_nick, _domain] <- String.split(nickname, "@"),
757 {:ok, user} <- fetch_by_nickname(nickname) do
758 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
759 fetch_initial_posts(user)
764 _e -> {:error, "not found " <> nickname}
769 @doc "Fetch some posts when the user has just been federated with"
770 def fetch_initial_posts(user) do
771 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
774 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
775 def get_followers_query(%User{} = user, nil) do
776 User.Query.build(%{followers: user, deactivated: false})
779 def get_followers_query(user, page) do
781 |> get_followers_query(nil)
782 |> User.Query.paginate(page, 20)
785 @spec get_followers_query(User.t()) :: Ecto.Query.t()
786 def get_followers_query(user), do: get_followers_query(user, nil)
788 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
789 def get_followers(user, page \\ nil) do
791 |> get_followers_query(page)
795 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
796 def get_external_followers(user, page \\ nil) do
798 |> get_followers_query(page)
799 |> User.Query.build(%{external: true})
803 def get_followers_ids(user, page \\ nil) do
805 |> get_followers_query(page)
810 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
811 def get_friends_query(%User{} = user, nil) do
812 User.Query.build(%{friends: user, deactivated: false})
815 def get_friends_query(user, page) do
817 |> get_friends_query(nil)
818 |> User.Query.paginate(page, 20)
821 @spec get_friends_query(User.t()) :: Ecto.Query.t()
822 def get_friends_query(user), do: get_friends_query(user, nil)
824 def get_friends(user, page \\ nil) do
826 |> get_friends_query(page)
830 def get_friends_ids(user, page \\ nil) do
832 |> get_friends_query(page)
837 @spec get_follow_requests(User.t()) :: {:ok, [User.t()]}
838 def get_follow_requests(%User{} = user) do
840 |> Activity.follow_requests_for_actor()
841 |> join(:inner, [a], u in User, on: a.actor == u.ap_id)
842 |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
843 |> group_by([a, u], u.id)
848 def increase_note_count(%User{} = user) do
850 |> where(id: ^user.id)
851 |> update([u], inc: [note_count: 1])
853 |> Repo.update_all([])
855 {1, [user]} -> set_cache(user)
860 def decrease_note_count(%User{} = user) do
862 |> where(id: ^user.id)
865 note_count: fragment("greatest(0, note_count - 1)")
869 |> Repo.update_all([])
871 {1, [user]} -> set_cache(user)
876 def update_note_count(%User{} = user, note_count \\ nil) do
881 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
887 |> cast(%{note_count: note_count}, [:note_count])
888 |> update_and_set_cache()
891 @spec maybe_fetch_follow_information(User.t()) :: User.t()
892 def maybe_fetch_follow_information(user) do
893 with {:ok, user} <- fetch_follow_information(user) do
897 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
903 def fetch_follow_information(user) do
904 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
906 |> follow_information_changeset(info)
907 |> update_and_set_cache()
911 defp follow_information_changeset(user, params) do
918 :hide_followers_count,
923 def update_follower_count(%User{} = user) do
924 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
925 follower_count_query =
926 User.Query.build(%{followers: user, deactivated: false})
927 |> select([u], %{count: count(u.id)})
930 |> where(id: ^user.id)
931 |> join(:inner, [u], s in subquery(follower_count_query))
933 set: [follower_count: s.count]
936 |> Repo.update_all([])
938 {1, [user]} -> set_cache(user)
942 {:ok, maybe_fetch_follow_information(user)}
946 @spec maybe_update_following_count(User.t()) :: User.t()
947 def maybe_update_following_count(%User{local: false} = user) do
948 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
949 maybe_fetch_follow_information(user)
955 def maybe_update_following_count(user), do: user
957 def set_unread_conversation_count(%User{local: true} = user) do
958 unread_query = Participation.unread_conversation_count_for_user(user)
961 |> join(:inner, [u], p in subquery(unread_query))
963 set: [unread_conversation_count: p.count]
965 |> where([u], u.id == ^user.id)
967 |> Repo.update_all([])
969 {1, [user]} -> set_cache(user)
974 def set_unread_conversation_count(_), do: :noop
976 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
978 Participation.unread_conversation_count_for_user(user)
979 |> where([p], p.conversation_id == ^conversation.id)
982 |> join(:inner, [u], p in subquery(unread_query))
984 inc: [unread_conversation_count: 1]
986 |> where([u], u.id == ^user.id)
987 |> where([u, p], p.count == 0)
989 |> Repo.update_all([])
991 {1, [user]} -> set_cache(user)
996 def increment_unread_conversation_count(_, _), do: :noop
998 def remove_duplicated_following(%User{following: following} = user) do
999 uniq_following = Enum.uniq(following)
1001 if length(following) == length(uniq_following) do
1005 |> update_changeset(%{following: uniq_following})
1006 |> update_and_set_cache()
1010 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1011 def get_users_from_set(ap_ids, local_only \\ true) do
1012 criteria = %{ap_id: ap_ids, deactivated: false}
1013 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1015 User.Query.build(criteria)
1019 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1020 def get_recipients_from_activity(%Activity{recipients: to}) do
1021 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1025 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
1026 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
1027 add_to_mutes(muter, ap_id, notifications?)
1030 def unmute(muter, %{ap_id: ap_id}) do
1031 remove_from_mutes(muter, ap_id)
1034 def subscribe(subscriber, %{ap_id: ap_id}) do
1035 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
1036 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1038 if blocks?(subscribed, subscriber) and deny_follow_blocked do
1039 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
1041 User.add_to_subscribers(subscribed, subscriber.ap_id)
1046 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
1047 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1048 User.remove_from_subscribers(user, unsubscriber.ap_id)
1052 def block(blocker, %User{ap_id: ap_id} = blocked) do
1053 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1055 if following?(blocker, blocked) do
1056 {:ok, blocker, _} = unfollow(blocker, blocked)
1062 # clear any requested follows as well
1064 case CommonAPI.reject_follow_request(blocked, blocker) do
1065 {:ok, %User{} = updated_blocked} -> updated_blocked
1070 if subscribed_to?(blocked, blocker) do
1071 {:ok, blocker} = unsubscribe(blocked, blocker)
1077 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1079 {:ok, blocker} = update_follower_count(blocker)
1081 add_to_block(blocker, ap_id)
1084 # helper to handle the block given only an actor's AP id
1085 def block(blocker, %{ap_id: ap_id}) do
1086 block(blocker, get_cached_by_ap_id(ap_id))
1089 def unblock(blocker, %{ap_id: ap_id}) do
1090 remove_from_block(blocker, ap_id)
1093 def mutes?(nil, _), do: false
1094 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1096 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1097 def muted_notifications?(nil, _), do: false
1099 def muted_notifications?(user, %{ap_id: ap_id}),
1100 do: Enum.member?(user.muted_notifications, ap_id)
1102 def blocks?(%User{} = user, %User{} = target) do
1103 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1106 def blocks?(nil, _), do: false
1108 def blocks_ap_id?(%User{} = user, %User{} = target) do
1109 Enum.member?(user.blocks, target.ap_id)
1112 def blocks_ap_id?(_, _), do: false
1114 def blocks_domain?(%User{} = user, %User{} = target) do
1115 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1116 %{host: host} = URI.parse(target.ap_id)
1117 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1120 def blocks_domain?(_, _), do: false
1122 def subscribed_to?(user, %{ap_id: ap_id}) do
1123 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1124 Enum.member?(target.subscribers, user.ap_id)
1128 @spec muted_users(User.t()) :: [User.t()]
1129 def muted_users(user) do
1130 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1134 @spec blocked_users(User.t()) :: [User.t()]
1135 def blocked_users(user) do
1136 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1140 @spec subscribers(User.t()) :: [User.t()]
1141 def subscribers(user) do
1142 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1146 def deactivate_async(user, status \\ true) do
1147 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1150 def deactivate(user, status \\ true)
1152 def deactivate(users, status) when is_list(users) do
1153 Repo.transaction(fn ->
1154 for user <- users, do: deactivate(user, status)
1158 def deactivate(%User{} = user, status) do
1159 with {:ok, user} <- set_activation_status(user, status) do
1160 Enum.each(get_followers(user), &invalidate_cache/1)
1161 Enum.each(get_friends(user), &update_follower_count/1)
1167 def update_notification_settings(%User{} = user, settings) do
1170 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1173 notification_settings =
1174 user.notification_settings
1175 |> Map.merge(settings)
1176 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1178 params = %{notification_settings: notification_settings}
1181 |> cast(params, [:notification_settings])
1182 |> validate_required([:notification_settings])
1183 |> update_and_set_cache()
1186 def delete(users) when is_list(users) do
1187 for user <- users, do: delete(user)
1190 def delete(%User{} = user) do
1191 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1194 def perform(:force_password_reset, user), do: force_password_reset(user)
1196 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1197 def perform(:delete, %User{} = user) do
1198 {:ok, _user} = ActivityPub.delete(user)
1200 # Remove all relationships
1203 |> Enum.each(fn follower ->
1204 ActivityPub.unfollow(follower, user)
1205 unfollow(follower, user)
1210 |> Enum.each(fn followed ->
1211 ActivityPub.unfollow(user, followed)
1212 unfollow(user, followed)
1215 delete_user_activities(user)
1216 invalidate_cache(user)
1220 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1221 def perform(:fetch_initial_posts, %User{} = user) do
1222 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1224 # Insert all the posts in reverse order, so they're in the right order on the timeline
1225 user.source_data["outbox"]
1226 |> Utils.fetch_ordered_collection(pages)
1228 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1231 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1233 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1234 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1235 when is_list(blocked_identifiers) do
1237 blocked_identifiers,
1238 fn blocked_identifier ->
1239 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1240 {:ok, blocker} <- block(blocker, blocked),
1241 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1245 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1252 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1253 def perform(:follow_import, %User{} = follower, followed_identifiers)
1254 when is_list(followed_identifiers) do
1256 followed_identifiers,
1257 fn followed_identifier ->
1258 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1259 {:ok, follower} <- maybe_direct_follow(follower, followed),
1260 {:ok, _} <- ActivityPub.follow(follower, followed) do
1264 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1271 @spec external_users_query() :: Ecto.Query.t()
1272 def external_users_query do
1280 @spec external_users(keyword()) :: [User.t()]
1281 def external_users(opts \\ []) do
1283 external_users_query()
1284 |> select([u], struct(u, [:id, :ap_id, :info]))
1288 do: where(query, [u], u.id > ^opts[:max_id]),
1293 do: limit(query, ^opts[:limit]),
1299 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1300 BackgroundWorker.enqueue("blocks_import", %{
1301 "blocker_id" => blocker.id,
1302 "blocked_identifiers" => blocked_identifiers
1306 def follow_import(%User{} = follower, followed_identifiers)
1307 when is_list(followed_identifiers) do
1308 BackgroundWorker.enqueue("follow_import", %{
1309 "follower_id" => follower.id,
1310 "followed_identifiers" => followed_identifiers
1314 def delete_user_activities(%User{ap_id: ap_id}) do
1316 |> Activity.Queries.by_actor()
1317 |> RepoStreamer.chunk_stream(50)
1318 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1322 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1324 |> Object.normalize()
1325 |> ActivityPub.delete()
1328 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1329 object = Object.normalize(activity)
1332 |> get_cached_by_ap_id()
1333 |> ActivityPub.unlike(object)
1336 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1337 object = Object.normalize(activity)
1340 |> get_cached_by_ap_id()
1341 |> ActivityPub.unannounce(object)
1344 defp delete_activity(_activity), do: "Doing nothing"
1346 def html_filter_policy(%User{no_rich_text: true}) do
1347 Pleroma.HTML.Scrubber.TwitterText
1350 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1352 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1354 def get_or_fetch_by_ap_id(ap_id) do
1355 user = get_cached_by_ap_id(ap_id)
1357 if !is_nil(user) and !needs_update?(user) do
1360 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1361 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1363 resp = fetch_by_ap_id(ap_id)
1365 if should_fetch_initial do
1366 with {:ok, %User{} = user} <- resp do
1367 fetch_initial_posts(user)
1375 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1376 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1377 with %User{} = user <- get_cached_by_ap_id(uri) do
1383 |> cast(%{}, [:ap_id, :nickname, :local])
1384 |> put_change(:ap_id, uri)
1385 |> put_change(:nickname, nickname)
1386 |> put_change(:local, true)
1387 |> put_change(:follower_address, uri <> "/followers")
1395 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1398 |> :public_key.pem_decode()
1400 |> :public_key.pem_entry_decode()
1405 def public_key(_), do: {:error, "not found key"}
1407 def get_public_key_for_ap_id(ap_id) do
1408 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1409 {:ok, public_key} <- public_key(user) do
1416 defp blank?(""), do: nil
1417 defp blank?(n), do: n
1419 def insert_or_update_user(data) do
1421 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1422 |> remote_user_creation()
1423 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1427 def ap_enabled?(%User{local: true}), do: true
1428 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1429 def ap_enabled?(_), do: false
1431 @doc "Gets or fetch a user by uri or nickname."
1432 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1433 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1434 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1436 # wait a period of time and return newest version of the User structs
1437 # this is because we have synchronous follow APIs and need to simulate them
1438 # with an async handshake
1439 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1440 with %User{} = a <- get_cached_by_id(a.id),
1441 %User{} = b <- get_cached_by_id(b.id) do
1448 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1449 with :ok <- :timer.sleep(timeout),
1450 %User{} = a <- get_cached_by_id(a.id),
1451 %User{} = b <- get_cached_by_id(b.id) do
1458 def parse_bio(bio) when is_binary(bio) and bio != "" do
1460 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1464 def parse_bio(_), do: ""
1466 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1467 # TODO: get profile URLs other than user.ap_id
1468 profile_urls = [user.ap_id]
1471 |> CommonUtils.format_input("text/plain",
1472 mentions_format: :full,
1473 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1478 def parse_bio(_, _), do: ""
1480 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1481 Repo.transaction(fn ->
1482 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1486 def tag(nickname, tags) when is_binary(nickname),
1487 do: tag(get_by_nickname(nickname), tags)
1489 def tag(%User{} = user, tags),
1490 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1492 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1493 Repo.transaction(fn ->
1494 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1498 def untag(nickname, tags) when is_binary(nickname),
1499 do: untag(get_by_nickname(nickname), tags)
1501 def untag(%User{} = user, tags),
1502 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1504 defp update_tags(%User{} = user, new_tags) do
1505 {:ok, updated_user} =
1507 |> change(%{tags: new_tags})
1508 |> update_and_set_cache()
1513 defp normalize_tags(tags) do
1516 |> Enum.map(&String.downcase/1)
1519 defp local_nickname_regex do
1520 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1521 @extended_local_nickname_regex
1523 @strict_local_nickname_regex
1527 def local_nickname(nickname_or_mention) do
1530 |> String.split("@")
1534 def full_nickname(nickname_or_mention),
1535 do: String.trim_leading(nickname_or_mention, "@")
1537 def error_user(ap_id) do
1541 nickname: "erroruser@example.com",
1542 inserted_at: NaiveDateTime.utc_now()
1546 @spec all_superusers() :: [User.t()]
1547 def all_superusers do
1548 User.Query.build(%{super_users: true, local: true, deactivated: false})
1552 def showing_reblogs?(%User{} = user, %User{} = target) do
1553 target.ap_id not in user.muted_reblogs
1557 The function returns a query to get users with no activity for given interval of days.
1558 Inactive users are those who didn't read any notification, or had any activity where
1559 the user is the activity's actor, during `inactivity_threshold` days.
1560 Deactivated users will not appear in this list.
1564 iex> Pleroma.User.list_inactive_users()
1567 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1568 def list_inactive_users_query(inactivity_threshold \\ 7) do
1569 negative_inactivity_threshold = -inactivity_threshold
1570 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1571 # Subqueries are not supported in `where` clauses, join gets too complicated.
1572 has_read_notifications =
1573 from(n in Pleroma.Notification,
1574 where: n.seen == true,
1576 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1579 |> Pleroma.Repo.all()
1581 from(u in Pleroma.User,
1582 left_join: a in Pleroma.Activity,
1583 on: u.ap_id == a.actor,
1584 where: not is_nil(u.nickname),
1585 where: u.deactivated != ^true,
1586 where: u.id not in ^has_read_notifications,
1589 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1590 is_nil(max(a.inserted_at))
1595 Enable or disable email notifications for user
1599 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1600 Pleroma.User{email_notifications: %{"digest" => true}}
1602 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1603 Pleroma.User{email_notifications: %{"digest" => false}}
1605 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1606 {:ok, t()} | {:error, Ecto.Changeset.t()}
1607 def switch_email_notifications(user, type, status) do
1608 User.update_email_notifications(user, %{type => status})
1612 Set `last_digest_emailed_at` value for the user to current time
1614 @spec touch_last_digest_emailed_at(t()) :: t()
1615 def touch_last_digest_emailed_at(user) do
1616 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1618 {:ok, updated_user} =
1620 |> change(%{last_digest_emailed_at: now})
1621 |> update_and_set_cache()
1626 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1627 def toggle_confirmation(%User{} = user) do
1629 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1630 |> update_and_set_cache()
1633 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1637 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1638 # use instance-default
1639 config = Pleroma.Config.get([:assets, :mascots])
1640 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1641 mascot = Keyword.get(config, default_mascot)
1644 "id" => "default-mascot",
1645 "url" => mascot[:url],
1646 "preview_url" => mascot[:url],
1648 "mime_type" => mascot[:mime_type]
1653 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1655 def ensure_keys_present(%User{} = user) do
1656 with {:ok, pem} <- Keys.generate_rsa_pem() do
1658 |> cast(%{keys: pem}, [:keys])
1659 |> validate_required([:keys])
1660 |> update_and_set_cache()
1664 def get_ap_ids_by_nicknames(nicknames) do
1666 where: u.nickname in ^nicknames,
1672 defdelegate search(query, opts \\ []), to: User.Search
1674 defp put_password_hash(
1675 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1677 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1680 defp put_password_hash(changeset), do: changeset
1682 def is_internal_user?(%User{nickname: nil}), do: true
1683 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1684 def is_internal_user?(_), do: false
1686 # A hack because user delete activities have a fake id for whatever reason
1687 # TODO: Get rid of this
1688 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1690 def get_delivered_users_by_object_id(object_id) do
1692 inner_join: delivery in assoc(u, :deliveries),
1693 where: delivery.object_id == ^object_id
1698 def change_email(user, email) do
1700 |> cast(%{email: email}, [:email])
1701 |> validate_required([:email])
1702 |> unique_constraint(:email)
1703 |> validate_format(:email, @email_regex)
1704 |> update_and_set_cache()
1707 # Internal function; public one is `deactivate/2`
1708 defp set_activation_status(user, deactivated) do
1710 |> cast(%{deactivated: deactivated}, [:deactivated])
1711 |> update_and_set_cache()
1714 def update_banner(user, banner) do
1716 |> cast(%{banner: banner}, [:banner])
1717 |> update_and_set_cache()
1720 def update_background(user, background) do
1722 |> cast(%{background: background}, [:background])
1723 |> update_and_set_cache()
1726 def update_source_data(user, source_data) do
1728 |> cast(%{source_data: source_data}, [:source_data])
1729 |> update_and_set_cache()
1732 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1735 moderator: is_moderator
1739 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1740 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1741 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1742 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1745 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1746 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1750 def fields(%{fields: nil}), do: []
1752 def fields(%{fields: fields}), do: fields
1754 def validate_fields(changeset, remote? \\ false) do
1755 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1756 limit = Pleroma.Config.get([:instance, limit_name], 0)
1759 |> validate_length(:fields, max: limit)
1760 |> validate_change(:fields, fn :fields, fields ->
1761 if Enum.all?(fields, &valid_field?/1) do
1769 defp valid_field?(%{"name" => name, "value" => value}) do
1770 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1771 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1773 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1774 String.length(value) <= value_limit
1777 defp valid_field?(_), do: false
1779 defp truncate_field(%{"name" => name, "value" => value}) do
1781 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1784 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1786 %{"name" => name, "value" => value}
1789 def admin_api_update(user, params) do
1796 |> update_and_set_cache()
1799 def mascot_update(user, url) do
1801 |> cast(%{mascot: url}, [:mascot])
1802 |> validate_required([:mascot])
1803 |> update_and_set_cache()
1806 def mastodon_settings_update(user, settings) do
1808 |> cast(%{settings: settings}, [:settings])
1809 |> validate_required([:settings])
1810 |> update_and_set_cache()
1813 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1814 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1816 if need_confirmation? do
1818 confirmation_pending: true,
1819 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1823 confirmation_pending: false,
1824 confirmation_token: nil
1828 cast(user, params, [:confirmation_pending, :confirmation_token])
1831 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1832 if id not in user.pinned_activities do
1833 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1834 params = %{pinned_activities: user.pinned_activities ++ [id]}
1837 |> cast(params, [:pinned_activities])
1838 |> validate_length(:pinned_activities,
1839 max: max_pinned_statuses,
1840 message: "You have already pinned the maximum number of statuses"
1845 |> update_and_set_cache()
1848 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1849 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1852 |> cast(params, [:pinned_activities])
1853 |> update_and_set_cache()
1856 def update_email_notifications(user, settings) do
1857 email_notifications =
1858 user.email_notifications
1859 |> Map.merge(settings)
1860 |> Map.take(["digest"])
1862 params = %{email_notifications: email_notifications}
1863 fields = [:email_notifications]
1866 |> cast(params, fields)
1867 |> validate_required(fields)
1868 |> update_and_set_cache()
1871 defp set_subscribers(user, subscribers) do
1872 params = %{subscribers: subscribers}
1875 |> cast(params, [:subscribers])
1876 |> validate_required([:subscribers])
1877 |> update_and_set_cache()
1880 def add_to_subscribers(user, subscribed) do
1881 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1884 def remove_from_subscribers(user, subscribed) do
1885 set_subscribers(user, List.delete(user.subscribers, subscribed))
1888 defp set_domain_blocks(user, domain_blocks) do
1889 params = %{domain_blocks: domain_blocks}
1892 |> cast(params, [:domain_blocks])
1893 |> validate_required([:domain_blocks])
1894 |> update_and_set_cache()
1897 def block_domain(user, domain_blocked) do
1898 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1901 def unblock_domain(user, domain_blocked) do
1902 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1905 defp set_blocks(user, blocks) do
1906 params = %{blocks: blocks}
1909 |> cast(params, [:blocks])
1910 |> validate_required([:blocks])
1911 |> update_and_set_cache()
1914 def add_to_block(user, blocked) do
1915 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1918 def remove_from_block(user, blocked) do
1919 set_blocks(user, List.delete(user.blocks, blocked))
1922 defp set_mutes(user, mutes) do
1923 params = %{mutes: mutes}
1926 |> cast(params, [:mutes])
1927 |> validate_required([:mutes])
1928 |> update_and_set_cache()
1931 def add_to_mutes(user, muted, notifications?) do
1932 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1933 set_notification_mutes(
1935 Enum.uniq([muted | user.muted_notifications]),
1941 def remove_from_mutes(user, muted) do
1942 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1943 set_notification_mutes(
1945 List.delete(user.muted_notifications, muted),
1951 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1955 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1956 params = %{muted_notifications: muted_notifications}
1959 |> cast(params, [:muted_notifications])
1960 |> validate_required([:muted_notifications])
1961 |> update_and_set_cache()
1964 def add_reblog_mute(user, ap_id) do
1965 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1968 |> cast(params, [:muted_reblogs])
1969 |> update_and_set_cache()
1972 def remove_reblog_mute(user, ap_id) do
1973 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1976 |> cast(params, [:muted_reblogs])
1977 |> update_and_set_cache()
1980 def set_invisible(user, invisible) do
1981 params = %{invisible: invisible}
1984 |> cast(params, [:invisible])
1985 |> validate_required([:invisible])
1986 |> update_and_set_cache()