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
26 alias Pleroma.UserMute
28 alias Pleroma.Web.ActivityPub.ActivityPub
29 alias Pleroma.Web.ActivityPub.Utils
30 alias Pleroma.Web.CommonAPI
31 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
32 alias Pleroma.Web.OAuth
33 alias Pleroma.Web.RelMe
34 alias Pleroma.Workers.BackgroundWorker
38 @type t :: %__MODULE__{}
40 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
42 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
43 @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])?)*$/
45 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
46 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
50 field(:email, :string)
52 field(:nickname, :string)
53 field(:password_hash, :string)
54 field(:password, :string, virtual: true)
55 field(:password_confirmation, :string, virtual: true)
57 field(:ap_id, :string)
59 field(:local, :boolean, default: true)
60 field(:follower_address, :string)
61 field(:following_address, :string)
62 field(:search_rank, :float, virtual: true)
63 field(:search_type, :integer, virtual: true)
64 field(:tags, {:array, :string}, default: [])
65 field(:last_refreshed_at, :naive_datetime_usec)
66 field(:last_digest_emailed_at, :naive_datetime)
68 field(:banner, :map, default: %{})
69 field(:background, :map, default: %{})
70 field(:source_data, :map, default: %{})
71 field(:note_count, :integer, default: 0)
72 field(:follower_count, :integer, default: 0)
73 # Should be filled in only for remote users
74 field(:following_count, :integer, default: nil)
75 field(:locked, :boolean, default: false)
76 field(:confirmation_pending, :boolean, default: false)
77 field(:password_reset_pending, :boolean, default: false)
78 field(:confirmation_token, :string, default: nil)
79 field(:default_scope, :string, default: "public")
80 field(:domain_blocks, {:array, :string}, default: [])
81 field(:mutes, {:array, :string}, default: [])
82 field(:muted_reblogs, {:array, :string}, default: [])
83 field(:muted_notifications, {:array, :string}, default: [])
84 field(:subscribers, {:array, :string}, default: [])
85 field(:deactivated, :boolean, default: false)
86 field(:no_rich_text, :boolean, default: false)
87 field(:ap_enabled, :boolean, default: false)
88 field(:is_moderator, :boolean, default: false)
89 field(:is_admin, :boolean, default: false)
90 field(:show_role, :boolean, default: true)
91 field(:settings, :map, default: nil)
92 field(:magic_key, :string, default: nil)
93 field(:uri, :string, default: nil)
94 field(:hide_followers_count, :boolean, default: false)
95 field(:hide_follows_count, :boolean, default: false)
96 field(:hide_followers, :boolean, default: false)
97 field(:hide_follows, :boolean, default: false)
98 field(:hide_favorites, :boolean, default: true)
99 field(:unread_conversation_count, :integer, default: 0)
100 field(:pinned_activities, {:array, :string}, default: [])
101 field(:email_notifications, :map, default: %{"digest" => false})
102 field(:mascot, :map, default: nil)
103 field(:emoji, {:array, :map}, default: [])
104 field(:pleroma_settings_store, :map, default: %{})
105 field(:fields, {:array, :map}, default: [])
106 field(:raw_fields, {:array, :map}, default: [])
107 field(:discoverable, :boolean, default: false)
108 field(:invisible, :boolean, default: false)
109 field(:skip_thread_containment, :boolean, default: false)
111 field(:notification_settings, :map,
115 "non_follows" => true,
116 "non_followers" => true
120 has_many(:notifications, Notification)
121 has_many(:registrations, Registration)
122 has_many(:deliveries, Delivery)
124 has_many(:blocker_blocks, UserBlock, foreign_key: :blocker_id)
125 has_many(:blockee_blocks, UserBlock, foreign_key: :blockee_id)
126 has_many(:blocked_users, through: [:blocker_blocks, :blockee])
127 has_many(:blocker_users, through: [:blockee_blocks, :blocker])
129 has_many(:muter_mutes, UserMute, foreign_key: :muter_id)
130 has_many(:mutee_mutes, UserMute, foreign_key: :mutee_id)
131 has_many(:muted_users, through: [:muter_mutes, :mutee])
132 has_many(:muter_users, through: [:mutee_mutes, :muter])
134 field(:info, :map, default: %{})
136 # `:blocks` is deprecated (replaced with `blocked_users` relation)
137 field(:blocks, {:array, :string}, default: [])
142 @doc "Returns if the user should be allowed to authenticate"
143 def auth_active?(%User{deactivated: true}), do: false
145 def auth_active?(%User{confirmation_pending: true}),
146 do: !Pleroma.Config.get([:instance, :account_activation_required])
148 def auth_active?(%User{}), do: true
150 def visible_for?(user, for_user \\ nil)
152 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
154 def visible_for?(%User{} = user, for_user) do
155 auth_active?(user) || superuser?(for_user)
158 def visible_for?(_, _), do: false
160 def superuser?(%User{local: true, is_admin: true}), do: true
161 def superuser?(%User{local: true, is_moderator: true}), do: true
162 def superuser?(_), do: false
164 def invisible?(%User{invisible: true}), do: true
165 def invisible?(_), do: false
167 def avatar_url(user, options \\ []) do
169 %{"url" => [%{"href" => href} | _]} -> href
170 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
174 def banner_url(user, options \\ []) do
176 %{"url" => [%{"href" => href} | _]} -> href
177 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
181 def profile_url(%User{source_data: %{"url" => url}}), do: url
182 def profile_url(%User{ap_id: ap_id}), do: ap_id
183 def profile_url(_), do: nil
185 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
187 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
188 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
190 @spec ap_following(User.t()) :: Sring.t()
191 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
192 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
194 def user_info(%User{} = user, args \\ %{}) do
196 Map.get(args, :following_count, user.following_count || following_count(user))
198 follower_count = Map.get(args, :follower_count, user.follower_count)
201 note_count: user.note_count,
203 confirmation_pending: user.confirmation_pending,
204 default_scope: user.default_scope
206 |> Map.put(:following_count, following_count)
207 |> Map.put(:follower_count, follower_count)
210 def follow_state(%User{} = user, %User{} = target) do
211 case Utils.fetch_latest_follow(user, target) do
212 %{data: %{"state" => state}} -> state
213 # Ideally this would be nil, but then Cachex does not commit the value
218 def get_cached_follow_state(user, target) do
219 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
220 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
223 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
224 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
225 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
228 def set_info_cache(user, args) do
229 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
232 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
233 def restrict_deactivated(query) do
234 from(u in query, where: u.deactivated != ^true)
237 defdelegate following_count(user), to: FollowingRelationship
239 defp truncate_fields_param(params) do
240 if Map.has_key?(params, :fields) do
241 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
247 defp truncate_if_exists(params, key, max_length) do
248 if Map.has_key?(params, key) and is_binary(params[key]) do
249 {value, _chopped} = String.split_at(params[key], max_length)
250 Map.put(params, key, value)
256 def remote_user_creation(params) do
257 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
258 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
262 |> Map.put(:info, params[:info] || %{})
263 |> truncate_if_exists(:name, name_limit)
264 |> truncate_if_exists(:bio, bio_limit)
265 |> truncate_fields_param()
285 :hide_followers_count,
294 |> validate_required([:name, :ap_id])
295 |> unique_constraint(:nickname)
296 |> validate_format(:nickname, @email_regex)
297 |> validate_length(:bio, max: bio_limit)
298 |> validate_length(:name, max: name_limit)
299 |> validate_fields(true)
301 case params[:source_data] do
302 %{"followers" => followers, "following" => following} ->
304 |> put_change(:follower_address, followers)
305 |> put_change(:following_address, following)
308 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
309 put_change(changeset, :follower_address, followers)
313 def update_changeset(struct, params \\ %{}) do
314 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
315 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
330 :hide_followers_count,
335 :skip_thread_containment,
338 :pleroma_settings_store,
342 |> unique_constraint(:nickname)
343 |> validate_format(:nickname, local_nickname_regex())
344 |> validate_length(:bio, max: bio_limit)
345 |> validate_length(:name, min: 1, max: name_limit)
346 |> validate_fields(false)
349 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
350 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
351 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
353 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
355 params = if remote?, do: truncate_fields_param(params), else: params
378 :hide_followers_count,
382 |> unique_constraint(:nickname)
383 |> validate_format(:nickname, local_nickname_regex())
384 |> validate_length(:bio, max: bio_limit)
385 |> validate_length(:name, max: name_limit)
386 |> validate_fields(remote?)
389 def password_update_changeset(struct, params) do
391 |> cast(params, [:password, :password_confirmation])
392 |> validate_required([:password, :password_confirmation])
393 |> validate_confirmation(:password)
394 |> put_password_hash()
395 |> put_change(:password_reset_pending, false)
398 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
399 def reset_password(%User{id: user_id} = user, data) do
402 |> Multi.update(:user, password_update_changeset(user, data))
403 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
404 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
406 case Repo.transaction(multi) do
407 {:ok, %{user: user} = _} -> set_cache(user)
408 {:error, _, changeset, _} -> {:error, changeset}
412 def update_password_reset_pending(user, value) do
415 |> put_change(:password_reset_pending, value)
416 |> update_and_set_cache()
419 def force_password_reset_async(user) do
420 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
423 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
424 def force_password_reset(user), do: update_password_reset_pending(user, true)
426 def register_changeset(struct, params \\ %{}, opts \\ []) do
427 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
428 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
431 if is_nil(opts[:need_confirmation]) do
432 Pleroma.Config.get([:instance, :account_activation_required])
434 opts[:need_confirmation]
438 |> confirmation_changeset(need_confirmation: need_confirmation?)
439 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
440 |> validate_required([:name, :nickname, :password, :password_confirmation])
441 |> validate_confirmation(:password)
442 |> unique_constraint(:email)
443 |> unique_constraint(:nickname)
444 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
445 |> validate_format(:nickname, local_nickname_regex())
446 |> validate_format(:email, @email_regex)
447 |> validate_length(:bio, max: bio_limit)
448 |> validate_length(:name, min: 1, max: name_limit)
449 |> maybe_validate_required_email(opts[:external])
452 |> unique_constraint(:ap_id)
453 |> put_following_and_follower_address()
456 def maybe_validate_required_email(changeset, true), do: changeset
457 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
459 defp put_ap_id(changeset) do
460 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
461 put_change(changeset, :ap_id, ap_id)
464 defp put_following_and_follower_address(changeset) do
465 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
468 |> put_change(:follower_address, followers)
471 defp autofollow_users(user) do
472 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
475 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
478 follow_all(user, autofollowed_users)
481 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
482 def register(%Ecto.Changeset{} = changeset) do
483 with {:ok, user} <- Repo.insert(changeset) do
484 post_register_action(user)
488 def post_register_action(%User{} = user) do
489 with {:ok, user} <- autofollow_users(user),
490 {:ok, user} <- set_cache(user),
491 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
492 {:ok, _} <- try_send_confirmation_email(user) do
497 def try_send_confirmation_email(%User{} = user) do
498 if user.confirmation_pending &&
499 Pleroma.Config.get([:instance, :account_activation_required]) do
501 |> Pleroma.Emails.UserEmail.account_confirmation_email()
502 |> Pleroma.Emails.Mailer.deliver_async()
510 def needs_update?(%User{local: true}), do: false
512 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
514 def needs_update?(%User{local: false} = user) do
515 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
518 def needs_update?(_), do: true
520 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
521 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
522 follow(follower, followed, "pending")
525 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
526 follow(follower, followed)
529 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
530 if not ap_enabled?(followed) do
531 follow(follower, followed)
537 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
538 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
539 def follow_all(follower, followeds) do
541 Enum.reject(followeds, fn followed ->
542 blocks?(follower, followed) || blocks?(followed, follower)
545 Enum.each(followeds, &follow(follower, &1, "accept"))
547 Enum.each(followeds, &update_follower_count/1)
552 defdelegate following(user), to: FollowingRelationship
554 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
555 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
558 followed.deactivated ->
559 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
561 deny_follow_blocked and blocks?(followed, follower) ->
562 {:error, "Could not follow user: #{followed.nickname} blocked you."}
565 FollowingRelationship.follow(follower, followed, state)
567 follower = maybe_update_following_count(follower)
569 {:ok, _} = update_follower_count(followed)
575 def unfollow(%User{} = follower, %User{} = followed) do
576 if following?(follower, followed) and follower.ap_id != followed.ap_id do
577 FollowingRelationship.unfollow(follower, followed)
579 follower = maybe_update_following_count(follower)
581 {:ok, followed} = update_follower_count(followed)
585 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
587 {:error, "Not subscribed!"}
591 defdelegate following?(follower, followed), to: FollowingRelationship
593 def locked?(%User{} = user) do
598 Repo.get_by(User, id: id)
601 def get_by_ap_id(ap_id) do
602 Repo.get_by(User, ap_id: ap_id)
605 def get_all_by_ap_id(ap_ids) do
606 from(u in __MODULE__,
607 where: u.ap_id in ^ap_ids
612 def get_all_by_ids(ids) do
613 from(u in __MODULE__, where: u.id in ^ids)
617 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
618 # of the ap_id and the domain and tries to get that user
619 def get_by_guessed_nickname(ap_id) do
620 domain = URI.parse(ap_id).host
621 name = List.last(String.split(ap_id, "/"))
622 nickname = "#{name}@#{domain}"
624 get_cached_by_nickname(nickname)
627 def set_cache({:ok, user}), do: set_cache(user)
628 def set_cache({:error, err}), do: {:error, err}
630 def set_cache(%User{} = user) do
631 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
632 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
633 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
637 def update_and_set_cache(struct, params) do
639 |> update_changeset(params)
640 |> update_and_set_cache()
643 def update_and_set_cache(changeset) do
644 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
649 def invalidate_cache(user) do
650 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
651 Cachex.del(:user_cache, "nickname:#{user.nickname}")
652 Cachex.del(:user_cache, "user_info:#{user.id}")
655 def get_cached_by_ap_id(ap_id) do
656 key = "ap_id:#{ap_id}"
657 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
660 def get_cached_by_id(id) do
664 Cachex.fetch!(:user_cache, key, fn _ ->
668 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
669 {:commit, user.ap_id}
675 get_cached_by_ap_id(ap_id)
678 def get_cached_by_nickname(nickname) do
679 key = "nickname:#{nickname}"
681 Cachex.fetch!(:user_cache, key, fn ->
682 case get_or_fetch_by_nickname(nickname) do
683 {:ok, user} -> {:commit, user}
684 {:error, _error} -> {:ignore, nil}
689 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
690 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
693 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
694 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
696 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
697 get_cached_by_nickname(nickname_or_id)
699 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
700 get_cached_by_nickname(nickname_or_id)
707 def get_by_nickname(nickname) do
708 Repo.get_by(User, nickname: nickname) ||
709 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
710 Repo.get_by(User, nickname: local_nickname(nickname))
714 def get_by_email(email), do: Repo.get_by(User, email: email)
716 def get_by_nickname_or_email(nickname_or_email) do
717 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
720 def get_cached_user_info(user) do
721 key = "user_info:#{user.id}"
722 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
725 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
727 def get_or_fetch_by_nickname(nickname) do
728 with %User{} = user <- get_by_nickname(nickname) do
732 with [_nick, _domain] <- String.split(nickname, "@"),
733 {:ok, user} <- fetch_by_nickname(nickname) do
734 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
735 fetch_initial_posts(user)
740 _e -> {:error, "not found " <> nickname}
745 @doc "Fetch some posts when the user has just been federated with"
746 def fetch_initial_posts(user) do
747 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
750 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
751 def get_followers_query(%User{} = user, nil) do
752 User.Query.build(%{followers: user, deactivated: false})
755 def get_followers_query(user, page) do
757 |> get_followers_query(nil)
758 |> User.Query.paginate(page, 20)
761 @spec get_followers_query(User.t()) :: Ecto.Query.t()
762 def get_followers_query(user), do: get_followers_query(user, nil)
764 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
765 def get_followers(user, page \\ nil) do
767 |> get_followers_query(page)
771 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
772 def get_external_followers(user, page \\ nil) do
774 |> get_followers_query(page)
775 |> User.Query.build(%{external: true})
779 def get_followers_ids(user, page \\ nil) do
781 |> get_followers_query(page)
786 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
787 def get_friends_query(%User{} = user, nil) do
788 User.Query.build(%{friends: user, deactivated: false})
791 def get_friends_query(user, page) do
793 |> get_friends_query(nil)
794 |> User.Query.paginate(page, 20)
797 @spec get_friends_query(User.t()) :: Ecto.Query.t()
798 def get_friends_query(user), do: get_friends_query(user, nil)
800 def get_friends(user, page \\ nil) do
802 |> get_friends_query(page)
806 def get_friends_ids(user, page \\ nil) do
808 |> get_friends_query(page)
813 defdelegate get_follow_requests(user), to: FollowingRelationship
815 def increase_note_count(%User{} = user) do
817 |> where(id: ^user.id)
818 |> update([u], inc: [note_count: 1])
820 |> Repo.update_all([])
822 {1, [user]} -> set_cache(user)
827 def decrease_note_count(%User{} = user) do
829 |> where(id: ^user.id)
832 note_count: fragment("greatest(0, note_count - 1)")
836 |> Repo.update_all([])
838 {1, [user]} -> set_cache(user)
843 def update_note_count(%User{} = user, note_count \\ nil) do
848 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
854 |> cast(%{note_count: note_count}, [:note_count])
855 |> update_and_set_cache()
858 @spec maybe_fetch_follow_information(User.t()) :: User.t()
859 def maybe_fetch_follow_information(user) do
860 with {:ok, user} <- fetch_follow_information(user) do
864 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
870 def fetch_follow_information(user) do
871 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
873 |> follow_information_changeset(info)
874 |> update_and_set_cache()
878 defp follow_information_changeset(user, params) do
885 :hide_followers_count,
890 def update_follower_count(%User{} = user) do
891 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
892 follower_count_query =
893 User.Query.build(%{followers: user, deactivated: false})
894 |> select([u], %{count: count(u.id)})
897 |> where(id: ^user.id)
898 |> join(:inner, [u], s in subquery(follower_count_query))
900 set: [follower_count: s.count]
903 |> Repo.update_all([])
905 {1, [user]} -> set_cache(user)
909 {:ok, maybe_fetch_follow_information(user)}
913 @spec maybe_update_following_count(User.t()) :: User.t()
914 def maybe_update_following_count(%User{local: false} = user) do
915 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
916 maybe_fetch_follow_information(user)
922 def maybe_update_following_count(user), do: user
924 def set_unread_conversation_count(%User{local: true} = user) do
925 unread_query = Participation.unread_conversation_count_for_user(user)
928 |> join(:inner, [u], p in subquery(unread_query))
930 set: [unread_conversation_count: p.count]
932 |> where([u], u.id == ^user.id)
934 |> Repo.update_all([])
936 {1, [user]} -> set_cache(user)
941 def set_unread_conversation_count(user), do: {:ok, user}
943 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
945 Participation.unread_conversation_count_for_user(user)
946 |> where([p], p.conversation_id == ^conversation.id)
949 |> join(:inner, [u], p in subquery(unread_query))
951 inc: [unread_conversation_count: 1]
953 |> where([u], u.id == ^user.id)
954 |> where([u, p], p.count == 0)
956 |> Repo.update_all([])
958 {1, [user]} -> set_cache(user)
963 def increment_unread_conversation_count(_, user), do: {:ok, user}
965 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
966 def get_users_from_set(ap_ids, local_only \\ true) do
967 criteria = %{ap_id: ap_ids, deactivated: false}
968 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
970 User.Query.build(criteria)
974 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
975 def get_recipients_from_activity(%Activity{recipients: to}) do
976 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
980 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
981 def mute(muter, %User{} = mutee, notifications? \\ true) do
982 add_to_mutes(muter, mutee, notifications?)
985 def unmute(muter, %User{} = mutee) do
986 remove_from_mutes(muter, mutee)
989 def subscribe(subscriber, %{ap_id: ap_id}) do
990 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
991 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
993 if blocks?(subscribed, subscriber) and deny_follow_blocked do
994 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
996 User.add_to_subscribers(subscribed, subscriber.ap_id)
1001 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
1002 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1003 User.remove_from_subscribers(user, unsubscriber.ap_id)
1007 def block(blocker, %User{} = blocked) do
1008 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1010 if following?(blocker, blocked) do
1011 {:ok, blocker, _} = unfollow(blocker, blocked)
1017 # clear any requested follows as well
1019 case CommonAPI.reject_follow_request(blocked, blocker) do
1020 {:ok, %User{} = updated_blocked} -> updated_blocked
1025 if subscribed_to?(blocked, blocker) do
1026 {:ok, blocker} = unsubscribe(blocked, blocker)
1032 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1034 {:ok, blocker} = update_follower_count(blocker)
1035 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1036 add_to_block(blocker, blocked)
1039 # helper to handle the block given only an actor's AP id
1040 def block(blocker, %{ap_id: ap_id}) do
1041 block(blocker, get_cached_by_ap_id(ap_id))
1044 def unblock(blocker, %User{} = blocked) do
1045 remove_from_block(blocker, blocked)
1048 # helper to handle the block given only an actor's AP id
1049 def unblock(blocker, %{ap_id: ap_id}) do
1050 unblock(blocker, get_cached_by_ap_id(ap_id))
1053 def mutes?(nil, _), do: false
1054 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1056 def mutes_user?(%User{} = user, %User{} = target) do
1057 UserMute.exists?(user, target)
1060 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1061 def muted_notifications?(nil, _), do: false
1063 def muted_notifications?(user, %{ap_id: ap_id}),
1064 do: Enum.member?(user.muted_notifications, ap_id)
1066 def blocks?(nil, _), do: false
1068 def blocks?(%User{} = user, %User{} = target) do
1069 blocks_user?(user, target) || blocks_domain?(user, target)
1072 def blocks_user?(%User{} = user, %User{} = target) do
1073 UserBlock.exists?(user, target)
1076 def blocks_user?(_, _), do: false
1078 def blocks_domain?(%User{} = user, %User{} = target) do
1079 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1080 %{host: host} = URI.parse(target.ap_id)
1081 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1084 def blocks_domain?(_, _), do: false
1086 def subscribed_to?(user, %{ap_id: ap_id}) do
1087 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1088 Enum.member?(target.subscribers, user.ap_id)
1092 @spec muted_users(User.t()) :: [User.t()]
1093 def muted_users(user) do
1095 |> assoc(:muted_users)
1096 |> restrict_deactivated()
1100 def muted_ap_ids(user) do
1102 |> assoc(:muted_users)
1103 |> select([u], u.ap_id)
1107 @spec blocked_users(User.t()) :: [User.t()]
1108 def blocked_users(user) do
1110 |> assoc(:blocked_users)
1111 |> restrict_deactivated()
1115 def blocked_ap_ids(user) do
1117 |> assoc(:blocked_users)
1118 |> select([u], u.ap_id)
1122 defp related_ap_ids_sql(join_table, source_column, target_column) do
1123 "(SELECT array_agg(u.ap_id) FROM users as u " <>
1124 "INNER JOIN #{join_table} AS join_table " <>
1125 "ON join_table.#{source_column} = $1 " <>
1126 "WHERE u.id = join_table.#{target_column})"
1129 @related_ap_ids_sql_params %{
1130 blocked_users: ["user_blocks", "blocker_id", "blockee_id"],
1131 muted_users: ["user_mutes", "muter_id", "mutee_id"]
1134 def related_ap_ids(user, relations) when is_list(relations) do
1137 |> Enum.map(fn r -> @related_ap_ids_sql_params[r] end)
1138 |> Enum.filter(& &1)
1139 |> Enum.map(fn [join_table, source_column, target_column] ->
1140 related_ap_ids_sql(join_table, source_column, target_column)
1144 with {:ok, %{rows: [ap_ids_arrays]}} <-
1145 Repo.query("SELECT #{query}", [FlakeId.from_string(user.id)]) do
1146 ap_ids_arrays = Enum.map(ap_ids_arrays, &(&1 || []))
1147 {:ok, ap_ids_arrays}
1151 @spec subscribers(User.t()) :: [User.t()]
1152 def subscribers(user) do
1153 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1157 def deactivate_async(user, status \\ true) do
1158 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1161 def deactivate(user, status \\ true)
1163 def deactivate(users, status) when is_list(users) do
1164 Repo.transaction(fn ->
1165 for user <- users, do: deactivate(user, status)
1169 def deactivate(%User{} = user, status) do
1170 with {:ok, user} <- set_activation_status(user, status) do
1171 Enum.each(get_followers(user), &invalidate_cache/1)
1173 # Only update local user counts, remote will be update during the next pull.
1176 |> Enum.filter(& &1.local)
1177 |> Enum.each(&update_follower_count/1)
1183 def update_notification_settings(%User{} = user, settings) do
1186 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1189 notification_settings =
1190 user.notification_settings
1191 |> Map.merge(settings)
1192 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1194 params = %{notification_settings: notification_settings}
1197 |> cast(params, [:notification_settings])
1198 |> validate_required([:notification_settings])
1199 |> update_and_set_cache()
1202 def delete(users) when is_list(users) do
1203 for user <- users, do: delete(user)
1206 def delete(%User{} = user) do
1207 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1210 def perform(:force_password_reset, user), do: force_password_reset(user)
1212 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1213 def perform(:delete, %User{} = user) do
1214 {:ok, _user} = ActivityPub.delete(user)
1216 # Remove all relationships
1219 |> Enum.each(fn follower ->
1220 ActivityPub.unfollow(follower, user)
1221 unfollow(follower, user)
1226 |> Enum.each(fn followed ->
1227 ActivityPub.unfollow(user, followed)
1228 unfollow(user, followed)
1231 delete_user_activities(user)
1232 invalidate_cache(user)
1236 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1237 def perform(:fetch_initial_posts, %User{} = user) do
1238 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1240 # Insert all the posts in reverse order, so they're in the right order on the timeline
1241 user.source_data["outbox"]
1242 |> Utils.fetch_ordered_collection(pages)
1244 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1247 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1249 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1250 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1251 when is_list(blocked_identifiers) do
1253 blocked_identifiers,
1254 fn blocked_identifier ->
1255 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1256 {:ok, _user_block} <- block(blocker, blocked),
1257 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1261 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1268 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1269 def perform(:follow_import, %User{} = follower, followed_identifiers)
1270 when is_list(followed_identifiers) do
1272 followed_identifiers,
1273 fn followed_identifier ->
1274 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1275 {:ok, follower} <- maybe_direct_follow(follower, followed),
1276 {:ok, _} <- ActivityPub.follow(follower, followed) do
1280 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1287 @spec external_users_query() :: Ecto.Query.t()
1288 def external_users_query do
1296 @spec external_users(keyword()) :: [User.t()]
1297 def external_users(opts \\ []) do
1299 external_users_query()
1300 |> select([u], struct(u, [:id, :ap_id, :info]))
1304 do: where(query, [u], u.id > ^opts[:max_id]),
1309 do: limit(query, ^opts[:limit]),
1315 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1316 BackgroundWorker.enqueue("blocks_import", %{
1317 "blocker_id" => blocker.id,
1318 "blocked_identifiers" => blocked_identifiers
1322 def follow_import(%User{} = follower, followed_identifiers)
1323 when is_list(followed_identifiers) do
1324 BackgroundWorker.enqueue("follow_import", %{
1325 "follower_id" => follower.id,
1326 "followed_identifiers" => followed_identifiers
1330 def delete_user_activities(%User{ap_id: ap_id}) do
1332 |> Activity.Queries.by_actor()
1333 |> RepoStreamer.chunk_stream(50)
1334 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1338 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1340 |> Object.normalize()
1341 |> ActivityPub.delete()
1344 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1345 object = Object.normalize(activity)
1348 |> get_cached_by_ap_id()
1349 |> ActivityPub.unlike(object)
1352 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1353 object = Object.normalize(activity)
1356 |> get_cached_by_ap_id()
1357 |> ActivityPub.unannounce(object)
1360 defp delete_activity(_activity), do: "Doing nothing"
1362 def html_filter_policy(%User{no_rich_text: true}) do
1363 Pleroma.HTML.Scrubber.TwitterText
1366 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1368 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1370 def get_or_fetch_by_ap_id(ap_id) do
1371 user = get_cached_by_ap_id(ap_id)
1373 if !is_nil(user) and !needs_update?(user) do
1376 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1377 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1379 resp = fetch_by_ap_id(ap_id)
1381 if should_fetch_initial do
1382 with {:ok, %User{} = user} <- resp do
1383 fetch_initial_posts(user)
1391 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1392 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1393 with %User{} = user <- get_cached_by_ap_id(uri) do
1399 |> cast(%{}, [:ap_id, :nickname, :local])
1400 |> put_change(:ap_id, uri)
1401 |> put_change(:nickname, nickname)
1402 |> put_change(:local, true)
1403 |> put_change(:follower_address, uri <> "/followers")
1411 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1414 |> :public_key.pem_decode()
1416 |> :public_key.pem_entry_decode()
1421 def public_key(_), do: {:error, "not found key"}
1423 def get_public_key_for_ap_id(ap_id) do
1424 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1425 {:ok, public_key} <- public_key(user) do
1432 defp blank?(""), do: nil
1433 defp blank?(n), do: n
1435 def insert_or_update_user(data) do
1437 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1438 |> remote_user_creation()
1439 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1443 def ap_enabled?(%User{local: true}), do: true
1444 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1445 def ap_enabled?(_), do: false
1447 @doc "Gets or fetch a user by uri or nickname."
1448 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1449 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1450 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1452 # wait a period of time and return newest version of the User structs
1453 # this is because we have synchronous follow APIs and need to simulate them
1454 # with an async handshake
1455 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1456 with %User{} = a <- get_cached_by_id(a.id),
1457 %User{} = b <- get_cached_by_id(b.id) do
1464 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1465 with :ok <- :timer.sleep(timeout),
1466 %User{} = a <- get_cached_by_id(a.id),
1467 %User{} = b <- get_cached_by_id(b.id) do
1474 def parse_bio(bio) when is_binary(bio) and bio != "" do
1476 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1480 def parse_bio(_), do: ""
1482 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1483 # TODO: get profile URLs other than user.ap_id
1484 profile_urls = [user.ap_id]
1487 |> CommonUtils.format_input("text/plain",
1488 mentions_format: :full,
1489 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1494 def parse_bio(_, _), do: ""
1496 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1497 Repo.transaction(fn ->
1498 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1502 def tag(nickname, tags) when is_binary(nickname),
1503 do: tag(get_by_nickname(nickname), tags)
1505 def tag(%User{} = user, tags),
1506 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1508 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1509 Repo.transaction(fn ->
1510 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1514 def untag(nickname, tags) when is_binary(nickname),
1515 do: untag(get_by_nickname(nickname), tags)
1517 def untag(%User{} = user, tags),
1518 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1520 defp update_tags(%User{} = user, new_tags) do
1521 {:ok, updated_user} =
1523 |> change(%{tags: new_tags})
1524 |> update_and_set_cache()
1529 defp normalize_tags(tags) do
1532 |> Enum.map(&String.downcase/1)
1535 defp local_nickname_regex do
1536 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1537 @extended_local_nickname_regex
1539 @strict_local_nickname_regex
1543 def local_nickname(nickname_or_mention) do
1546 |> String.split("@")
1550 def full_nickname(nickname_or_mention),
1551 do: String.trim_leading(nickname_or_mention, "@")
1553 def error_user(ap_id) do
1557 nickname: "erroruser@example.com",
1558 inserted_at: NaiveDateTime.utc_now()
1562 @spec all_superusers() :: [User.t()]
1563 def all_superusers do
1564 User.Query.build(%{super_users: true, local: true, deactivated: false})
1568 def showing_reblogs?(%User{} = user, %User{} = target) do
1569 target.ap_id not in user.muted_reblogs
1573 The function returns a query to get users with no activity for given interval of days.
1574 Inactive users are those who didn't read any notification, or had any activity where
1575 the user is the activity's actor, during `inactivity_threshold` days.
1576 Deactivated users will not appear in this list.
1580 iex> Pleroma.User.list_inactive_users()
1583 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1584 def list_inactive_users_query(inactivity_threshold \\ 7) do
1585 negative_inactivity_threshold = -inactivity_threshold
1586 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1587 # Subqueries are not supported in `where` clauses, join gets too complicated.
1588 has_read_notifications =
1589 from(n in Pleroma.Notification,
1590 where: n.seen == true,
1592 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1595 |> Pleroma.Repo.all()
1597 from(u in Pleroma.User,
1598 left_join: a in Pleroma.Activity,
1599 on: u.ap_id == a.actor,
1600 where: not is_nil(u.nickname),
1601 where: u.deactivated != ^true,
1602 where: u.id not in ^has_read_notifications,
1605 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1606 is_nil(max(a.inserted_at))
1611 Enable or disable email notifications for user
1615 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1616 Pleroma.User{email_notifications: %{"digest" => true}}
1618 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1619 Pleroma.User{email_notifications: %{"digest" => false}}
1621 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1622 {:ok, t()} | {:error, Ecto.Changeset.t()}
1623 def switch_email_notifications(user, type, status) do
1624 User.update_email_notifications(user, %{type => status})
1628 Set `last_digest_emailed_at` value for the user to current time
1630 @spec touch_last_digest_emailed_at(t()) :: t()
1631 def touch_last_digest_emailed_at(user) do
1632 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1634 {:ok, updated_user} =
1636 |> change(%{last_digest_emailed_at: now})
1637 |> update_and_set_cache()
1642 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1643 def toggle_confirmation(%User{} = user) do
1645 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1646 |> update_and_set_cache()
1649 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1653 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1654 # use instance-default
1655 config = Pleroma.Config.get([:assets, :mascots])
1656 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1657 mascot = Keyword.get(config, default_mascot)
1660 "id" => "default-mascot",
1661 "url" => mascot[:url],
1662 "preview_url" => mascot[:url],
1664 "mime_type" => mascot[:mime_type]
1669 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1671 def ensure_keys_present(%User{} = user) do
1672 with {:ok, pem} <- Keys.generate_rsa_pem() do
1674 |> cast(%{keys: pem}, [:keys])
1675 |> validate_required([:keys])
1676 |> update_and_set_cache()
1680 def get_ap_ids_by_nicknames(nicknames) do
1682 where: u.nickname in ^nicknames,
1688 defdelegate search(query, opts \\ []), to: User.Search
1690 defp put_password_hash(
1691 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1693 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1696 defp put_password_hash(changeset), do: changeset
1698 def is_internal_user?(%User{nickname: nil}), do: true
1699 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1700 def is_internal_user?(_), do: false
1702 # A hack because user delete activities have a fake id for whatever reason
1703 # TODO: Get rid of this
1704 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1706 def get_delivered_users_by_object_id(object_id) do
1708 inner_join: delivery in assoc(u, :deliveries),
1709 where: delivery.object_id == ^object_id
1714 def change_email(user, email) do
1716 |> cast(%{email: email}, [:email])
1717 |> validate_required([:email])
1718 |> unique_constraint(:email)
1719 |> validate_format(:email, @email_regex)
1720 |> update_and_set_cache()
1723 # Internal function; public one is `deactivate/2`
1724 defp set_activation_status(user, deactivated) do
1726 |> cast(%{deactivated: deactivated}, [:deactivated])
1727 |> update_and_set_cache()
1730 def update_banner(user, banner) do
1732 |> cast(%{banner: banner}, [:banner])
1733 |> update_and_set_cache()
1736 def update_background(user, background) do
1738 |> cast(%{background: background}, [:background])
1739 |> update_and_set_cache()
1742 def update_source_data(user, source_data) do
1744 |> cast(%{source_data: source_data}, [:source_data])
1745 |> update_and_set_cache()
1748 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1751 moderator: is_moderator
1755 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1756 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1757 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1758 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1761 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1762 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1766 def fields(%{fields: nil}), do: []
1768 def fields(%{fields: fields}), do: fields
1770 def validate_fields(changeset, remote? \\ false) do
1771 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1772 limit = Pleroma.Config.get([:instance, limit_name], 0)
1775 |> validate_length(:fields, max: limit)
1776 |> validate_change(:fields, fn :fields, fields ->
1777 if Enum.all?(fields, &valid_field?/1) do
1785 defp valid_field?(%{"name" => name, "value" => value}) do
1786 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1787 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1789 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1790 String.length(value) <= value_limit
1793 defp valid_field?(_), do: false
1795 defp truncate_field(%{"name" => name, "value" => value}) do
1797 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1800 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1802 %{"name" => name, "value" => value}
1805 def admin_api_update(user, params) do
1812 |> update_and_set_cache()
1815 def mascot_update(user, url) do
1817 |> cast(%{mascot: url}, [:mascot])
1818 |> validate_required([:mascot])
1819 |> update_and_set_cache()
1822 def mastodon_settings_update(user, settings) do
1824 |> cast(%{settings: settings}, [:settings])
1825 |> validate_required([:settings])
1826 |> update_and_set_cache()
1829 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1830 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1832 if need_confirmation? do
1834 confirmation_pending: true,
1835 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1839 confirmation_pending: false,
1840 confirmation_token: nil
1844 cast(user, params, [:confirmation_pending, :confirmation_token])
1847 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1848 if id not in user.pinned_activities do
1849 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1850 params = %{pinned_activities: user.pinned_activities ++ [id]}
1853 |> cast(params, [:pinned_activities])
1854 |> validate_length(:pinned_activities,
1855 max: max_pinned_statuses,
1856 message: "You have already pinned the maximum number of statuses"
1861 |> update_and_set_cache()
1864 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1865 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1868 |> cast(params, [:pinned_activities])
1869 |> update_and_set_cache()
1872 def update_email_notifications(user, settings) do
1873 email_notifications =
1874 user.email_notifications
1875 |> Map.merge(settings)
1876 |> Map.take(["digest"])
1878 params = %{email_notifications: email_notifications}
1879 fields = [:email_notifications]
1882 |> cast(params, fields)
1883 |> validate_required(fields)
1884 |> update_and_set_cache()
1887 defp set_subscribers(user, subscribers) do
1888 params = %{subscribers: subscribers}
1891 |> cast(params, [:subscribers])
1892 |> validate_required([:subscribers])
1893 |> update_and_set_cache()
1896 def add_to_subscribers(user, subscribed) do
1897 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1900 def remove_from_subscribers(user, subscribed) do
1901 set_subscribers(user, List.delete(user.subscribers, subscribed))
1904 defp set_domain_blocks(user, domain_blocks) do
1905 params = %{domain_blocks: domain_blocks}
1908 |> cast(params, [:domain_blocks])
1909 |> validate_required([:domain_blocks])
1910 |> update_and_set_cache()
1913 def block_domain(user, domain_blocked) do
1914 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1917 def unblock_domain(user, domain_blocked) do
1918 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1921 @spec add_to_block(User.t(), User.t()) :: {:ok, UserBlock.t()} | {:error, Ecto.Changeset.t()}
1922 defp add_to_block(%User{} = user, %User{} = blocked) do
1923 UserBlock.create(user, blocked)
1926 @spec add_to_block(User.t(), User.t()) ::
1927 {:ok, UserBlock.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
1928 defp remove_from_block(%User{} = user, %User{} = blocked) do
1929 UserBlock.delete(user, blocked)
1932 defp add_to_mutes(%User{} = user, %User{ap_id: ap_id} = muted_user, notifications?) do
1933 with {:ok, user_mute} <- UserMute.create(user, muted_user),
1935 set_notification_mutes(
1937 Enum.uniq([ap_id | user.muted_notifications]),
1944 defp remove_from_mutes(user, %User{ap_id: ap_id} = muted_user) do
1945 with {:ok, user_mute} <- UserMute.delete(user, muted_user),
1947 set_notification_mutes(
1949 List.delete(user.muted_notifications, ap_id),
1956 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1960 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1961 params = %{muted_notifications: muted_notifications}
1964 |> cast(params, [:muted_notifications])
1965 |> validate_required([:muted_notifications])
1966 |> update_and_set_cache()
1969 def add_reblog_mute(user, ap_id) do
1970 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1973 |> cast(params, [:muted_reblogs])
1974 |> update_and_set_cache()
1977 def remove_reblog_mute(user, ap_id) do
1978 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1981 |> cast(params, [:muted_reblogs])
1982 |> update_and_set_cache()
1985 def set_invisible(user, invisible) do
1986 params = %{invisible: invisible}
1989 |> cast(params, [:invisible])
1990 |> validate_required([:invisible])
1991 |> update_and_set_cache()