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
10 import Ecto, only: [assoc: 2]
14 alias Pleroma.Activity
15 alias Pleroma.Conversation.Participation
16 alias Pleroma.Delivery
17 alias Pleroma.FollowingRelationship
19 alias Pleroma.Notification
21 alias Pleroma.Registration
23 alias Pleroma.RepoStreamer
25 alias Pleroma.UserBlock
27 alias Pleroma.Web.ActivityPub.ActivityPub
28 alias Pleroma.Web.ActivityPub.Utils
29 alias Pleroma.Web.CommonAPI
30 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
31 alias Pleroma.Web.OAuth
32 alias Pleroma.Web.RelMe
33 alias Pleroma.Workers.BackgroundWorker
37 @type t :: %__MODULE__{}
39 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
41 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
42 @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])?)*$/
44 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
45 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
49 field(:email, :string)
51 field(:nickname, :string)
52 field(:password_hash, :string)
53 field(:password, :string, virtual: true)
54 field(:password_confirmation, :string, virtual: true)
56 field(:ap_id, :string)
58 field(:local, :boolean, default: true)
59 field(:follower_address, :string)
60 field(:following_address, :string)
61 field(:search_rank, :float, virtual: true)
62 field(:search_type, :integer, virtual: true)
63 field(:tags, {:array, :string}, default: [])
64 field(:last_refreshed_at, :naive_datetime_usec)
65 field(:last_digest_emailed_at, :naive_datetime)
67 field(:banner, :map, default: %{})
68 field(:background, :map, default: %{})
69 field(:source_data, :map, default: %{})
70 field(:note_count, :integer, default: 0)
71 field(:follower_count, :integer, default: 0)
72 # Should be filled in only for remote users
73 field(:following_count, :integer, default: nil)
74 field(:locked, :boolean, default: false)
75 field(:confirmation_pending, :boolean, default: false)
76 field(:password_reset_pending, :boolean, default: false)
77 field(:confirmation_token, :string, default: nil)
78 field(:default_scope, :string, default: "public")
79 field(:domain_blocks, {:array, :string}, default: [])
80 field(:mutes, {:array, :string}, default: [])
81 field(:muted_reblogs, {:array, :string}, default: [])
82 field(:muted_notifications, {:array, :string}, default: [])
83 field(:subscribers, {:array, :string}, default: [])
84 field(:deactivated, :boolean, default: false)
85 field(:no_rich_text, :boolean, default: false)
86 field(:ap_enabled, :boolean, default: false)
87 field(:is_moderator, :boolean, default: false)
88 field(:is_admin, :boolean, default: false)
89 field(:show_role, :boolean, default: true)
90 field(:settings, :map, default: nil)
91 field(:magic_key, :string, default: nil)
92 field(:uri, :string, default: nil)
93 field(:hide_followers_count, :boolean, default: false)
94 field(:hide_follows_count, :boolean, default: false)
95 field(:hide_followers, :boolean, default: false)
96 field(:hide_follows, :boolean, default: false)
97 field(:hide_favorites, :boolean, default: true)
98 field(:unread_conversation_count, :integer, default: 0)
99 field(:pinned_activities, {:array, :string}, default: [])
100 field(:email_notifications, :map, default: %{"digest" => false})
101 field(:mascot, :map, default: nil)
102 field(:emoji, {:array, :map}, default: [])
103 field(:pleroma_settings_store, :map, default: %{})
104 field(:fields, {:array, :map}, default: [])
105 field(:raw_fields, {:array, :map}, default: [])
106 field(:discoverable, :boolean, default: false)
107 field(:invisible, :boolean, default: false)
108 field(:skip_thread_containment, :boolean, default: false)
110 field(:notification_settings, :map,
114 "non_follows" => true,
115 "non_followers" => true
119 has_many(:notifications, Notification)
120 has_many(:registrations, Registration)
121 has_many(:deliveries, Delivery)
122 has_many(:blocker_blocks, UserBlock, foreign_key: :blocker_id)
123 has_many(:blockee_blocks, UserBlock, foreign_key: :blockee_id)
124 has_many(:blocked_users, through: [:blocker_blocks, :blockee])
125 has_many(:blocker_users, through: [:blockee_blocks, :blocker])
127 field(:info, :map, default: %{})
129 # `:blocks` is deprecated (replaced with `blocked_users` relation)
130 field(:blocks, {:array, :string}, default: [])
135 def auth_active?(%User{confirmation_pending: true}),
136 do: !Pleroma.Config.get([:instance, :account_activation_required])
138 def auth_active?(%User{}), do: true
140 def visible_for?(user, for_user \\ nil)
142 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
144 def visible_for?(%User{} = user, for_user) do
145 auth_active?(user) || superuser?(for_user)
148 def visible_for?(_, _), do: false
150 def superuser?(%User{local: true, is_admin: true}), do: true
151 def superuser?(%User{local: true, is_moderator: true}), do: true
152 def superuser?(_), do: false
154 def invisible?(%User{invisible: true}), do: true
155 def invisible?(_), do: false
157 def avatar_url(user, options \\ []) do
159 %{"url" => [%{"href" => href} | _]} -> href
160 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
164 def banner_url(user, options \\ []) do
166 %{"url" => [%{"href" => href} | _]} -> href
167 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
171 def profile_url(%User{source_data: %{"url" => url}}), do: url
172 def profile_url(%User{ap_id: ap_id}), do: ap_id
173 def profile_url(_), do: nil
175 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
177 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
178 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
180 @spec ap_following(User.t()) :: Sring.t()
181 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
182 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
184 def user_info(%User{} = user, args \\ %{}) do
186 Map.get(args, :following_count, user.following_count || following_count(user))
188 follower_count = Map.get(args, :follower_count, user.follower_count)
191 note_count: user.note_count,
193 confirmation_pending: user.confirmation_pending,
194 default_scope: user.default_scope
196 |> Map.put(:following_count, following_count)
197 |> Map.put(:follower_count, follower_count)
200 def follow_state(%User{} = user, %User{} = target) do
201 case Utils.fetch_latest_follow(user, target) do
202 %{data: %{"state" => state}} -> state
203 # Ideally this would be nil, but then Cachex does not commit the value
208 def get_cached_follow_state(user, target) do
209 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
210 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
213 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
214 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
215 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
218 def set_info_cache(user, args) do
219 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
222 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
223 def restrict_deactivated(query) do
224 from(u in query, where: u.deactivated != ^true)
227 defdelegate following_count(user), to: FollowingRelationship
229 defp truncate_fields_param(params) do
230 if Map.has_key?(params, :fields) do
231 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
237 defp truncate_if_exists(params, key, max_length) do
238 if Map.has_key?(params, key) and is_binary(params[key]) do
239 {value, _chopped} = String.split_at(params[key], max_length)
240 Map.put(params, key, value)
246 def remote_user_creation(params) do
247 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
248 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
252 |> Map.put(:info, params[:info] || %{})
253 |> truncate_if_exists(:name, name_limit)
254 |> truncate_if_exists(:bio, bio_limit)
255 |> truncate_fields_param()
275 :hide_followers_count,
284 |> validate_required([:name, :ap_id])
285 |> unique_constraint(:nickname)
286 |> validate_format(:nickname, @email_regex)
287 |> validate_length(:bio, max: bio_limit)
288 |> validate_length(:name, max: name_limit)
289 |> validate_fields(true)
291 case params[:source_data] do
292 %{"followers" => followers, "following" => following} ->
294 |> put_change(:follower_address, followers)
295 |> put_change(:following_address, following)
298 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
299 put_change(changeset, :follower_address, followers)
303 def update_changeset(struct, params \\ %{}) do
304 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
305 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
320 :hide_followers_count,
325 :skip_thread_containment,
328 :pleroma_settings_store,
332 |> unique_constraint(:nickname)
333 |> validate_format(:nickname, local_nickname_regex())
334 |> validate_length(:bio, max: bio_limit)
335 |> validate_length(:name, min: 1, max: name_limit)
336 |> validate_fields(false)
339 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
340 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
341 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
343 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
345 params = if remote?, do: truncate_fields_param(params), else: params
368 :hide_followers_count,
372 |> unique_constraint(:nickname)
373 |> validate_format(:nickname, local_nickname_regex())
374 |> validate_length(:bio, max: bio_limit)
375 |> validate_length(:name, max: name_limit)
376 |> validate_fields(remote?)
379 def password_update_changeset(struct, params) do
381 |> cast(params, [:password, :password_confirmation])
382 |> validate_required([:password, :password_confirmation])
383 |> validate_confirmation(:password)
384 |> put_password_hash()
385 |> put_change(:password_reset_pending, false)
388 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
389 def reset_password(%User{id: user_id} = user, data) do
392 |> Multi.update(:user, password_update_changeset(user, data))
393 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
394 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
396 case Repo.transaction(multi) do
397 {:ok, %{user: user} = _} -> set_cache(user)
398 {:error, _, changeset, _} -> {:error, changeset}
402 def update_password_reset_pending(user, value) do
405 |> put_change(:password_reset_pending, value)
406 |> update_and_set_cache()
409 def force_password_reset_async(user) do
410 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
413 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
414 def force_password_reset(user), do: update_password_reset_pending(user, true)
416 def register_changeset(struct, params \\ %{}, opts \\ []) do
417 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
418 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
421 if is_nil(opts[:need_confirmation]) do
422 Pleroma.Config.get([:instance, :account_activation_required])
424 opts[:need_confirmation]
428 |> confirmation_changeset(need_confirmation: need_confirmation?)
429 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
430 |> validate_required([:name, :nickname, :password, :password_confirmation])
431 |> validate_confirmation(:password)
432 |> unique_constraint(:email)
433 |> unique_constraint(:nickname)
434 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
435 |> validate_format(:nickname, local_nickname_regex())
436 |> validate_format(:email, @email_regex)
437 |> validate_length(:bio, max: bio_limit)
438 |> validate_length(:name, min: 1, max: name_limit)
439 |> maybe_validate_required_email(opts[:external])
442 |> unique_constraint(:ap_id)
443 |> put_following_and_follower_address()
446 def maybe_validate_required_email(changeset, true), do: changeset
447 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
449 defp put_ap_id(changeset) do
450 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
451 put_change(changeset, :ap_id, ap_id)
454 defp put_following_and_follower_address(changeset) do
455 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
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} = followed) do
512 follow(follower, followed, "pending")
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
531 Enum.reject(followeds, fn followed ->
532 blocks?(follower, followed) || blocks?(followed, follower)
535 Enum.each(followeds, &follow(follower, &1, "accept"))
537 Enum.each(followeds, &update_follower_count/1)
542 defdelegate following(user), to: FollowingRelationship
544 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
545 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
548 followed.deactivated ->
549 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
551 deny_follow_blocked and blocks?(followed, follower) ->
552 {:error, "Could not follow user: #{followed.nickname} blocked you."}
555 FollowingRelationship.follow(follower, followed, state)
557 follower = maybe_update_following_count(follower)
559 {:ok, _} = update_follower_count(followed)
565 def unfollow(%User{} = follower, %User{} = followed) do
566 if following?(follower, followed) and follower.ap_id != followed.ap_id do
567 FollowingRelationship.unfollow(follower, followed)
569 follower = maybe_update_following_count(follower)
571 {:ok, followed} = update_follower_count(followed)
575 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
577 {:error, "Not subscribed!"}
581 defdelegate following?(follower, followed), to: FollowingRelationship
583 def locked?(%User{} = user) do
588 Repo.get_by(User, id: id)
591 def get_by_ap_id(ap_id) do
592 Repo.get_by(User, ap_id: ap_id)
595 def get_all_by_ap_id(ap_ids) do
596 from(u in __MODULE__,
597 where: u.ap_id in ^ap_ids
602 def get_all_by_ids(ids) do
603 from(u in __MODULE__, where: u.id in ^ids)
607 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
608 # of the ap_id and the domain and tries to get that user
609 def get_by_guessed_nickname(ap_id) do
610 domain = URI.parse(ap_id).host
611 name = List.last(String.split(ap_id, "/"))
612 nickname = "#{name}@#{domain}"
614 get_cached_by_nickname(nickname)
617 def set_cache({:ok, user}), do: set_cache(user)
618 def set_cache({:error, err}), do: {:error, err}
620 def set_cache(%User{} = user) do
621 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
622 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
623 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
627 def update_and_set_cache(struct, params) do
629 |> update_changeset(params)
630 |> update_and_set_cache()
633 def update_and_set_cache(changeset) do
634 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
639 def invalidate_cache(user) do
640 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
641 Cachex.del(:user_cache, "nickname:#{user.nickname}")
642 Cachex.del(:user_cache, "user_info:#{user.id}")
645 def get_cached_by_ap_id(ap_id) do
646 key = "ap_id:#{ap_id}"
647 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
650 def get_cached_by_id(id) do
654 Cachex.fetch!(:user_cache, key, fn _ ->
658 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
659 {:commit, user.ap_id}
665 get_cached_by_ap_id(ap_id)
668 def get_cached_by_nickname(nickname) do
669 key = "nickname:#{nickname}"
671 Cachex.fetch!(:user_cache, key, fn ->
672 case get_or_fetch_by_nickname(nickname) do
673 {:ok, user} -> {:commit, user}
674 {:error, _error} -> {:ignore, nil}
679 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
680 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
683 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
684 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
686 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
687 get_cached_by_nickname(nickname_or_id)
689 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
690 get_cached_by_nickname(nickname_or_id)
697 def get_by_nickname(nickname) do
698 Repo.get_by(User, nickname: nickname) ||
699 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
700 Repo.get_by(User, nickname: local_nickname(nickname))
704 def get_by_email(email), do: Repo.get_by(User, email: email)
706 def get_by_nickname_or_email(nickname_or_email) do
707 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
710 def get_cached_user_info(user) do
711 key = "user_info:#{user.id}"
712 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
715 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
717 def get_or_fetch_by_nickname(nickname) do
718 with %User{} = user <- get_by_nickname(nickname) do
722 with [_nick, _domain] <- String.split(nickname, "@"),
723 {:ok, user} <- fetch_by_nickname(nickname) do
724 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
725 fetch_initial_posts(user)
730 _e -> {:error, "not found " <> nickname}
735 @doc "Fetch some posts when the user has just been federated with"
736 def fetch_initial_posts(user) do
737 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
740 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
741 def get_followers_query(%User{} = user, nil) do
742 User.Query.build(%{followers: user, deactivated: false})
745 def get_followers_query(user, page) do
747 |> get_followers_query(nil)
748 |> User.Query.paginate(page, 20)
751 @spec get_followers_query(User.t()) :: Ecto.Query.t()
752 def get_followers_query(user), do: get_followers_query(user, nil)
754 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
755 def get_followers(user, page \\ nil) do
757 |> get_followers_query(page)
761 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
762 def get_external_followers(user, page \\ nil) do
764 |> get_followers_query(page)
765 |> User.Query.build(%{external: true})
769 def get_followers_ids(user, page \\ nil) do
771 |> get_followers_query(page)
776 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
777 def get_friends_query(%User{} = user, nil) do
778 User.Query.build(%{friends: user, deactivated: false})
781 def get_friends_query(user, page) do
783 |> get_friends_query(nil)
784 |> User.Query.paginate(page, 20)
787 @spec get_friends_query(User.t()) :: Ecto.Query.t()
788 def get_friends_query(user), do: get_friends_query(user, nil)
790 def get_friends(user, page \\ nil) do
792 |> get_friends_query(page)
796 def get_friends_ids(user, page \\ nil) do
798 |> get_friends_query(page)
803 defdelegate get_follow_requests(user), to: FollowingRelationship
805 def increase_note_count(%User{} = user) do
807 |> where(id: ^user.id)
808 |> update([u], inc: [note_count: 1])
810 |> Repo.update_all([])
812 {1, [user]} -> set_cache(user)
817 def decrease_note_count(%User{} = user) do
819 |> where(id: ^user.id)
822 note_count: fragment("greatest(0, note_count - 1)")
826 |> Repo.update_all([])
828 {1, [user]} -> set_cache(user)
833 def update_note_count(%User{} = user, note_count \\ nil) do
838 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
844 |> cast(%{note_count: note_count}, [:note_count])
845 |> update_and_set_cache()
848 @spec maybe_fetch_follow_information(User.t()) :: User.t()
849 def maybe_fetch_follow_information(user) do
850 with {:ok, user} <- fetch_follow_information(user) do
854 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
860 def fetch_follow_information(user) do
861 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
863 |> follow_information_changeset(info)
864 |> update_and_set_cache()
868 defp follow_information_changeset(user, params) do
875 :hide_followers_count,
880 def update_follower_count(%User{} = user) do
881 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
882 follower_count_query =
883 User.Query.build(%{followers: user, deactivated: false})
884 |> select([u], %{count: count(u.id)})
887 |> where(id: ^user.id)
888 |> join(:inner, [u], s in subquery(follower_count_query))
890 set: [follower_count: s.count]
893 |> Repo.update_all([])
895 {1, [user]} -> set_cache(user)
899 {:ok, maybe_fetch_follow_information(user)}
903 @spec maybe_update_following_count(User.t()) :: User.t()
904 def maybe_update_following_count(%User{local: false} = user) do
905 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
906 maybe_fetch_follow_information(user)
912 def maybe_update_following_count(user), do: user
914 def set_unread_conversation_count(%User{local: true} = user) do
915 unread_query = Participation.unread_conversation_count_for_user(user)
918 |> join(:inner, [u], p in subquery(unread_query))
920 set: [unread_conversation_count: p.count]
922 |> where([u], u.id == ^user.id)
924 |> Repo.update_all([])
926 {1, [user]} -> set_cache(user)
931 def set_unread_conversation_count(user), do: {:ok, user}
933 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
935 Participation.unread_conversation_count_for_user(user)
936 |> where([p], p.conversation_id == ^conversation.id)
939 |> join(:inner, [u], p in subquery(unread_query))
941 inc: [unread_conversation_count: 1]
943 |> where([u], u.id == ^user.id)
944 |> where([u, p], p.count == 0)
946 |> Repo.update_all([])
948 {1, [user]} -> set_cache(user)
953 def increment_unread_conversation_count(_, user), do: {:ok, user}
955 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
956 def get_users_from_set(ap_ids, local_only \\ true) do
957 criteria = %{ap_id: ap_ids, deactivated: false}
958 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
960 User.Query.build(criteria)
964 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
965 def get_recipients_from_activity(%Activity{recipients: to}) do
966 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
970 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
971 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
972 add_to_mutes(muter, ap_id, notifications?)
975 def unmute(muter, %{ap_id: ap_id}) do
976 remove_from_mutes(muter, ap_id)
979 def subscribe(subscriber, %{ap_id: ap_id}) do
980 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
981 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
983 if blocks?(subscribed, subscriber) and deny_follow_blocked do
984 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
986 User.add_to_subscribers(subscribed, subscriber.ap_id)
991 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
992 with %User{} = user <- get_cached_by_ap_id(ap_id) do
993 User.remove_from_subscribers(user, unsubscriber.ap_id)
997 def block(blocker, %User{} = blocked) do
998 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1000 if following?(blocker, blocked) do
1001 {:ok, blocker, _} = unfollow(blocker, blocked)
1007 # clear any requested follows as well
1009 case CommonAPI.reject_follow_request(blocked, blocker) do
1010 {:ok, %User{} = updated_blocked} -> updated_blocked
1015 if subscribed_to?(blocked, blocker) do
1016 {:ok, blocker} = unsubscribe(blocked, blocker)
1022 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1024 {:ok, blocker} = update_follower_count(blocker)
1025 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1026 add_to_block(blocker, blocked)
1029 # helper to handle the block given only an actor's AP id
1030 def block(blocker, %{ap_id: ap_id}) do
1031 block(blocker, get_cached_by_ap_id(ap_id))
1034 def unblock(blocker, %User{} = blocked) do
1035 remove_from_block(blocker, blocked)
1038 # helper to handle the block given only an actor's AP id
1039 def unblock(blocker, %{ap_id: ap_id}) do
1040 unblock(blocker, get_cached_by_ap_id(ap_id))
1043 def mutes?(nil, _), do: false
1044 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1046 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1047 def muted_notifications?(nil, _), do: false
1049 def muted_notifications?(user, %{ap_id: ap_id}),
1050 do: Enum.member?(user.muted_notifications, ap_id)
1052 def blocks?(%User{} = user, %User{} = target) do
1053 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1056 def blocks?(nil, _), do: false
1058 def blocks_ap_id?(%User{} = user, %User{} = target) do
1059 UserBlock.exists?(user, target)
1062 def blocks_ap_id?(_, _), do: false
1064 def blocks_domain?(%User{} = user, %User{} = target) do
1065 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1066 %{host: host} = URI.parse(target.ap_id)
1067 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1070 def blocks_domain?(_, _), do: false
1072 def subscribed_to?(user, %{ap_id: ap_id}) do
1073 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1074 Enum.member?(target.subscribers, user.ap_id)
1078 @spec muted_users(User.t()) :: [User.t()]
1079 def muted_users(user) do
1080 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1084 @spec blocked_users(User.t()) :: [User.t()]
1085 def blocked_users(user) do
1087 |> assoc(:blocked_users)
1088 |> restrict_deactivated()
1092 def blocked_ap_ids(user) do
1094 from(u in assoc(user, :blocked_users),
1100 @spec subscribers(User.t()) :: [User.t()]
1101 def subscribers(user) do
1102 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1106 def deactivate_async(user, status \\ true) do
1107 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1110 def deactivate(user, status \\ true)
1112 def deactivate(users, status) when is_list(users) do
1113 Repo.transaction(fn ->
1114 for user <- users, do: deactivate(user, status)
1118 def deactivate(%User{} = user, status) do
1119 with {:ok, user} <- set_activation_status(user, status) do
1120 Enum.each(get_followers(user), &invalidate_cache/1)
1122 # Only update local user counts, remote will be update during the next pull.
1125 |> Enum.filter(& &1.local)
1126 |> Enum.each(&update_follower_count/1)
1132 def update_notification_settings(%User{} = user, settings) do
1135 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1138 notification_settings =
1139 user.notification_settings
1140 |> Map.merge(settings)
1141 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1143 params = %{notification_settings: notification_settings}
1146 |> cast(params, [:notification_settings])
1147 |> validate_required([:notification_settings])
1148 |> update_and_set_cache()
1151 def delete(users) when is_list(users) do
1152 for user <- users, do: delete(user)
1155 def delete(%User{} = user) do
1156 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1159 def perform(:force_password_reset, user), do: force_password_reset(user)
1161 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1162 def perform(:delete, %User{} = user) do
1163 {:ok, _user} = ActivityPub.delete(user)
1165 # Remove all relationships
1168 |> Enum.each(fn follower ->
1169 ActivityPub.unfollow(follower, user)
1170 unfollow(follower, user)
1175 |> Enum.each(fn followed ->
1176 ActivityPub.unfollow(user, followed)
1177 unfollow(user, followed)
1180 delete_user_activities(user)
1181 invalidate_cache(user)
1185 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1186 def perform(:fetch_initial_posts, %User{} = user) do
1187 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1189 # Insert all the posts in reverse order, so they're in the right order on the timeline
1190 user.source_data["outbox"]
1191 |> Utils.fetch_ordered_collection(pages)
1193 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1196 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1198 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1199 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1200 when is_list(blocked_identifiers) do
1202 blocked_identifiers,
1203 fn blocked_identifier ->
1204 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1205 {:ok, _user_block} <- block(blocker, blocked),
1206 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1210 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1217 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1218 def perform(:follow_import, %User{} = follower, followed_identifiers)
1219 when is_list(followed_identifiers) do
1221 followed_identifiers,
1222 fn followed_identifier ->
1223 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1224 {:ok, follower} <- maybe_direct_follow(follower, followed),
1225 {:ok, _} <- ActivityPub.follow(follower, followed) do
1229 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1236 @spec external_users_query() :: Ecto.Query.t()
1237 def external_users_query do
1245 @spec external_users(keyword()) :: [User.t()]
1246 def external_users(opts \\ []) do
1248 external_users_query()
1249 |> select([u], struct(u, [:id, :ap_id, :info]))
1253 do: where(query, [u], u.id > ^opts[:max_id]),
1258 do: limit(query, ^opts[:limit]),
1264 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1265 BackgroundWorker.enqueue("blocks_import", %{
1266 "blocker_id" => blocker.id,
1267 "blocked_identifiers" => blocked_identifiers
1271 def follow_import(%User{} = follower, followed_identifiers)
1272 when is_list(followed_identifiers) do
1273 BackgroundWorker.enqueue("follow_import", %{
1274 "follower_id" => follower.id,
1275 "followed_identifiers" => followed_identifiers
1279 def delete_user_activities(%User{ap_id: ap_id}) do
1281 |> Activity.Queries.by_actor()
1282 |> RepoStreamer.chunk_stream(50)
1283 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1287 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1289 |> Object.normalize()
1290 |> ActivityPub.delete()
1293 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1294 object = Object.normalize(activity)
1297 |> get_cached_by_ap_id()
1298 |> ActivityPub.unlike(object)
1301 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1302 object = Object.normalize(activity)
1305 |> get_cached_by_ap_id()
1306 |> ActivityPub.unannounce(object)
1309 defp delete_activity(_activity), do: "Doing nothing"
1311 def html_filter_policy(%User{no_rich_text: true}) do
1312 Pleroma.HTML.Scrubber.TwitterText
1315 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1317 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1319 def get_or_fetch_by_ap_id(ap_id) do
1320 user = get_cached_by_ap_id(ap_id)
1322 if !is_nil(user) and !needs_update?(user) do
1325 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1326 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1328 resp = fetch_by_ap_id(ap_id)
1330 if should_fetch_initial do
1331 with {:ok, %User{} = user} <- resp do
1332 fetch_initial_posts(user)
1340 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1341 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1342 with %User{} = user <- get_cached_by_ap_id(uri) do
1348 |> cast(%{}, [:ap_id, :nickname, :local])
1349 |> put_change(:ap_id, uri)
1350 |> put_change(:nickname, nickname)
1351 |> put_change(:local, true)
1352 |> put_change(:follower_address, uri <> "/followers")
1360 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1363 |> :public_key.pem_decode()
1365 |> :public_key.pem_entry_decode()
1370 def public_key(_), do: {:error, "not found key"}
1372 def get_public_key_for_ap_id(ap_id) do
1373 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1374 {:ok, public_key} <- public_key(user) do
1381 defp blank?(""), do: nil
1382 defp blank?(n), do: n
1384 def insert_or_update_user(data) do
1386 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1387 |> remote_user_creation()
1388 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1392 def ap_enabled?(%User{local: true}), do: true
1393 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1394 def ap_enabled?(_), do: false
1396 @doc "Gets or fetch a user by uri or nickname."
1397 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1398 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1399 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1401 # wait a period of time and return newest version of the User structs
1402 # this is because we have synchronous follow APIs and need to simulate them
1403 # with an async handshake
1404 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1405 with %User{} = a <- get_cached_by_id(a.id),
1406 %User{} = b <- get_cached_by_id(b.id) do
1413 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1414 with :ok <- :timer.sleep(timeout),
1415 %User{} = a <- get_cached_by_id(a.id),
1416 %User{} = b <- get_cached_by_id(b.id) do
1423 def parse_bio(bio) when is_binary(bio) and bio != "" do
1425 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1429 def parse_bio(_), do: ""
1431 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1432 # TODO: get profile URLs other than user.ap_id
1433 profile_urls = [user.ap_id]
1436 |> CommonUtils.format_input("text/plain",
1437 mentions_format: :full,
1438 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1443 def parse_bio(_, _), do: ""
1445 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1446 Repo.transaction(fn ->
1447 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1451 def tag(nickname, tags) when is_binary(nickname),
1452 do: tag(get_by_nickname(nickname), tags)
1454 def tag(%User{} = user, tags),
1455 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1457 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1458 Repo.transaction(fn ->
1459 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1463 def untag(nickname, tags) when is_binary(nickname),
1464 do: untag(get_by_nickname(nickname), tags)
1466 def untag(%User{} = user, tags),
1467 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1469 defp update_tags(%User{} = user, new_tags) do
1470 {:ok, updated_user} =
1472 |> change(%{tags: new_tags})
1473 |> update_and_set_cache()
1478 defp normalize_tags(tags) do
1481 |> Enum.map(&String.downcase/1)
1484 defp local_nickname_regex do
1485 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1486 @extended_local_nickname_regex
1488 @strict_local_nickname_regex
1492 def local_nickname(nickname_or_mention) do
1495 |> String.split("@")
1499 def full_nickname(nickname_or_mention),
1500 do: String.trim_leading(nickname_or_mention, "@")
1502 def error_user(ap_id) do
1506 nickname: "erroruser@example.com",
1507 inserted_at: NaiveDateTime.utc_now()
1511 @spec all_superusers() :: [User.t()]
1512 def all_superusers do
1513 User.Query.build(%{super_users: true, local: true, deactivated: false})
1517 def showing_reblogs?(%User{} = user, %User{} = target) do
1518 target.ap_id not in user.muted_reblogs
1522 The function returns a query to get users with no activity for given interval of days.
1523 Inactive users are those who didn't read any notification, or had any activity where
1524 the user is the activity's actor, during `inactivity_threshold` days.
1525 Deactivated users will not appear in this list.
1529 iex> Pleroma.User.list_inactive_users()
1532 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1533 def list_inactive_users_query(inactivity_threshold \\ 7) do
1534 negative_inactivity_threshold = -inactivity_threshold
1535 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1536 # Subqueries are not supported in `where` clauses, join gets too complicated.
1537 has_read_notifications =
1538 from(n in Pleroma.Notification,
1539 where: n.seen == true,
1541 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1544 |> Pleroma.Repo.all()
1546 from(u in Pleroma.User,
1547 left_join: a in Pleroma.Activity,
1548 on: u.ap_id == a.actor,
1549 where: not is_nil(u.nickname),
1550 where: u.deactivated != ^true,
1551 where: u.id not in ^has_read_notifications,
1554 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1555 is_nil(max(a.inserted_at))
1560 Enable or disable email notifications for user
1564 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1565 Pleroma.User{email_notifications: %{"digest" => true}}
1567 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1568 Pleroma.User{email_notifications: %{"digest" => false}}
1570 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1571 {:ok, t()} | {:error, Ecto.Changeset.t()}
1572 def switch_email_notifications(user, type, status) do
1573 User.update_email_notifications(user, %{type => status})
1577 Set `last_digest_emailed_at` value for the user to current time
1579 @spec touch_last_digest_emailed_at(t()) :: t()
1580 def touch_last_digest_emailed_at(user) do
1581 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1583 {:ok, updated_user} =
1585 |> change(%{last_digest_emailed_at: now})
1586 |> update_and_set_cache()
1591 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1592 def toggle_confirmation(%User{} = user) do
1594 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1595 |> update_and_set_cache()
1598 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1602 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1603 # use instance-default
1604 config = Pleroma.Config.get([:assets, :mascots])
1605 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1606 mascot = Keyword.get(config, default_mascot)
1609 "id" => "default-mascot",
1610 "url" => mascot[:url],
1611 "preview_url" => mascot[:url],
1613 "mime_type" => mascot[:mime_type]
1618 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1620 def ensure_keys_present(%User{} = user) do
1621 with {:ok, pem} <- Keys.generate_rsa_pem() do
1623 |> cast(%{keys: pem}, [:keys])
1624 |> validate_required([:keys])
1625 |> update_and_set_cache()
1629 def get_ap_ids_by_nicknames(nicknames) do
1631 where: u.nickname in ^nicknames,
1637 defdelegate search(query, opts \\ []), to: User.Search
1639 defp put_password_hash(
1640 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1642 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1645 defp put_password_hash(changeset), do: changeset
1647 def is_internal_user?(%User{nickname: nil}), do: true
1648 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1649 def is_internal_user?(_), do: false
1651 # A hack because user delete activities have a fake id for whatever reason
1652 # TODO: Get rid of this
1653 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1655 def get_delivered_users_by_object_id(object_id) do
1657 inner_join: delivery in assoc(u, :deliveries),
1658 where: delivery.object_id == ^object_id
1663 def change_email(user, email) do
1665 |> cast(%{email: email}, [:email])
1666 |> validate_required([:email])
1667 |> unique_constraint(:email)
1668 |> validate_format(:email, @email_regex)
1669 |> update_and_set_cache()
1672 # Internal function; public one is `deactivate/2`
1673 defp set_activation_status(user, deactivated) do
1675 |> cast(%{deactivated: deactivated}, [:deactivated])
1676 |> update_and_set_cache()
1679 def update_banner(user, banner) do
1681 |> cast(%{banner: banner}, [:banner])
1682 |> update_and_set_cache()
1685 def update_background(user, background) do
1687 |> cast(%{background: background}, [:background])
1688 |> update_and_set_cache()
1691 def update_source_data(user, source_data) do
1693 |> cast(%{source_data: source_data}, [:source_data])
1694 |> update_and_set_cache()
1697 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1700 moderator: is_moderator
1704 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1705 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1706 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1707 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1710 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1711 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1715 def fields(%{fields: nil}), do: []
1717 def fields(%{fields: fields}), do: fields
1719 def validate_fields(changeset, remote? \\ false) do
1720 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1721 limit = Pleroma.Config.get([:instance, limit_name], 0)
1724 |> validate_length(:fields, max: limit)
1725 |> validate_change(:fields, fn :fields, fields ->
1726 if Enum.all?(fields, &valid_field?/1) do
1734 defp valid_field?(%{"name" => name, "value" => value}) do
1735 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1736 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1738 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1739 String.length(value) <= value_limit
1742 defp valid_field?(_), do: false
1744 defp truncate_field(%{"name" => name, "value" => value}) do
1746 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1749 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1751 %{"name" => name, "value" => value}
1754 def admin_api_update(user, params) do
1761 |> update_and_set_cache()
1764 def mascot_update(user, url) do
1766 |> cast(%{mascot: url}, [:mascot])
1767 |> validate_required([:mascot])
1768 |> update_and_set_cache()
1771 def mastodon_settings_update(user, settings) do
1773 |> cast(%{settings: settings}, [:settings])
1774 |> validate_required([:settings])
1775 |> update_and_set_cache()
1778 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1779 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1781 if need_confirmation? do
1783 confirmation_pending: true,
1784 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1788 confirmation_pending: false,
1789 confirmation_token: nil
1793 cast(user, params, [:confirmation_pending, :confirmation_token])
1796 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1797 if id not in user.pinned_activities do
1798 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1799 params = %{pinned_activities: user.pinned_activities ++ [id]}
1802 |> cast(params, [:pinned_activities])
1803 |> validate_length(:pinned_activities,
1804 max: max_pinned_statuses,
1805 message: "You have already pinned the maximum number of statuses"
1810 |> update_and_set_cache()
1813 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1814 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1817 |> cast(params, [:pinned_activities])
1818 |> update_and_set_cache()
1821 def update_email_notifications(user, settings) do
1822 email_notifications =
1823 user.email_notifications
1824 |> Map.merge(settings)
1825 |> Map.take(["digest"])
1827 params = %{email_notifications: email_notifications}
1828 fields = [:email_notifications]
1831 |> cast(params, fields)
1832 |> validate_required(fields)
1833 |> update_and_set_cache()
1836 defp set_subscribers(user, subscribers) do
1837 params = %{subscribers: subscribers}
1840 |> cast(params, [:subscribers])
1841 |> validate_required([:subscribers])
1842 |> update_and_set_cache()
1845 def add_to_subscribers(user, subscribed) do
1846 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1849 def remove_from_subscribers(user, subscribed) do
1850 set_subscribers(user, List.delete(user.subscribers, subscribed))
1853 defp set_domain_blocks(user, domain_blocks) do
1854 params = %{domain_blocks: domain_blocks}
1857 |> cast(params, [:domain_blocks])
1858 |> validate_required([:domain_blocks])
1859 |> update_and_set_cache()
1862 def block_domain(user, domain_blocked) do
1863 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1866 def unblock_domain(user, domain_blocked) do
1867 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1870 @spec add_to_block(User.t(), User.t()) :: {:ok, UserBlock.t()} | {:error, Ecto.Changeset.t()}
1871 defp add_to_block(%User{} = user, %User{} = blocked) do
1872 UserBlock.create(user, blocked)
1875 @spec add_to_block(User.t(), User.t()) ::
1876 {:ok, UserBlock.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
1877 defp remove_from_block(%User{} = user, %User{} = blocked) do
1878 UserBlock.delete(user, blocked)
1881 defp set_mutes(user, mutes) do
1882 params = %{mutes: mutes}
1885 |> cast(params, [:mutes])
1886 |> validate_required([:mutes])
1887 |> update_and_set_cache()
1890 def add_to_mutes(user, muted, notifications?) do
1891 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1892 set_notification_mutes(
1894 Enum.uniq([muted | user.muted_notifications]),
1900 def remove_from_mutes(user, muted) do
1901 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1902 set_notification_mutes(
1904 List.delete(user.muted_notifications, muted),
1910 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1914 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1915 params = %{muted_notifications: muted_notifications}
1918 |> cast(params, [:muted_notifications])
1919 |> validate_required([:muted_notifications])
1920 |> update_and_set_cache()
1923 def add_reblog_mute(user, ap_id) do
1924 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1927 |> cast(params, [:muted_reblogs])
1928 |> update_and_set_cache()
1931 def remove_reblog_mute(user, ap_id) do
1932 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1935 |> cast(params, [:muted_reblogs])
1936 |> update_and_set_cache()
1939 def set_invisible(user, invisible) do
1940 params = %{invisible: invisible}
1943 |> cast(params, [:invisible])
1944 |> validate_required([:invisible])
1945 |> update_and_set_cache()