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 |> assoc(:blocked_users)
1095 |> select([u], u.ap_id)
1099 @spec subscribers(User.t()) :: [User.t()]
1100 def subscribers(user) do
1101 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1105 def deactivate_async(user, status \\ true) do
1106 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1109 def deactivate(user, status \\ true)
1111 def deactivate(users, status) when is_list(users) do
1112 Repo.transaction(fn ->
1113 for user <- users, do: deactivate(user, status)
1117 def deactivate(%User{} = user, status) do
1118 with {:ok, user} <- set_activation_status(user, status) do
1119 Enum.each(get_followers(user), &invalidate_cache/1)
1121 # Only update local user counts, remote will be update during the next pull.
1124 |> Enum.filter(& &1.local)
1125 |> Enum.each(&update_follower_count/1)
1131 def update_notification_settings(%User{} = user, settings) do
1134 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1137 notification_settings =
1138 user.notification_settings
1139 |> Map.merge(settings)
1140 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1142 params = %{notification_settings: notification_settings}
1145 |> cast(params, [:notification_settings])
1146 |> validate_required([:notification_settings])
1147 |> update_and_set_cache()
1150 def delete(users) when is_list(users) do
1151 for user <- users, do: delete(user)
1154 def delete(%User{} = user) do
1155 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1158 def perform(:force_password_reset, user), do: force_password_reset(user)
1160 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1161 def perform(:delete, %User{} = user) do
1162 {:ok, _user} = ActivityPub.delete(user)
1164 # Remove all relationships
1167 |> Enum.each(fn follower ->
1168 ActivityPub.unfollow(follower, user)
1169 unfollow(follower, user)
1174 |> Enum.each(fn followed ->
1175 ActivityPub.unfollow(user, followed)
1176 unfollow(user, followed)
1179 delete_user_activities(user)
1180 invalidate_cache(user)
1184 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1185 def perform(:fetch_initial_posts, %User{} = user) do
1186 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1188 # Insert all the posts in reverse order, so they're in the right order on the timeline
1189 user.source_data["outbox"]
1190 |> Utils.fetch_ordered_collection(pages)
1192 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1195 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1197 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1198 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1199 when is_list(blocked_identifiers) do
1201 blocked_identifiers,
1202 fn blocked_identifier ->
1203 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1204 {:ok, _user_block} <- block(blocker, blocked),
1205 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1209 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1216 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1217 def perform(:follow_import, %User{} = follower, followed_identifiers)
1218 when is_list(followed_identifiers) do
1220 followed_identifiers,
1221 fn followed_identifier ->
1222 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1223 {:ok, follower} <- maybe_direct_follow(follower, followed),
1224 {:ok, _} <- ActivityPub.follow(follower, followed) do
1228 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1235 @spec external_users_query() :: Ecto.Query.t()
1236 def external_users_query do
1244 @spec external_users(keyword()) :: [User.t()]
1245 def external_users(opts \\ []) do
1247 external_users_query()
1248 |> select([u], struct(u, [:id, :ap_id, :info]))
1252 do: where(query, [u], u.id > ^opts[:max_id]),
1257 do: limit(query, ^opts[:limit]),
1263 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1264 BackgroundWorker.enqueue("blocks_import", %{
1265 "blocker_id" => blocker.id,
1266 "blocked_identifiers" => blocked_identifiers
1270 def follow_import(%User{} = follower, followed_identifiers)
1271 when is_list(followed_identifiers) do
1272 BackgroundWorker.enqueue("follow_import", %{
1273 "follower_id" => follower.id,
1274 "followed_identifiers" => followed_identifiers
1278 def delete_user_activities(%User{ap_id: ap_id}) do
1280 |> Activity.Queries.by_actor()
1281 |> RepoStreamer.chunk_stream(50)
1282 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1286 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1288 |> Object.normalize()
1289 |> ActivityPub.delete()
1292 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1293 object = Object.normalize(activity)
1296 |> get_cached_by_ap_id()
1297 |> ActivityPub.unlike(object)
1300 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1301 object = Object.normalize(activity)
1304 |> get_cached_by_ap_id()
1305 |> ActivityPub.unannounce(object)
1308 defp delete_activity(_activity), do: "Doing nothing"
1310 def html_filter_policy(%User{no_rich_text: true}) do
1311 Pleroma.HTML.Scrubber.TwitterText
1314 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1316 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1318 def get_or_fetch_by_ap_id(ap_id) do
1319 user = get_cached_by_ap_id(ap_id)
1321 if !is_nil(user) and !needs_update?(user) do
1324 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1325 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1327 resp = fetch_by_ap_id(ap_id)
1329 if should_fetch_initial do
1330 with {:ok, %User{} = user} <- resp do
1331 fetch_initial_posts(user)
1339 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1340 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1341 with %User{} = user <- get_cached_by_ap_id(uri) do
1347 |> cast(%{}, [:ap_id, :nickname, :local])
1348 |> put_change(:ap_id, uri)
1349 |> put_change(:nickname, nickname)
1350 |> put_change(:local, true)
1351 |> put_change(:follower_address, uri <> "/followers")
1359 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1362 |> :public_key.pem_decode()
1364 |> :public_key.pem_entry_decode()
1369 def public_key(_), do: {:error, "not found key"}
1371 def get_public_key_for_ap_id(ap_id) do
1372 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1373 {:ok, public_key} <- public_key(user) do
1380 defp blank?(""), do: nil
1381 defp blank?(n), do: n
1383 def insert_or_update_user(data) do
1385 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1386 |> remote_user_creation()
1387 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1391 def ap_enabled?(%User{local: true}), do: true
1392 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1393 def ap_enabled?(_), do: false
1395 @doc "Gets or fetch a user by uri or nickname."
1396 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1397 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1398 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1400 # wait a period of time and return newest version of the User structs
1401 # this is because we have synchronous follow APIs and need to simulate them
1402 # with an async handshake
1403 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1404 with %User{} = a <- get_cached_by_id(a.id),
1405 %User{} = b <- get_cached_by_id(b.id) do
1412 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1413 with :ok <- :timer.sleep(timeout),
1414 %User{} = a <- get_cached_by_id(a.id),
1415 %User{} = b <- get_cached_by_id(b.id) do
1422 def parse_bio(bio) when is_binary(bio) and bio != "" do
1424 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1428 def parse_bio(_), do: ""
1430 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1431 # TODO: get profile URLs other than user.ap_id
1432 profile_urls = [user.ap_id]
1435 |> CommonUtils.format_input("text/plain",
1436 mentions_format: :full,
1437 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1442 def parse_bio(_, _), do: ""
1444 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1445 Repo.transaction(fn ->
1446 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1450 def tag(nickname, tags) when is_binary(nickname),
1451 do: tag(get_by_nickname(nickname), tags)
1453 def tag(%User{} = user, tags),
1454 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1456 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1457 Repo.transaction(fn ->
1458 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1462 def untag(nickname, tags) when is_binary(nickname),
1463 do: untag(get_by_nickname(nickname), tags)
1465 def untag(%User{} = user, tags),
1466 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1468 defp update_tags(%User{} = user, new_tags) do
1469 {:ok, updated_user} =
1471 |> change(%{tags: new_tags})
1472 |> update_and_set_cache()
1477 defp normalize_tags(tags) do
1480 |> Enum.map(&String.downcase/1)
1483 defp local_nickname_regex do
1484 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1485 @extended_local_nickname_regex
1487 @strict_local_nickname_regex
1491 def local_nickname(nickname_or_mention) do
1494 |> String.split("@")
1498 def full_nickname(nickname_or_mention),
1499 do: String.trim_leading(nickname_or_mention, "@")
1501 def error_user(ap_id) do
1505 nickname: "erroruser@example.com",
1506 inserted_at: NaiveDateTime.utc_now()
1510 @spec all_superusers() :: [User.t()]
1511 def all_superusers do
1512 User.Query.build(%{super_users: true, local: true, deactivated: false})
1516 def showing_reblogs?(%User{} = user, %User{} = target) do
1517 target.ap_id not in user.muted_reblogs
1521 The function returns a query to get users with no activity for given interval of days.
1522 Inactive users are those who didn't read any notification, or had any activity where
1523 the user is the activity's actor, during `inactivity_threshold` days.
1524 Deactivated users will not appear in this list.
1528 iex> Pleroma.User.list_inactive_users()
1531 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1532 def list_inactive_users_query(inactivity_threshold \\ 7) do
1533 negative_inactivity_threshold = -inactivity_threshold
1534 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1535 # Subqueries are not supported in `where` clauses, join gets too complicated.
1536 has_read_notifications =
1537 from(n in Pleroma.Notification,
1538 where: n.seen == true,
1540 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1543 |> Pleroma.Repo.all()
1545 from(u in Pleroma.User,
1546 left_join: a in Pleroma.Activity,
1547 on: u.ap_id == a.actor,
1548 where: not is_nil(u.nickname),
1549 where: u.deactivated != ^true,
1550 where: u.id not in ^has_read_notifications,
1553 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1554 is_nil(max(a.inserted_at))
1559 Enable or disable email notifications for user
1563 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1564 Pleroma.User{email_notifications: %{"digest" => true}}
1566 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1567 Pleroma.User{email_notifications: %{"digest" => false}}
1569 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1570 {:ok, t()} | {:error, Ecto.Changeset.t()}
1571 def switch_email_notifications(user, type, status) do
1572 User.update_email_notifications(user, %{type => status})
1576 Set `last_digest_emailed_at` value for the user to current time
1578 @spec touch_last_digest_emailed_at(t()) :: t()
1579 def touch_last_digest_emailed_at(user) do
1580 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1582 {:ok, updated_user} =
1584 |> change(%{last_digest_emailed_at: now})
1585 |> update_and_set_cache()
1590 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1591 def toggle_confirmation(%User{} = user) do
1593 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1594 |> update_and_set_cache()
1597 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1601 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1602 # use instance-default
1603 config = Pleroma.Config.get([:assets, :mascots])
1604 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1605 mascot = Keyword.get(config, default_mascot)
1608 "id" => "default-mascot",
1609 "url" => mascot[:url],
1610 "preview_url" => mascot[:url],
1612 "mime_type" => mascot[:mime_type]
1617 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1619 def ensure_keys_present(%User{} = user) do
1620 with {:ok, pem} <- Keys.generate_rsa_pem() do
1622 |> cast(%{keys: pem}, [:keys])
1623 |> validate_required([:keys])
1624 |> update_and_set_cache()
1628 def get_ap_ids_by_nicknames(nicknames) do
1630 where: u.nickname in ^nicknames,
1636 defdelegate search(query, opts \\ []), to: User.Search
1638 defp put_password_hash(
1639 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1641 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1644 defp put_password_hash(changeset), do: changeset
1646 def is_internal_user?(%User{nickname: nil}), do: true
1647 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1648 def is_internal_user?(_), do: false
1650 # A hack because user delete activities have a fake id for whatever reason
1651 # TODO: Get rid of this
1652 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1654 def get_delivered_users_by_object_id(object_id) do
1656 inner_join: delivery in assoc(u, :deliveries),
1657 where: delivery.object_id == ^object_id
1662 def change_email(user, email) do
1664 |> cast(%{email: email}, [:email])
1665 |> validate_required([:email])
1666 |> unique_constraint(:email)
1667 |> validate_format(:email, @email_regex)
1668 |> update_and_set_cache()
1671 # Internal function; public one is `deactivate/2`
1672 defp set_activation_status(user, deactivated) do
1674 |> cast(%{deactivated: deactivated}, [:deactivated])
1675 |> update_and_set_cache()
1678 def update_banner(user, banner) do
1680 |> cast(%{banner: banner}, [:banner])
1681 |> update_and_set_cache()
1684 def update_background(user, background) do
1686 |> cast(%{background: background}, [:background])
1687 |> update_and_set_cache()
1690 def update_source_data(user, source_data) do
1692 |> cast(%{source_data: source_data}, [:source_data])
1693 |> update_and_set_cache()
1696 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1699 moderator: is_moderator
1703 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1704 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1705 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1706 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1709 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1710 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1714 def fields(%{fields: nil}), do: []
1716 def fields(%{fields: fields}), do: fields
1718 def validate_fields(changeset, remote? \\ false) do
1719 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1720 limit = Pleroma.Config.get([:instance, limit_name], 0)
1723 |> validate_length(:fields, max: limit)
1724 |> validate_change(:fields, fn :fields, fields ->
1725 if Enum.all?(fields, &valid_field?/1) do
1733 defp valid_field?(%{"name" => name, "value" => value}) do
1734 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1735 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1737 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1738 String.length(value) <= value_limit
1741 defp valid_field?(_), do: false
1743 defp truncate_field(%{"name" => name, "value" => value}) do
1745 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1748 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1750 %{"name" => name, "value" => value}
1753 def admin_api_update(user, params) do
1760 |> update_and_set_cache()
1763 def mascot_update(user, url) do
1765 |> cast(%{mascot: url}, [:mascot])
1766 |> validate_required([:mascot])
1767 |> update_and_set_cache()
1770 def mastodon_settings_update(user, settings) do
1772 |> cast(%{settings: settings}, [:settings])
1773 |> validate_required([:settings])
1774 |> update_and_set_cache()
1777 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1778 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1780 if need_confirmation? do
1782 confirmation_pending: true,
1783 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1787 confirmation_pending: false,
1788 confirmation_token: nil
1792 cast(user, params, [:confirmation_pending, :confirmation_token])
1795 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1796 if id not in user.pinned_activities do
1797 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1798 params = %{pinned_activities: user.pinned_activities ++ [id]}
1801 |> cast(params, [:pinned_activities])
1802 |> validate_length(:pinned_activities,
1803 max: max_pinned_statuses,
1804 message: "You have already pinned the maximum number of statuses"
1809 |> update_and_set_cache()
1812 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1813 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1816 |> cast(params, [:pinned_activities])
1817 |> update_and_set_cache()
1820 def update_email_notifications(user, settings) do
1821 email_notifications =
1822 user.email_notifications
1823 |> Map.merge(settings)
1824 |> Map.take(["digest"])
1826 params = %{email_notifications: email_notifications}
1827 fields = [:email_notifications]
1830 |> cast(params, fields)
1831 |> validate_required(fields)
1832 |> update_and_set_cache()
1835 defp set_subscribers(user, subscribers) do
1836 params = %{subscribers: subscribers}
1839 |> cast(params, [:subscribers])
1840 |> validate_required([:subscribers])
1841 |> update_and_set_cache()
1844 def add_to_subscribers(user, subscribed) do
1845 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1848 def remove_from_subscribers(user, subscribed) do
1849 set_subscribers(user, List.delete(user.subscribers, subscribed))
1852 defp set_domain_blocks(user, domain_blocks) do
1853 params = %{domain_blocks: domain_blocks}
1856 |> cast(params, [:domain_blocks])
1857 |> validate_required([:domain_blocks])
1858 |> update_and_set_cache()
1861 def block_domain(user, domain_blocked) do
1862 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1865 def unblock_domain(user, domain_blocked) do
1866 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1869 @spec add_to_block(User.t(), User.t()) :: {:ok, UserBlock.t()} | {:error, Ecto.Changeset.t()}
1870 defp add_to_block(%User{} = user, %User{} = blocked) do
1871 UserBlock.create(user, blocked)
1874 @spec add_to_block(User.t(), User.t()) ::
1875 {:ok, UserBlock.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
1876 defp remove_from_block(%User{} = user, %User{} = blocked) do
1877 UserBlock.delete(user, blocked)
1880 defp set_mutes(user, mutes) do
1881 params = %{mutes: mutes}
1884 |> cast(params, [:mutes])
1885 |> validate_required([:mutes])
1886 |> update_and_set_cache()
1889 def add_to_mutes(user, muted, notifications?) do
1890 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1891 set_notification_mutes(
1893 Enum.uniq([muted | user.muted_notifications]),
1899 def remove_from_mutes(user, muted) do
1900 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1901 set_notification_mutes(
1903 List.delete(user.muted_notifications, muted),
1909 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1913 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1914 params = %{muted_notifications: muted_notifications}
1917 |> cast(params, [:muted_notifications])
1918 |> validate_required([:muted_notifications])
1919 |> update_and_set_cache()
1922 def add_reblog_mute(user, ap_id) do
1923 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1926 |> cast(params, [:muted_reblogs])
1927 |> update_and_set_cache()
1930 def remove_reblog_mute(user, ap_id) do
1931 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1934 |> cast(params, [:muted_reblogs])
1935 |> update_and_set_cache()
1938 def set_invisible(user, invisible) do
1939 params = %{invisible: invisible}
1942 |> cast(params, [:invisible])
1943 |> validate_required([:invisible])
1944 |> update_and_set_cache()