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.UserRelationship
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_-]+$/
47 # AP ID user relationships (blocks, mutes etc.)
48 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
49 @user_relationships_config [
51 blocker_blocks: :blocked_users,
52 blockee_blocks: :blocker_users
55 muter_mutes: :muted_users,
56 mutee_mutes: :muter_users
59 reblog_muter_mutes: :reblog_muted_users,
60 reblog_mutee_mutes: :reblog_muter_users
63 notification_muter_mutes: :notification_muted_users,
64 notification_mutee_mutes: :notification_muter_users
66 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
67 inverse_subscription: [
68 subscribee_subscriptions: :subscriber_users,
69 subscriber_subscriptions: :subscribee_users
75 field(:email, :string)
77 field(:nickname, :string)
78 field(:password_hash, :string)
79 field(:password, :string, virtual: true)
80 field(:password_confirmation, :string, virtual: true)
82 field(:ap_id, :string)
84 field(:local, :boolean, default: true)
85 field(:follower_address, :string)
86 field(:following_address, :string)
87 field(:search_rank, :float, virtual: true)
88 field(:search_type, :integer, virtual: true)
89 field(:tags, {:array, :string}, default: [])
90 field(:last_refreshed_at, :naive_datetime_usec)
91 field(:last_digest_emailed_at, :naive_datetime)
92 field(:banner, :map, default: %{})
93 field(:background, :map, default: %{})
94 field(:source_data, :map, default: %{})
95 field(:note_count, :integer, default: 0)
96 field(:follower_count, :integer, default: 0)
97 field(:following_count, :integer, default: 0)
98 field(:locked, :boolean, default: false)
99 field(:confirmation_pending, :boolean, default: false)
100 field(:password_reset_pending, :boolean, default: false)
101 field(:confirmation_token, :string, default: nil)
102 field(:default_scope, :string, default: "public")
103 field(:domain_blocks, {:array, :string}, default: [])
104 field(:deactivated, :boolean, default: false)
105 field(:no_rich_text, :boolean, default: false)
106 field(:ap_enabled, :boolean, default: false)
107 field(:is_moderator, :boolean, default: false)
108 field(:is_admin, :boolean, default: false)
109 field(:show_role, :boolean, default: true)
110 field(:settings, :map, default: nil)
111 field(:magic_key, :string, default: nil)
112 field(:uri, :string, default: nil)
113 field(:hide_followers_count, :boolean, default: false)
114 field(:hide_follows_count, :boolean, default: false)
115 field(:hide_followers, :boolean, default: false)
116 field(:hide_follows, :boolean, default: false)
117 field(:hide_favorites, :boolean, default: true)
118 field(:unread_conversation_count, :integer, default: 0)
119 field(:pinned_activities, {:array, :string}, default: [])
120 field(:email_notifications, :map, default: %{"digest" => false})
121 field(:mascot, :map, default: nil)
122 field(:emoji, {:array, :map}, default: [])
123 field(:pleroma_settings_store, :map, default: %{})
124 field(:fields, {:array, :map}, default: [])
125 field(:raw_fields, {:array, :map}, default: [])
126 field(:discoverable, :boolean, default: false)
127 field(:invisible, :boolean, default: false)
128 field(:allow_following_move, :boolean, default: true)
129 field(:skip_thread_containment, :boolean, default: false)
130 field(:also_known_as, {:array, :string}, default: [])
132 field(:notification_settings, :map,
136 "non_follows" => true,
137 "non_followers" => true
141 has_many(:notifications, Notification)
142 has_many(:registrations, Registration)
143 has_many(:deliveries, Delivery)
145 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
146 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
148 for {relationship_type,
150 {outgoing_relation, outgoing_relation_target},
151 {incoming_relation, incoming_relation_source}
152 ]} <- @user_relationships_config do
153 # Definitions of `has_many :blocker_blocks`, `has_many :muter_mutes` etc.
154 has_many(outgoing_relation, UserRelationship,
155 foreign_key: :source_id,
156 where: [relationship_type: relationship_type]
159 # Definitions of `has_many :blockee_blocks`, `has_many :mutee_mutes` etc.
160 has_many(incoming_relation, UserRelationship,
161 foreign_key: :target_id,
162 where: [relationship_type: relationship_type]
165 # Definitions of `has_many :blocked_users`, `has_many :muted_users` etc.
166 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
168 # Definitions of `has_many :blocker_users`, `has_many :muter_users` etc.
169 has_many(incoming_relation_source, through: [incoming_relation, :source])
172 # `:blocks` is deprecated (replaced with `blocked_users` relation)
173 field(:blocks, {:array, :string}, default: [])
174 # `:mutes` is deprecated (replaced with `muted_users` relation)
175 field(:mutes, {:array, :string}, default: [])
176 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
177 field(:muted_reblogs, {:array, :string}, default: [])
178 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
179 field(:muted_notifications, {:array, :string}, default: [])
180 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
181 field(:subscribers, {:array, :string}, default: [])
186 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
187 @user_relationships_config do
188 # Definitions of `blocked_users_relation/1`, `muted_users_relation/1`, etc.
189 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
190 target_users_query = assoc(user, unquote(outgoing_relation_target))
192 if restrict_deactivated? do
193 restrict_deactivated(target_users_query)
199 # Definitions of `blocked_users/1`, `muted_users/1`, etc.
200 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
202 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
204 restrict_deactivated?
209 # Definitions of `blocked_users_ap_ids/1`, `muted_users_ap_ids/1`, etc.
210 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
212 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
214 restrict_deactivated?
216 |> select([u], u.ap_id)
221 @doc "Returns if the user should be allowed to authenticate"
222 def auth_active?(%User{deactivated: true}), do: false
224 def auth_active?(%User{confirmation_pending: true}),
225 do: !Pleroma.Config.get([:instance, :account_activation_required])
227 def auth_active?(%User{}), do: true
229 def visible_for?(user, for_user \\ nil)
231 def visible_for?(%User{invisible: true}, _), do: false
233 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
235 def visible_for?(%User{} = user, for_user) do
236 auth_active?(user) || superuser?(for_user)
239 def visible_for?(_, _), do: false
241 def superuser?(%User{local: true, is_admin: true}), do: true
242 def superuser?(%User{local: true, is_moderator: true}), do: true
243 def superuser?(_), do: false
245 def invisible?(%User{invisible: true}), do: true
246 def invisible?(_), do: false
248 def avatar_url(user, options \\ []) do
250 %{"url" => [%{"href" => href} | _]} -> href
251 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
255 def banner_url(user, options \\ []) do
257 %{"url" => [%{"href" => href} | _]} -> href
258 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
262 def profile_url(%User{source_data: %{"url" => url}}), do: url
263 def profile_url(%User{ap_id: ap_id}), do: ap_id
264 def profile_url(_), do: nil
266 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
268 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
269 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
271 @spec ap_following(User.t()) :: Sring.t()
272 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
273 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
275 def follow_state(%User{} = user, %User{} = target) do
276 case Utils.fetch_latest_follow(user, target) do
277 %{data: %{"state" => state}} -> state
278 # Ideally this would be nil, but then Cachex does not commit the value
283 def get_cached_follow_state(user, target) do
284 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
285 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
288 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
289 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
290 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
293 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
294 def restrict_deactivated(query) do
295 from(u in query, where: u.deactivated != ^true)
298 defdelegate following_count(user), to: FollowingRelationship
300 defp truncate_fields_param(params) do
301 if Map.has_key?(params, :fields) do
302 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
308 defp truncate_if_exists(params, key, max_length) do
309 if Map.has_key?(params, key) and is_binary(params[key]) do
310 {value, _chopped} = String.split_at(params[key], max_length)
311 Map.put(params, key, value)
317 def remote_user_creation(params) do
318 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
319 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
323 |> truncate_if_exists(:name, name_limit)
324 |> truncate_if_exists(:bio, bio_limit)
325 |> truncate_fields_param()
345 :hide_followers_count,
355 |> validate_required([:name, :ap_id])
356 |> unique_constraint(:nickname)
357 |> validate_format(:nickname, @email_regex)
358 |> validate_length(:bio, max: bio_limit)
359 |> validate_length(:name, max: name_limit)
360 |> validate_fields(true)
362 case params[:source_data] do
363 %{"followers" => followers, "following" => following} ->
365 |> put_change(:follower_address, followers)
366 |> put_change(:following_address, following)
369 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
370 put_change(changeset, :follower_address, followers)
374 def update_changeset(struct, params \\ %{}) do
375 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
376 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
391 :hide_followers_count,
394 :allow_following_move,
397 :skip_thread_containment,
400 :pleroma_settings_store,
405 |> unique_constraint(:nickname)
406 |> validate_format(:nickname, local_nickname_regex())
407 |> validate_length(:bio, max: bio_limit)
408 |> validate_length(:name, min: 1, max: name_limit)
409 |> validate_fields(false)
412 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
413 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
414 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
416 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
418 params = if remote?, do: truncate_fields_param(params), else: params
440 :allow_following_move,
442 :hide_followers_count,
447 |> unique_constraint(:nickname)
448 |> validate_format(:nickname, local_nickname_regex())
449 |> validate_length(:bio, max: bio_limit)
450 |> validate_length(:name, max: name_limit)
451 |> validate_fields(remote?)
454 def password_update_changeset(struct, params) do
456 |> cast(params, [:password, :password_confirmation])
457 |> validate_required([:password, :password_confirmation])
458 |> validate_confirmation(:password)
459 |> put_password_hash()
460 |> put_change(:password_reset_pending, false)
463 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
464 def reset_password(%User{id: user_id} = user, data) do
467 |> Multi.update(:user, password_update_changeset(user, data))
468 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
469 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
471 case Repo.transaction(multi) do
472 {:ok, %{user: user} = _} -> set_cache(user)
473 {:error, _, changeset, _} -> {:error, changeset}
477 def update_password_reset_pending(user, value) do
480 |> put_change(:password_reset_pending, value)
481 |> update_and_set_cache()
484 def force_password_reset_async(user) do
485 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
488 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
489 def force_password_reset(user), do: update_password_reset_pending(user, true)
491 def register_changeset(struct, params \\ %{}, opts \\ []) do
492 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
493 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
496 if is_nil(opts[:need_confirmation]) do
497 Pleroma.Config.get([:instance, :account_activation_required])
499 opts[:need_confirmation]
503 |> confirmation_changeset(need_confirmation: need_confirmation?)
504 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
505 |> validate_required([:name, :nickname, :password, :password_confirmation])
506 |> validate_confirmation(:password)
507 |> unique_constraint(:email)
508 |> unique_constraint(:nickname)
509 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
510 |> validate_format(:nickname, local_nickname_regex())
511 |> validate_format(:email, @email_regex)
512 |> validate_length(:bio, max: bio_limit)
513 |> validate_length(:name, min: 1, max: name_limit)
514 |> maybe_validate_required_email(opts[:external])
517 |> unique_constraint(:ap_id)
518 |> put_following_and_follower_address()
521 def maybe_validate_required_email(changeset, true), do: changeset
522 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
524 defp put_ap_id(changeset) do
525 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
526 put_change(changeset, :ap_id, ap_id)
529 defp put_following_and_follower_address(changeset) do
530 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
533 |> put_change(:follower_address, followers)
536 defp autofollow_users(user) do
537 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
540 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
543 follow_all(user, autofollowed_users)
546 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
547 def register(%Ecto.Changeset{} = changeset) do
548 with {:ok, user} <- Repo.insert(changeset) do
549 post_register_action(user)
553 def post_register_action(%User{} = user) do
554 with {:ok, user} <- autofollow_users(user),
555 {:ok, user} <- set_cache(user),
556 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
557 {:ok, _} <- try_send_confirmation_email(user) do
562 def try_send_confirmation_email(%User{} = user) do
563 if user.confirmation_pending &&
564 Pleroma.Config.get([:instance, :account_activation_required]) do
566 |> Pleroma.Emails.UserEmail.account_confirmation_email()
567 |> Pleroma.Emails.Mailer.deliver_async()
575 def try_send_confirmation_email(users) do
576 Enum.each(users, &try_send_confirmation_email/1)
579 def needs_update?(%User{local: true}), do: false
581 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
583 def needs_update?(%User{local: false} = user) do
584 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
587 def needs_update?(_), do: true
589 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
590 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
591 follow(follower, followed, "pending")
594 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
595 follow(follower, followed)
598 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
599 if not ap_enabled?(followed) do
600 follow(follower, followed)
606 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
607 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
608 def follow_all(follower, followeds) do
610 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
611 |> Enum.each(&follow(follower, &1, "accept"))
616 defdelegate following(user), to: FollowingRelationship
618 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
619 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
622 followed.deactivated ->
623 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
625 deny_follow_blocked and blocks?(followed, follower) ->
626 {:error, "Could not follow user: #{followed.nickname} blocked you."}
629 FollowingRelationship.follow(follower, followed, state)
631 {:ok, _} = update_follower_count(followed)
634 |> update_following_count()
639 def unfollow(%User{} = follower, %User{} = followed) do
640 if following?(follower, followed) and follower.ap_id != followed.ap_id do
641 FollowingRelationship.unfollow(follower, followed)
643 {:ok, followed} = update_follower_count(followed)
647 |> update_following_count()
650 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
652 {:error, "Not subscribed!"}
656 defdelegate following?(follower, followed), to: FollowingRelationship
658 def locked?(%User{} = user) do
663 Repo.get_by(User, id: id)
666 def get_by_ap_id(ap_id) do
667 Repo.get_by(User, ap_id: ap_id)
670 def get_all_by_ap_id(ap_ids) do
671 from(u in __MODULE__,
672 where: u.ap_id in ^ap_ids
677 def get_all_by_ids(ids) do
678 from(u in __MODULE__, where: u.id in ^ids)
682 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
683 # of the ap_id and the domain and tries to get that user
684 def get_by_guessed_nickname(ap_id) do
685 domain = URI.parse(ap_id).host
686 name = List.last(String.split(ap_id, "/"))
687 nickname = "#{name}@#{domain}"
689 get_cached_by_nickname(nickname)
692 def set_cache({:ok, user}), do: set_cache(user)
693 def set_cache({:error, err}), do: {:error, err}
695 def set_cache(%User{} = user) do
696 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
697 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
701 def update_and_set_cache(struct, params) do
703 |> update_changeset(params)
704 |> update_and_set_cache()
707 def update_and_set_cache(changeset) do
708 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
713 def invalidate_cache(user) do
714 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
715 Cachex.del(:user_cache, "nickname:#{user.nickname}")
718 def get_cached_by_ap_id(ap_id) do
719 key = "ap_id:#{ap_id}"
720 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
723 def get_cached_by_id(id) do
727 Cachex.fetch!(:user_cache, key, fn _ ->
731 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
732 {:commit, user.ap_id}
738 get_cached_by_ap_id(ap_id)
741 def get_cached_by_nickname(nickname) do
742 key = "nickname:#{nickname}"
744 Cachex.fetch!(:user_cache, key, fn ->
745 case get_or_fetch_by_nickname(nickname) do
746 {:ok, user} -> {:commit, user}
747 {:error, _error} -> {:ignore, nil}
752 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
753 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
756 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
757 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
759 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
760 get_cached_by_nickname(nickname_or_id)
762 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
763 get_cached_by_nickname(nickname_or_id)
770 def get_by_nickname(nickname) do
771 Repo.get_by(User, nickname: nickname) ||
772 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
773 Repo.get_by(User, nickname: local_nickname(nickname))
777 def get_by_email(email), do: Repo.get_by(User, email: email)
779 def get_by_nickname_or_email(nickname_or_email) do
780 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
783 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
785 def get_or_fetch_by_nickname(nickname) do
786 with %User{} = user <- get_by_nickname(nickname) do
790 with [_nick, _domain] <- String.split(nickname, "@"),
791 {:ok, user} <- fetch_by_nickname(nickname) do
792 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
793 fetch_initial_posts(user)
798 _e -> {:error, "not found " <> nickname}
803 @doc "Fetch some posts when the user has just been federated with"
804 def fetch_initial_posts(user) do
805 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
808 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
809 def get_followers_query(%User{} = user, nil) do
810 User.Query.build(%{followers: user, deactivated: false})
813 def get_followers_query(user, page) do
815 |> get_followers_query(nil)
816 |> User.Query.paginate(page, 20)
819 @spec get_followers_query(User.t()) :: Ecto.Query.t()
820 def get_followers_query(user), do: get_followers_query(user, nil)
822 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
823 def get_followers(user, page \\ nil) do
825 |> get_followers_query(page)
829 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
830 def get_external_followers(user, page \\ nil) do
832 |> get_followers_query(page)
833 |> User.Query.build(%{external: true})
837 def get_followers_ids(user, page \\ nil) do
839 |> get_followers_query(page)
844 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
845 def get_friends_query(%User{} = user, nil) do
846 User.Query.build(%{friends: user, deactivated: false})
849 def get_friends_query(user, page) do
851 |> get_friends_query(nil)
852 |> User.Query.paginate(page, 20)
855 @spec get_friends_query(User.t()) :: Ecto.Query.t()
856 def get_friends_query(user), do: get_friends_query(user, nil)
858 def get_friends(user, page \\ nil) do
860 |> get_friends_query(page)
864 def get_friends_ids(user, page \\ nil) do
866 |> get_friends_query(page)
871 defdelegate get_follow_requests(user), to: FollowingRelationship
873 def increase_note_count(%User{} = user) do
875 |> where(id: ^user.id)
876 |> update([u], inc: [note_count: 1])
878 |> Repo.update_all([])
880 {1, [user]} -> set_cache(user)
885 def decrease_note_count(%User{} = user) do
887 |> where(id: ^user.id)
890 note_count: fragment("greatest(0, note_count - 1)")
894 |> Repo.update_all([])
896 {1, [user]} -> set_cache(user)
901 def update_note_count(%User{} = user, note_count \\ nil) do
906 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
912 |> cast(%{note_count: note_count}, [:note_count])
913 |> update_and_set_cache()
916 @spec maybe_fetch_follow_information(User.t()) :: User.t()
917 def maybe_fetch_follow_information(user) do
918 with {:ok, user} <- fetch_follow_information(user) do
922 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
928 def fetch_follow_information(user) do
929 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
931 |> follow_information_changeset(info)
932 |> update_and_set_cache()
936 defp follow_information_changeset(user, params) do
943 :hide_followers_count,
948 def update_follower_count(%User{} = user) do
949 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
950 follower_count_query =
951 User.Query.build(%{followers: user, deactivated: false})
952 |> select([u], %{count: count(u.id)})
955 |> where(id: ^user.id)
956 |> join(:inner, [u], s in subquery(follower_count_query))
958 set: [follower_count: s.count]
961 |> Repo.update_all([])
963 {1, [user]} -> set_cache(user)
967 {:ok, maybe_fetch_follow_information(user)}
971 @spec update_following_count(User.t()) :: User.t()
972 def update_following_count(%User{local: false} = user) do
973 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
974 maybe_fetch_follow_information(user)
980 def update_following_count(%User{local: true} = user) do
981 following_count = FollowingRelationship.following_count(user)
984 |> follow_information_changeset(%{following_count: following_count})
988 def set_unread_conversation_count(%User{local: true} = user) do
989 unread_query = Participation.unread_conversation_count_for_user(user)
992 |> join(:inner, [u], p in subquery(unread_query))
994 set: [unread_conversation_count: p.count]
996 |> where([u], u.id == ^user.id)
998 |> Repo.update_all([])
1000 {1, [user]} -> set_cache(user)
1005 def set_unread_conversation_count(user), do: {:ok, user}
1007 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1009 Participation.unread_conversation_count_for_user(user)
1010 |> where([p], p.conversation_id == ^conversation.id)
1013 |> join(:inner, [u], p in subquery(unread_query))
1015 inc: [unread_conversation_count: 1]
1017 |> where([u], u.id == ^user.id)
1018 |> where([u, p], p.count == 0)
1020 |> Repo.update_all([])
1022 {1, [user]} -> set_cache(user)
1027 def increment_unread_conversation_count(_, user), do: {:ok, user}
1029 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1030 def get_users_from_set(ap_ids, local_only \\ true) do
1031 criteria = %{ap_id: ap_ids, deactivated: false}
1032 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1034 User.Query.build(criteria)
1038 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1039 def get_recipients_from_activity(%Activity{recipients: to}) do
1040 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1044 @spec mute(User.t(), User.t(), boolean()) ::
1045 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1046 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1047 add_to_mutes(muter, mutee, notifications?)
1050 def unmute(%User{} = muter, %User{} = mutee) do
1051 remove_from_mutes(muter, mutee)
1054 def subscribe(%User{} = subscriber, %User{} = target) do
1055 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1057 if blocks?(target, subscriber) and deny_follow_blocked do
1058 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1060 # Note: the relationship is inverse: subscriber acts as relationship target
1061 UserRelationship.create_inverse_subscription(target, subscriber)
1065 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1066 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1067 subscribe(subscriber, subscribee)
1071 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1072 # Note: the relationship is inverse: subscriber acts as relationship target
1073 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1076 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1077 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1078 unsubscribe(unsubscriber, user)
1082 def block(%User{} = blocker, %User{} = blocked) do
1083 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1085 if following?(blocker, blocked) do
1086 {:ok, blocker, _} = unfollow(blocker, blocked)
1092 # clear any requested follows as well
1094 case CommonAPI.reject_follow_request(blocked, blocker) do
1095 {:ok, %User{} = updated_blocked} -> updated_blocked
1099 unsubscribe(blocked, blocker)
1101 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1103 {:ok, blocker} = update_follower_count(blocker)
1104 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1105 add_to_block(blocker, blocked)
1108 # helper to handle the block given only an actor's AP id
1109 def block(%User{} = blocker, %{ap_id: ap_id}) do
1110 block(blocker, get_cached_by_ap_id(ap_id))
1113 def unblock(%User{} = blocker, %User{} = blocked) do
1114 remove_from_block(blocker, blocked)
1117 # helper to handle the block given only an actor's AP id
1118 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1119 unblock(blocker, get_cached_by_ap_id(ap_id))
1122 def mutes?(nil, _), do: false
1123 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1125 def mutes_user?(%User{} = user, %User{} = target) do
1126 UserRelationship.mute_exists?(user, target)
1129 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1130 def muted_notifications?(nil, _), do: false
1132 def muted_notifications?(%User{} = user, %User{} = target),
1133 do: UserRelationship.notification_mute_exists?(user, target)
1135 def blocks?(nil, _), do: false
1137 def blocks?(%User{} = user, %User{} = target) do
1138 blocks_user?(user, target) || blocks_domain?(user, target)
1141 def blocks_user?(%User{} = user, %User{} = target) do
1142 UserRelationship.block_exists?(user, target)
1145 def blocks_user?(_, _), do: false
1147 def blocks_domain?(%User{} = user, %User{} = target) do
1148 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1149 %{host: host} = URI.parse(target.ap_id)
1150 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1153 def blocks_domain?(_, _), do: false
1155 def subscribed_to?(%User{} = user, %User{} = target) do
1156 # Note: the relationship is inverse: subscriber acts as relationship target
1157 UserRelationship.inverse_subscription_exists?(target, user)
1160 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1161 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1162 subscribed_to?(user, target)
1167 Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
1168 E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1170 @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1171 def outgoing_relations_ap_ids(_, []), do: %{}
1173 def outgoing_relations_ap_ids(%User{} = user, relationship_types)
1174 when is_list(relationship_types) do
1177 |> assoc(:outgoing_relationships)
1178 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1179 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1180 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1181 |> group_by([user_rel, u], user_rel.relationship_type)
1183 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1188 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1192 def deactivate_async(user, status \\ true) do
1193 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1196 def deactivate(user, status \\ true)
1198 def deactivate(users, status) when is_list(users) do
1199 Repo.transaction(fn ->
1200 for user <- users, do: deactivate(user, status)
1204 def deactivate(%User{} = user, status) do
1205 with {:ok, user} <- set_activation_status(user, status) do
1208 |> Enum.filter(& &1.local)
1209 |> Enum.each(fn follower ->
1210 follower |> update_following_count() |> set_cache()
1213 # Only update local user counts, remote will be update during the next pull.
1216 |> Enum.filter(& &1.local)
1217 |> Enum.each(&update_follower_count/1)
1223 def update_notification_settings(%User{} = user, settings) do
1226 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1229 notification_settings =
1230 user.notification_settings
1231 |> Map.merge(settings)
1232 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1234 params = %{notification_settings: notification_settings}
1237 |> cast(params, [:notification_settings])
1238 |> validate_required([:notification_settings])
1239 |> update_and_set_cache()
1242 def delete(users) when is_list(users) do
1243 for user <- users, do: delete(user)
1246 def delete(%User{} = user) do
1247 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1250 def perform(:force_password_reset, user), do: force_password_reset(user)
1252 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1253 def perform(:delete, %User{} = user) do
1254 {:ok, _user} = ActivityPub.delete(user)
1256 # Remove all relationships
1259 |> Enum.each(fn follower ->
1260 ActivityPub.unfollow(follower, user)
1261 unfollow(follower, user)
1266 |> Enum.each(fn followed ->
1267 ActivityPub.unfollow(user, followed)
1268 unfollow(user, followed)
1271 delete_user_activities(user)
1272 invalidate_cache(user)
1276 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1277 def perform(:fetch_initial_posts, %User{} = user) do
1278 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1280 # Insert all the posts in reverse order, so they're in the right order on the timeline
1281 user.source_data["outbox"]
1282 |> Utils.fetch_ordered_collection(pages)
1284 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1287 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1289 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1290 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1291 when is_list(blocked_identifiers) do
1293 blocked_identifiers,
1294 fn blocked_identifier ->
1295 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1296 {:ok, _user_block} <- block(blocker, blocked),
1297 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1301 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1308 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1309 def perform(:follow_import, %User{} = follower, followed_identifiers)
1310 when is_list(followed_identifiers) do
1312 followed_identifiers,
1313 fn followed_identifier ->
1314 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1315 {:ok, follower} <- maybe_direct_follow(follower, followed),
1316 {:ok, _} <- ActivityPub.follow(follower, followed) do
1320 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1327 @spec external_users_query() :: Ecto.Query.t()
1328 def external_users_query do
1336 @spec external_users(keyword()) :: [User.t()]
1337 def external_users(opts \\ []) do
1339 external_users_query()
1340 |> select([u], struct(u, [:id, :ap_id]))
1344 do: where(query, [u], u.id > ^opts[:max_id]),
1349 do: limit(query, ^opts[:limit]),
1355 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1356 BackgroundWorker.enqueue("blocks_import", %{
1357 "blocker_id" => blocker.id,
1358 "blocked_identifiers" => blocked_identifiers
1362 def follow_import(%User{} = follower, followed_identifiers)
1363 when is_list(followed_identifiers) do
1364 BackgroundWorker.enqueue("follow_import", %{
1365 "follower_id" => follower.id,
1366 "followed_identifiers" => followed_identifiers
1370 def delete_user_activities(%User{ap_id: ap_id}) do
1372 |> Activity.Queries.by_actor()
1373 |> RepoStreamer.chunk_stream(50)
1374 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1378 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1380 |> Object.normalize()
1381 |> ActivityPub.delete()
1384 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1385 object = Object.normalize(activity)
1388 |> get_cached_by_ap_id()
1389 |> ActivityPub.unlike(object)
1392 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1393 object = Object.normalize(activity)
1396 |> get_cached_by_ap_id()
1397 |> ActivityPub.unannounce(object)
1400 defp delete_activity(_activity), do: "Doing nothing"
1402 def html_filter_policy(%User{no_rich_text: true}) do
1403 Pleroma.HTML.Scrubber.TwitterText
1406 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1408 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1410 def get_or_fetch_by_ap_id(ap_id) do
1411 user = get_cached_by_ap_id(ap_id)
1413 if !is_nil(user) and !needs_update?(user) do
1416 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1417 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1419 resp = fetch_by_ap_id(ap_id)
1421 if should_fetch_initial do
1422 with {:ok, %User{} = user} <- resp do
1423 fetch_initial_posts(user)
1432 Creates an internal service actor by URI if missing.
1433 Optionally takes nickname for addressing.
1435 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1436 with user when is_nil(user) <- get_cached_by_ap_id(uri) do
1443 follower_address: uri <> "/followers"
1452 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1455 |> :public_key.pem_decode()
1457 |> :public_key.pem_entry_decode()
1462 def public_key(_), do: {:error, "not found key"}
1464 def get_public_key_for_ap_id(ap_id) do
1465 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1466 {:ok, public_key} <- public_key(user) do
1473 defp blank?(""), do: nil
1474 defp blank?(n), do: n
1476 def insert_or_update_user(data) do
1478 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1479 |> remote_user_creation()
1480 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1484 def ap_enabled?(%User{local: true}), do: true
1485 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1486 def ap_enabled?(_), do: false
1488 @doc "Gets or fetch a user by uri or nickname."
1489 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1490 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1491 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1493 # wait a period of time and return newest version of the User structs
1494 # this is because we have synchronous follow APIs and need to simulate them
1495 # with an async handshake
1496 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1497 with %User{} = a <- get_cached_by_id(a.id),
1498 %User{} = b <- get_cached_by_id(b.id) do
1505 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1506 with :ok <- :timer.sleep(timeout),
1507 %User{} = a <- get_cached_by_id(a.id),
1508 %User{} = b <- get_cached_by_id(b.id) do
1515 def parse_bio(bio) when is_binary(bio) and bio != "" do
1517 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1521 def parse_bio(_), do: ""
1523 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1524 # TODO: get profile URLs other than user.ap_id
1525 profile_urls = [user.ap_id]
1528 |> CommonUtils.format_input("text/plain",
1529 mentions_format: :full,
1530 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1535 def parse_bio(_, _), do: ""
1537 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1538 Repo.transaction(fn ->
1539 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1543 def tag(nickname, tags) when is_binary(nickname),
1544 do: tag(get_by_nickname(nickname), tags)
1546 def tag(%User{} = user, tags),
1547 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1549 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1550 Repo.transaction(fn ->
1551 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1555 def untag(nickname, tags) when is_binary(nickname),
1556 do: untag(get_by_nickname(nickname), tags)
1558 def untag(%User{} = user, tags),
1559 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1561 defp update_tags(%User{} = user, new_tags) do
1562 {:ok, updated_user} =
1564 |> change(%{tags: new_tags})
1565 |> update_and_set_cache()
1570 defp normalize_tags(tags) do
1573 |> Enum.map(&String.downcase/1)
1576 defp local_nickname_regex do
1577 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1578 @extended_local_nickname_regex
1580 @strict_local_nickname_regex
1584 def local_nickname(nickname_or_mention) do
1587 |> String.split("@")
1591 def full_nickname(nickname_or_mention),
1592 do: String.trim_leading(nickname_or_mention, "@")
1594 def error_user(ap_id) do
1598 nickname: "erroruser@example.com",
1599 inserted_at: NaiveDateTime.utc_now()
1603 @spec all_superusers() :: [User.t()]
1604 def all_superusers do
1605 User.Query.build(%{super_users: true, local: true, deactivated: false})
1609 def showing_reblogs?(%User{} = user, %User{} = target) do
1610 not UserRelationship.reblog_mute_exists?(user, target)
1614 The function returns a query to get users with no activity for given interval of days.
1615 Inactive users are those who didn't read any notification, or had any activity where
1616 the user is the activity's actor, during `inactivity_threshold` days.
1617 Deactivated users will not appear in this list.
1621 iex> Pleroma.User.list_inactive_users()
1624 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1625 def list_inactive_users_query(inactivity_threshold \\ 7) do
1626 negative_inactivity_threshold = -inactivity_threshold
1627 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1628 # Subqueries are not supported in `where` clauses, join gets too complicated.
1629 has_read_notifications =
1630 from(n in Pleroma.Notification,
1631 where: n.seen == true,
1633 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1636 |> Pleroma.Repo.all()
1638 from(u in Pleroma.User,
1639 left_join: a in Pleroma.Activity,
1640 on: u.ap_id == a.actor,
1641 where: not is_nil(u.nickname),
1642 where: u.deactivated != ^true,
1643 where: u.id not in ^has_read_notifications,
1646 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1647 is_nil(max(a.inserted_at))
1652 Enable or disable email notifications for user
1656 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1657 Pleroma.User{email_notifications: %{"digest" => true}}
1659 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1660 Pleroma.User{email_notifications: %{"digest" => false}}
1662 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1663 {:ok, t()} | {:error, Ecto.Changeset.t()}
1664 def switch_email_notifications(user, type, status) do
1665 User.update_email_notifications(user, %{type => status})
1669 Set `last_digest_emailed_at` value for the user to current time
1671 @spec touch_last_digest_emailed_at(t()) :: t()
1672 def touch_last_digest_emailed_at(user) do
1673 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1675 {:ok, updated_user} =
1677 |> change(%{last_digest_emailed_at: now})
1678 |> update_and_set_cache()
1683 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1684 def toggle_confirmation(%User{} = user) do
1686 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1687 |> update_and_set_cache()
1690 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1691 def toggle_confirmation(users) do
1692 Enum.map(users, &toggle_confirmation/1)
1695 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1699 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1700 # use instance-default
1701 config = Pleroma.Config.get([:assets, :mascots])
1702 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1703 mascot = Keyword.get(config, default_mascot)
1706 "id" => "default-mascot",
1707 "url" => mascot[:url],
1708 "preview_url" => mascot[:url],
1710 "mime_type" => mascot[:mime_type]
1715 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1717 def ensure_keys_present(%User{} = user) do
1718 with {:ok, pem} <- Keys.generate_rsa_pem() do
1720 |> cast(%{keys: pem}, [:keys])
1721 |> validate_required([:keys])
1722 |> update_and_set_cache()
1726 def get_ap_ids_by_nicknames(nicknames) do
1728 where: u.nickname in ^nicknames,
1734 defdelegate search(query, opts \\ []), to: User.Search
1736 defp put_password_hash(
1737 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1739 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1742 defp put_password_hash(changeset), do: changeset
1744 def is_internal_user?(%User{nickname: nil}), do: true
1745 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1746 def is_internal_user?(_), do: false
1748 # A hack because user delete activities have a fake id for whatever reason
1749 # TODO: Get rid of this
1750 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1752 def get_delivered_users_by_object_id(object_id) do
1754 inner_join: delivery in assoc(u, :deliveries),
1755 where: delivery.object_id == ^object_id
1760 def change_email(user, email) do
1762 |> cast(%{email: email}, [:email])
1763 |> validate_required([:email])
1764 |> unique_constraint(:email)
1765 |> validate_format(:email, @email_regex)
1766 |> update_and_set_cache()
1769 # Internal function; public one is `deactivate/2`
1770 defp set_activation_status(user, deactivated) do
1772 |> cast(%{deactivated: deactivated}, [:deactivated])
1773 |> update_and_set_cache()
1776 def update_banner(user, banner) do
1778 |> cast(%{banner: banner}, [:banner])
1779 |> update_and_set_cache()
1782 def update_background(user, background) do
1784 |> cast(%{background: background}, [:background])
1785 |> update_and_set_cache()
1788 def update_source_data(user, source_data) do
1790 |> cast(%{source_data: source_data}, [:source_data])
1791 |> update_and_set_cache()
1794 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1797 moderator: is_moderator
1801 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1802 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1803 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1804 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1807 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1808 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1812 def fields(%{fields: nil}), do: []
1814 def fields(%{fields: fields}), do: fields
1816 def validate_fields(changeset, remote? \\ false) do
1817 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1818 limit = Pleroma.Config.get([:instance, limit_name], 0)
1821 |> validate_length(:fields, max: limit)
1822 |> validate_change(:fields, fn :fields, fields ->
1823 if Enum.all?(fields, &valid_field?/1) do
1831 defp valid_field?(%{"name" => name, "value" => value}) do
1832 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1833 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1835 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1836 String.length(value) <= value_limit
1839 defp valid_field?(_), do: false
1841 defp truncate_field(%{"name" => name, "value" => value}) do
1843 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1846 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1848 %{"name" => name, "value" => value}
1851 def admin_api_update(user, params) do
1858 |> update_and_set_cache()
1861 def mascot_update(user, url) do
1863 |> cast(%{mascot: url}, [:mascot])
1864 |> validate_required([:mascot])
1865 |> update_and_set_cache()
1868 def mastodon_settings_update(user, settings) do
1870 |> cast(%{settings: settings}, [:settings])
1871 |> validate_required([:settings])
1872 |> update_and_set_cache()
1875 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1876 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1878 if need_confirmation? do
1880 confirmation_pending: true,
1881 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1885 confirmation_pending: false,
1886 confirmation_token: nil
1890 cast(user, params, [:confirmation_pending, :confirmation_token])
1893 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1894 if id not in user.pinned_activities do
1895 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1896 params = %{pinned_activities: user.pinned_activities ++ [id]}
1899 |> cast(params, [:pinned_activities])
1900 |> validate_length(:pinned_activities,
1901 max: max_pinned_statuses,
1902 message: "You have already pinned the maximum number of statuses"
1907 |> update_and_set_cache()
1910 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1911 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1914 |> cast(params, [:pinned_activities])
1915 |> update_and_set_cache()
1918 def update_email_notifications(user, settings) do
1919 email_notifications =
1920 user.email_notifications
1921 |> Map.merge(settings)
1922 |> Map.take(["digest"])
1924 params = %{email_notifications: email_notifications}
1925 fields = [:email_notifications]
1928 |> cast(params, fields)
1929 |> validate_required(fields)
1930 |> update_and_set_cache()
1933 defp set_domain_blocks(user, domain_blocks) do
1934 params = %{domain_blocks: domain_blocks}
1937 |> cast(params, [:domain_blocks])
1938 |> validate_required([:domain_blocks])
1939 |> update_and_set_cache()
1942 def block_domain(user, domain_blocked) do
1943 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1946 def unblock_domain(user, domain_blocked) do
1947 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1950 @spec add_to_block(User.t(), User.t()) ::
1951 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
1952 defp add_to_block(%User{} = user, %User{} = blocked) do
1953 UserRelationship.create_block(user, blocked)
1956 @spec add_to_block(User.t(), User.t()) ::
1957 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
1958 defp remove_from_block(%User{} = user, %User{} = blocked) do
1959 UserRelationship.delete_block(user, blocked)
1962 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
1963 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
1964 {:ok, user_notification_mute} <-
1965 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
1967 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
1971 defp remove_from_mutes(user, %User{} = muted_user) do
1972 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
1973 {:ok, user_notification_mute} <-
1974 UserRelationship.delete_notification_mute(user, muted_user) do
1975 {:ok, [user_mute, user_notification_mute]}
1979 def set_invisible(user, invisible) do
1980 params = %{invisible: invisible}
1983 |> cast(params, [:invisible])
1984 |> validate_required([:invisible])
1985 |> update_and_set_cache()