1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 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
16 alias Pleroma.Conversation.Participation
17 alias Pleroma.Delivery
18 alias Pleroma.FollowingRelationship
21 alias Pleroma.Notification
23 alias Pleroma.Registration
25 alias Pleroma.RepoStreamer
27 alias Pleroma.UserRelationship
29 alias Pleroma.Web.ActivityPub.ActivityPub
30 alias Pleroma.Web.ActivityPub.Utils
31 alias Pleroma.Web.CommonAPI
32 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
33 alias Pleroma.Web.OAuth
34 alias Pleroma.Web.RelMe
35 alias Pleroma.Workers.BackgroundWorker
39 @type t :: %__MODULE__{}
40 @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
41 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
43 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
44 @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])?)*$/
46 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
47 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
49 # AP ID user relationships (blocks, mutes etc.)
50 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
51 @user_relationships_config [
53 blocker_blocks: :blocked_users,
54 blockee_blocks: :blocker_users
57 muter_mutes: :muted_users,
58 mutee_mutes: :muter_users
61 reblog_muter_mutes: :reblog_muted_users,
62 reblog_mutee_mutes: :reblog_muter_users
65 notification_muter_mutes: :notification_muted_users,
66 notification_mutee_mutes: :notification_muter_users
68 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
69 inverse_subscription: [
70 subscribee_subscriptions: :subscriber_users,
71 subscriber_subscriptions: :subscribee_users
77 field(:email, :string)
79 field(:nickname, :string)
80 field(:password_hash, :string)
81 field(:password, :string, virtual: true)
82 field(:password_confirmation, :string, virtual: true)
84 field(:ap_id, :string)
86 field(:local, :boolean, default: true)
87 field(:follower_address, :string)
88 field(:following_address, :string)
89 field(:search_rank, :float, virtual: true)
90 field(:search_type, :integer, virtual: true)
91 field(:tags, {:array, :string}, default: [])
92 field(:last_refreshed_at, :naive_datetime_usec)
93 field(:last_digest_emailed_at, :naive_datetime)
94 field(:banner, :map, default: %{})
95 field(:background, :map, default: %{})
96 field(:source_data, :map, default: %{})
97 field(:note_count, :integer, default: 0)
98 field(:follower_count, :integer, default: 0)
99 field(:following_count, :integer, default: 0)
100 field(:locked, :boolean, default: false)
101 field(:confirmation_pending, :boolean, default: false)
102 field(:password_reset_pending, :boolean, default: false)
103 field(:confirmation_token, :string, default: nil)
104 field(:default_scope, :string, default: "public")
105 field(:domain_blocks, {:array, :string}, default: [])
106 field(:deactivated, :boolean, default: false)
107 field(:no_rich_text, :boolean, default: false)
108 field(:ap_enabled, :boolean, default: false)
109 field(:is_moderator, :boolean, default: false)
110 field(:is_admin, :boolean, default: false)
111 field(:show_role, :boolean, default: true)
112 field(:settings, :map, default: nil)
113 field(:magic_key, :string, default: nil)
114 field(:uri, :string, default: nil)
115 field(:hide_followers_count, :boolean, default: false)
116 field(:hide_follows_count, :boolean, default: false)
117 field(:hide_followers, :boolean, default: false)
118 field(:hide_follows, :boolean, default: false)
119 field(:hide_favorites, :boolean, default: true)
120 field(:unread_conversation_count, :integer, default: 0)
121 field(:pinned_activities, {:array, :string}, default: [])
122 field(:email_notifications, :map, default: %{"digest" => false})
123 field(:mascot, :map, default: nil)
124 field(:emoji, {:array, :map}, default: [])
125 field(:pleroma_settings_store, :map, default: %{})
126 field(:fields, {:array, :map}, default: [])
127 field(:raw_fields, {:array, :map}, default: [])
128 field(:discoverable, :boolean, default: false)
129 field(:invisible, :boolean, default: false)
130 field(:allow_following_move, :boolean, default: true)
131 field(:skip_thread_containment, :boolean, default: false)
132 field(:actor_type, :string, default: "Person")
133 field(:also_known_as, {:array, :string}, default: [])
136 :notification_settings,
137 Pleroma.User.NotificationSetting,
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 status account"
222 @spec account_status(User.t()) :: account_status()
223 def account_status(%User{deactivated: true}), do: :deactivated
224 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
226 def account_status(%User{confirmation_pending: true}) do
227 case Config.get([:instance, :account_activation_required]) do
228 true -> :confirmation_pending
233 def account_status(%User{}), do: :active
235 @spec visible_for?(User.t(), User.t() | nil) :: boolean()
236 def visible_for?(user, for_user \\ nil)
238 def visible_for?(%User{invisible: true}, _), do: false
240 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
242 def visible_for?(%User{} = user, for_user) do
243 account_status(user) == :active || superuser?(for_user)
246 def visible_for?(_, _), do: false
248 @spec superuser?(User.t()) :: boolean()
249 def superuser?(%User{local: true, is_admin: true}), do: true
250 def superuser?(%User{local: true, is_moderator: true}), do: true
251 def superuser?(_), do: false
253 @spec invisible?(User.t()) :: boolean()
254 def invisible?(%User{invisible: true}), do: true
255 def invisible?(_), do: false
257 def avatar_url(user, options \\ []) do
259 %{"url" => [%{"href" => href} | _]} -> href
260 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
264 def banner_url(user, options \\ []) do
266 %{"url" => [%{"href" => href} | _]} -> href
267 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
271 def profile_url(%User{source_data: %{"url" => url}}), do: url
272 def profile_url(%User{ap_id: ap_id}), do: ap_id
273 def profile_url(_), do: nil
275 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
277 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
278 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
280 @spec ap_following(User.t()) :: Sring.t()
281 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
282 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
284 def follow_state(%User{} = user, %User{} = target) do
285 case Utils.fetch_latest_follow(user, target) do
286 %{data: %{"state" => state}} -> state
287 # Ideally this would be nil, but then Cachex does not commit the value
292 def get_cached_follow_state(user, target) do
293 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
294 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
297 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
298 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
299 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
302 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
303 def restrict_deactivated(query) do
304 from(u in query, where: u.deactivated != ^true)
307 defdelegate following_count(user), to: FollowingRelationship
309 defp truncate_fields_param(params) do
310 if Map.has_key?(params, :fields) do
311 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
317 defp truncate_if_exists(params, key, max_length) do
318 if Map.has_key?(params, key) and is_binary(params[key]) do
319 {value, _chopped} = String.split_at(params[key], max_length)
320 Map.put(params, key, value)
326 def remote_user_creation(params) do
327 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
328 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
332 |> truncate_if_exists(:name, name_limit)
333 |> truncate_if_exists(:bio, bio_limit)
334 |> truncate_fields_param()
354 :hide_followers_count,
365 |> validate_required([:name, :ap_id])
366 |> unique_constraint(:nickname)
367 |> validate_format(:nickname, @email_regex)
368 |> validate_length(:bio, max: bio_limit)
369 |> validate_length(:name, max: name_limit)
370 |> validate_fields(true)
372 case params[:source_data] do
373 %{"followers" => followers, "following" => following} ->
375 |> put_change(:follower_address, followers)
376 |> put_change(:following_address, following)
379 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
380 put_change(changeset, :follower_address, followers)
384 def update_changeset(struct, params \\ %{}) do
385 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
386 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
401 :hide_followers_count,
404 :allow_following_move,
407 :skip_thread_containment,
410 :pleroma_settings_store,
416 |> unique_constraint(:nickname)
417 |> validate_format(:nickname, local_nickname_regex())
418 |> validate_length(:bio, max: bio_limit)
419 |> validate_length(:name, min: 1, max: name_limit)
421 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
422 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
423 |> put_change_if_present(:banner, &put_upload(&1, :banner))
424 |> put_change_if_present(:background, &put_upload(&1, :background))
425 |> put_change_if_present(
426 :pleroma_settings_store,
427 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
429 |> validate_fields(false)
432 defp put_fields(changeset) do
433 if raw_fields = get_change(changeset, :raw_fields) do
436 |> Enum.filter(fn %{"name" => n} -> n != "" end)
440 |> Enum.map(fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
443 |> put_change(:raw_fields, raw_fields)
444 |> put_change(:fields, fields)
450 defp put_change_if_present(changeset, map_field, value_function) do
451 if value = get_change(changeset, map_field) do
452 with {:ok, new_value} <- value_function.(value) do
453 put_change(changeset, map_field, new_value)
462 defp put_upload(value, type) do
463 with %Plug.Upload{} <- value,
464 {:ok, object} <- ActivityPub.upload(value, type: type) do
469 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
470 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
471 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
473 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
475 params = if remote?, do: truncate_fields_param(params), else: params
497 :allow_following_move,
499 :hide_followers_count,
505 |> unique_constraint(:nickname)
506 |> validate_format(:nickname, local_nickname_regex())
507 |> validate_length(:bio, max: bio_limit)
508 |> validate_length(:name, max: name_limit)
509 |> validate_fields(remote?)
512 def update_as_admin_changeset(struct, params) do
514 |> update_changeset(params)
515 |> cast(params, [:email])
516 |> delete_change(:also_known_as)
517 |> unique_constraint(:email)
518 |> validate_format(:email, @email_regex)
521 @spec update_as_admin(%User{}, map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
522 def update_as_admin(user, params) do
523 params = Map.put(params, "password_confirmation", params["password"])
524 changeset = update_as_admin_changeset(user, params)
526 if params["password"] do
527 reset_password(user, changeset, params)
529 User.update_and_set_cache(changeset)
533 def password_update_changeset(struct, params) do
535 |> cast(params, [:password, :password_confirmation])
536 |> validate_required([:password, :password_confirmation])
537 |> validate_confirmation(:password)
538 |> put_password_hash()
539 |> put_change(:password_reset_pending, false)
542 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
543 def reset_password(%User{} = user, params) do
544 reset_password(user, user, params)
547 def reset_password(%User{id: user_id} = user, struct, params) do
550 |> Multi.update(:user, password_update_changeset(struct, params))
551 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
552 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
554 case Repo.transaction(multi) do
555 {:ok, %{user: user} = _} -> set_cache(user)
556 {:error, _, changeset, _} -> {:error, changeset}
560 def update_password_reset_pending(user, value) do
563 |> put_change(:password_reset_pending, value)
564 |> update_and_set_cache()
567 def force_password_reset_async(user) do
568 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
571 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
572 def force_password_reset(user), do: update_password_reset_pending(user, true)
574 def register_changeset(struct, params \\ %{}, opts \\ []) do
575 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
576 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
579 if is_nil(opts[:need_confirmation]) do
580 Pleroma.Config.get([:instance, :account_activation_required])
582 opts[:need_confirmation]
586 |> confirmation_changeset(need_confirmation: need_confirmation?)
587 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
588 |> validate_required([:name, :nickname, :password, :password_confirmation])
589 |> validate_confirmation(:password)
590 |> unique_constraint(:email)
591 |> unique_constraint(:nickname)
592 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
593 |> validate_format(:nickname, local_nickname_regex())
594 |> validate_format(:email, @email_regex)
595 |> validate_length(:bio, max: bio_limit)
596 |> validate_length(:name, min: 1, max: name_limit)
597 |> maybe_validate_required_email(opts[:external])
600 |> unique_constraint(:ap_id)
601 |> put_following_and_follower_address()
604 def maybe_validate_required_email(changeset, true), do: changeset
606 def maybe_validate_required_email(changeset, _) do
607 if Pleroma.Config.get([:instance, :account_activation_required]) do
608 validate_required(changeset, [:email])
614 defp put_ap_id(changeset) do
615 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
616 put_change(changeset, :ap_id, ap_id)
619 defp put_following_and_follower_address(changeset) do
620 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
623 |> put_change(:follower_address, followers)
626 defp autofollow_users(user) do
627 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
630 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
633 follow_all(user, autofollowed_users)
636 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
637 def register(%Ecto.Changeset{} = changeset) do
638 with {:ok, user} <- Repo.insert(changeset) do
639 post_register_action(user)
643 def post_register_action(%User{} = user) do
644 with {:ok, user} <- autofollow_users(user),
645 {:ok, user} <- set_cache(user),
646 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
647 {:ok, _} <- try_send_confirmation_email(user) do
652 def try_send_confirmation_email(%User{} = user) do
653 if user.confirmation_pending &&
654 Pleroma.Config.get([:instance, :account_activation_required]) do
656 |> Pleroma.Emails.UserEmail.account_confirmation_email()
657 |> Pleroma.Emails.Mailer.deliver_async()
665 def try_send_confirmation_email(users) do
666 Enum.each(users, &try_send_confirmation_email/1)
669 def needs_update?(%User{local: true}), do: false
671 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
673 def needs_update?(%User{local: false} = user) do
674 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
677 def needs_update?(_), do: true
679 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
680 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
681 follow(follower, followed, "pending")
684 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
685 follow(follower, followed)
688 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
689 if not ap_enabled?(followed) do
690 follow(follower, followed)
696 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
697 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
698 def follow_all(follower, followeds) do
700 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
701 |> Enum.each(&follow(follower, &1, "accept"))
706 defdelegate following(user), to: FollowingRelationship
708 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
709 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
712 followed.deactivated ->
713 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
715 deny_follow_blocked and blocks?(followed, follower) ->
716 {:error, "Could not follow user: #{followed.nickname} blocked you."}
719 FollowingRelationship.follow(follower, followed, state)
721 {:ok, _} = update_follower_count(followed)
724 |> update_following_count()
729 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
730 {:error, "Not subscribed!"}
733 def unfollow(%User{} = follower, %User{} = followed) do
734 case get_follow_state(follower, followed) do
735 state when state in ["accept", "pending"] ->
736 FollowingRelationship.unfollow(follower, followed)
737 {:ok, followed} = update_follower_count(followed)
741 |> update_following_count()
744 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
747 {:error, "Not subscribed!"}
751 defdelegate following?(follower, followed), to: FollowingRelationship
753 def get_follow_state(%User{} = follower, %User{} = following) do
754 following_relationship = FollowingRelationship.get(follower, following)
756 case {following_relationship, following.local} do
758 case Utils.fetch_latest_follow(follower, following) do
759 %{data: %{"state" => state}} when state in ["pending", "accept"] -> state
763 {%{state: state}, _} ->
771 def locked?(%User{} = user) do
776 Repo.get_by(User, id: id)
779 def get_by_ap_id(ap_id) do
780 Repo.get_by(User, ap_id: ap_id)
783 def get_all_by_ap_id(ap_ids) do
784 from(u in __MODULE__,
785 where: u.ap_id in ^ap_ids
790 def get_all_by_ids(ids) do
791 from(u in __MODULE__, where: u.id in ^ids)
795 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
796 # of the ap_id and the domain and tries to get that user
797 def get_by_guessed_nickname(ap_id) do
798 domain = URI.parse(ap_id).host
799 name = List.last(String.split(ap_id, "/"))
800 nickname = "#{name}@#{domain}"
802 get_cached_by_nickname(nickname)
805 def set_cache({:ok, user}), do: set_cache(user)
806 def set_cache({:error, err}), do: {:error, err}
808 def set_cache(%User{} = user) do
809 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
810 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
814 def update_and_set_cache(struct, params) do
816 |> update_changeset(params)
817 |> update_and_set_cache()
820 def update_and_set_cache(changeset) do
821 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
826 def invalidate_cache(user) do
827 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
828 Cachex.del(:user_cache, "nickname:#{user.nickname}")
831 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
832 def get_cached_by_ap_id(ap_id) do
833 key = "ap_id:#{ap_id}"
835 with {:ok, nil} <- Cachex.get(:user_cache, key),
836 user when not is_nil(user) <- get_by_ap_id(ap_id),
837 {:ok, true} <- Cachex.put(:user_cache, key, user) do
845 def get_cached_by_id(id) do
849 Cachex.fetch!(:user_cache, key, fn _ ->
853 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
854 {:commit, user.ap_id}
860 get_cached_by_ap_id(ap_id)
863 def get_cached_by_nickname(nickname) do
864 key = "nickname:#{nickname}"
866 Cachex.fetch!(:user_cache, key, fn ->
867 case get_or_fetch_by_nickname(nickname) do
868 {:ok, user} -> {:commit, user}
869 {:error, _error} -> {:ignore, nil}
874 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
875 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
878 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
879 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
881 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
882 get_cached_by_nickname(nickname_or_id)
884 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
885 get_cached_by_nickname(nickname_or_id)
892 def get_by_nickname(nickname) do
893 Repo.get_by(User, nickname: nickname) ||
894 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
895 Repo.get_by(User, nickname: local_nickname(nickname))
899 def get_by_email(email), do: Repo.get_by(User, email: email)
901 def get_by_nickname_or_email(nickname_or_email) do
902 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
905 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
907 def get_or_fetch_by_nickname(nickname) do
908 with %User{} = user <- get_by_nickname(nickname) do
912 with [_nick, _domain] <- String.split(nickname, "@"),
913 {:ok, user} <- fetch_by_nickname(nickname) do
916 _e -> {:error, "not found " <> nickname}
921 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
922 def get_followers_query(%User{} = user, nil) do
923 User.Query.build(%{followers: user, deactivated: false})
926 def get_followers_query(user, page) do
928 |> get_followers_query(nil)
929 |> User.Query.paginate(page, 20)
932 @spec get_followers_query(User.t()) :: Ecto.Query.t()
933 def get_followers_query(user), do: get_followers_query(user, nil)
935 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
936 def get_followers(user, page \\ nil) do
938 |> get_followers_query(page)
942 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
943 def get_external_followers(user, page \\ nil) do
945 |> get_followers_query(page)
946 |> User.Query.build(%{external: true})
950 def get_followers_ids(user, page \\ nil) do
952 |> get_followers_query(page)
957 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
958 def get_friends_query(%User{} = user, nil) do
959 User.Query.build(%{friends: user, deactivated: false})
962 def get_friends_query(user, page) do
964 |> get_friends_query(nil)
965 |> User.Query.paginate(page, 20)
968 @spec get_friends_query(User.t()) :: Ecto.Query.t()
969 def get_friends_query(user), do: get_friends_query(user, nil)
971 def get_friends(user, page \\ nil) do
973 |> get_friends_query(page)
977 def get_friends_ap_ids(user) do
979 |> get_friends_query(nil)
980 |> select([u], u.ap_id)
984 def get_friends_ids(user, page \\ nil) do
986 |> get_friends_query(page)
991 defdelegate get_follow_requests(user), to: FollowingRelationship
993 def increase_note_count(%User{} = user) do
995 |> where(id: ^user.id)
996 |> update([u], inc: [note_count: 1])
998 |> Repo.update_all([])
1000 {1, [user]} -> set_cache(user)
1005 def decrease_note_count(%User{} = user) do
1007 |> where(id: ^user.id)
1010 note_count: fragment("greatest(0, note_count - 1)")
1014 |> Repo.update_all([])
1016 {1, [user]} -> set_cache(user)
1021 def update_note_count(%User{} = user, note_count \\ nil) do
1026 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1032 |> cast(%{note_count: note_count}, [:note_count])
1033 |> update_and_set_cache()
1036 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1037 def maybe_fetch_follow_information(user) do
1038 with {:ok, user} <- fetch_follow_information(user) do
1042 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1048 def fetch_follow_information(user) do
1049 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1051 |> follow_information_changeset(info)
1052 |> update_and_set_cache()
1056 defp follow_information_changeset(user, params) do
1063 :hide_followers_count,
1068 def update_follower_count(%User{} = user) do
1069 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
1070 follower_count_query =
1071 User.Query.build(%{followers: user, deactivated: false})
1072 |> select([u], %{count: count(u.id)})
1075 |> where(id: ^user.id)
1076 |> join(:inner, [u], s in subquery(follower_count_query))
1078 set: [follower_count: s.count]
1081 |> Repo.update_all([])
1083 {1, [user]} -> set_cache(user)
1087 {:ok, maybe_fetch_follow_information(user)}
1091 @spec update_following_count(User.t()) :: User.t()
1092 def update_following_count(%User{local: false} = user) do
1093 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1094 maybe_fetch_follow_information(user)
1100 def update_following_count(%User{local: true} = user) do
1101 following_count = FollowingRelationship.following_count(user)
1104 |> follow_information_changeset(%{following_count: following_count})
1108 def set_unread_conversation_count(%User{local: true} = user) do
1109 unread_query = Participation.unread_conversation_count_for_user(user)
1112 |> join(:inner, [u], p in subquery(unread_query))
1114 set: [unread_conversation_count: p.count]
1116 |> where([u], u.id == ^user.id)
1118 |> Repo.update_all([])
1120 {1, [user]} -> set_cache(user)
1125 def set_unread_conversation_count(user), do: {:ok, user}
1127 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1129 Participation.unread_conversation_count_for_user(user)
1130 |> where([p], p.conversation_id == ^conversation.id)
1133 |> join(:inner, [u], p in subquery(unread_query))
1135 inc: [unread_conversation_count: 1]
1137 |> where([u], u.id == ^user.id)
1138 |> where([u, p], p.count == 0)
1140 |> Repo.update_all([])
1142 {1, [user]} -> set_cache(user)
1147 def increment_unread_conversation_count(_, user), do: {:ok, user}
1149 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1150 def get_users_from_set(ap_ids, local_only \\ true) do
1151 criteria = %{ap_id: ap_ids, deactivated: false}
1152 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1154 User.Query.build(criteria)
1158 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1159 def get_recipients_from_activity(%Activity{recipients: to}) do
1160 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1164 @spec mute(User.t(), User.t(), boolean()) ::
1165 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1166 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1167 add_to_mutes(muter, mutee, notifications?)
1170 def unmute(%User{} = muter, %User{} = mutee) do
1171 remove_from_mutes(muter, mutee)
1174 def subscribe(%User{} = subscriber, %User{} = target) do
1175 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1177 if blocks?(target, subscriber) and deny_follow_blocked do
1178 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1180 # Note: the relationship is inverse: subscriber acts as relationship target
1181 UserRelationship.create_inverse_subscription(target, subscriber)
1185 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1186 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1187 subscribe(subscriber, subscribee)
1191 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1192 # Note: the relationship is inverse: subscriber acts as relationship target
1193 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1196 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1197 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1198 unsubscribe(unsubscriber, user)
1202 def block(%User{} = blocker, %User{} = blocked) do
1203 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1205 if following?(blocker, blocked) do
1206 {:ok, blocker, _} = unfollow(blocker, blocked)
1212 # clear any requested follows as well
1214 case CommonAPI.reject_follow_request(blocked, blocker) do
1215 {:ok, %User{} = updated_blocked} -> updated_blocked
1219 unsubscribe(blocked, blocker)
1221 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1223 {:ok, blocker} = update_follower_count(blocker)
1224 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1225 add_to_block(blocker, blocked)
1228 # helper to handle the block given only an actor's AP id
1229 def block(%User{} = blocker, %{ap_id: ap_id}) do
1230 block(blocker, get_cached_by_ap_id(ap_id))
1233 def unblock(%User{} = blocker, %User{} = blocked) do
1234 remove_from_block(blocker, blocked)
1237 # helper to handle the block given only an actor's AP id
1238 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1239 unblock(blocker, get_cached_by_ap_id(ap_id))
1242 def mutes?(nil, _), do: false
1243 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1245 def mutes_user?(%User{} = user, %User{} = target) do
1246 UserRelationship.mute_exists?(user, target)
1249 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1250 def muted_notifications?(nil, _), do: false
1252 def muted_notifications?(%User{} = user, %User{} = target),
1253 do: UserRelationship.notification_mute_exists?(user, target)
1255 def blocks?(nil, _), do: false
1257 def blocks?(%User{} = user, %User{} = target) do
1258 blocks_user?(user, target) ||
1259 (!User.following?(user, target) && blocks_domain?(user, target))
1262 def blocks_user?(%User{} = user, %User{} = target) do
1263 UserRelationship.block_exists?(user, target)
1266 def blocks_user?(_, _), do: false
1268 def blocks_domain?(%User{} = user, %User{} = target) do
1269 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1270 %{host: host} = URI.parse(target.ap_id)
1271 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1274 def blocks_domain?(_, _), do: false
1276 def subscribed_to?(%User{} = user, %User{} = target) do
1277 # Note: the relationship is inverse: subscriber acts as relationship target
1278 UserRelationship.inverse_subscription_exists?(target, user)
1281 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1282 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1283 subscribed_to?(user, target)
1288 Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
1289 E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1291 @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1292 def outgoing_relations_ap_ids(_, []), do: %{}
1294 def outgoing_relations_ap_ids(%User{} = user, relationship_types)
1295 when is_list(relationship_types) do
1298 |> assoc(:outgoing_relationships)
1299 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1300 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1301 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1302 |> group_by([user_rel, u], user_rel.relationship_type)
1304 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1309 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1313 def deactivate_async(user, status \\ true) do
1314 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1317 def deactivate(user, status \\ true)
1319 def deactivate(users, status) when is_list(users) do
1320 Repo.transaction(fn ->
1321 for user <- users, do: deactivate(user, status)
1325 def deactivate(%User{} = user, status) do
1326 with {:ok, user} <- set_activation_status(user, status) do
1329 |> Enum.filter(& &1.local)
1330 |> Enum.each(fn follower ->
1331 follower |> update_following_count() |> set_cache()
1334 # Only update local user counts, remote will be update during the next pull.
1337 |> Enum.filter(& &1.local)
1338 |> Enum.each(&update_follower_count/1)
1344 def update_notification_settings(%User{} = user, settings) do
1346 |> cast(%{notification_settings: settings}, [])
1347 |> cast_embed(:notification_settings)
1348 |> validate_required([:notification_settings])
1349 |> update_and_set_cache()
1352 def delete(users) when is_list(users) do
1353 for user <- users, do: delete(user)
1356 def delete(%User{} = user) do
1357 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1360 def perform(:force_password_reset, user), do: force_password_reset(user)
1362 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1363 def perform(:delete, %User{} = user) do
1364 {:ok, _user} = ActivityPub.delete(user)
1366 # Remove all relationships
1369 |> Enum.each(fn follower ->
1370 ActivityPub.unfollow(follower, user)
1371 unfollow(follower, user)
1376 |> Enum.each(fn followed ->
1377 ActivityPub.unfollow(user, followed)
1378 unfollow(user, followed)
1381 delete_user_activities(user)
1382 invalidate_cache(user)
1386 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1388 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1389 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1390 when is_list(blocked_identifiers) do
1392 blocked_identifiers,
1393 fn blocked_identifier ->
1394 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1395 {:ok, _user_block} <- block(blocker, blocked),
1396 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1400 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1407 def perform(:follow_import, %User{} = follower, followed_identifiers)
1408 when is_list(followed_identifiers) do
1410 followed_identifiers,
1411 fn followed_identifier ->
1412 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1413 {:ok, follower} <- maybe_direct_follow(follower, followed),
1414 {:ok, _} <- ActivityPub.follow(follower, followed) do
1418 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1425 @spec external_users_query() :: Ecto.Query.t()
1426 def external_users_query do
1434 @spec external_users(keyword()) :: [User.t()]
1435 def external_users(opts \\ []) do
1437 external_users_query()
1438 |> select([u], struct(u, [:id, :ap_id]))
1442 do: where(query, [u], u.id > ^opts[:max_id]),
1447 do: limit(query, ^opts[:limit]),
1453 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1454 BackgroundWorker.enqueue("blocks_import", %{
1455 "blocker_id" => blocker.id,
1456 "blocked_identifiers" => blocked_identifiers
1460 def follow_import(%User{} = follower, followed_identifiers)
1461 when is_list(followed_identifiers) do
1462 BackgroundWorker.enqueue("follow_import", %{
1463 "follower_id" => follower.id,
1464 "followed_identifiers" => followed_identifiers
1468 def delete_user_activities(%User{ap_id: ap_id}) do
1470 |> Activity.Queries.by_actor()
1471 |> RepoStreamer.chunk_stream(50)
1472 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1476 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1478 |> Object.normalize()
1479 |> ActivityPub.delete()
1482 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1483 object = Object.normalize(activity)
1486 |> get_cached_by_ap_id()
1487 |> ActivityPub.unlike(object)
1490 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1491 object = Object.normalize(activity)
1494 |> get_cached_by_ap_id()
1495 |> ActivityPub.unannounce(object)
1498 defp delete_activity(_activity), do: "Doing nothing"
1500 def html_filter_policy(%User{no_rich_text: true}) do
1501 Pleroma.HTML.Scrubber.TwitterText
1504 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1506 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1508 def get_or_fetch_by_ap_id(ap_id) do
1509 user = get_cached_by_ap_id(ap_id)
1511 if !is_nil(user) and !needs_update?(user) do
1514 fetch_by_ap_id(ap_id)
1519 Creates an internal service actor by URI if missing.
1520 Optionally takes nickname for addressing.
1522 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1523 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1525 case get_cached_by_ap_id(uri) do
1527 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1528 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1532 %User{invisible: false} = user ->
1542 @spec set_invisible(User.t()) :: {:ok, User.t()}
1543 defp set_invisible(user) do
1545 |> change(%{invisible: true})
1546 |> update_and_set_cache()
1549 @spec create_service_actor(String.t(), String.t()) ::
1550 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1551 defp create_service_actor(uri, nickname) do
1557 follower_address: uri <> "/followers"
1560 |> unique_constraint(:nickname)
1566 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1569 |> :public_key.pem_decode()
1571 |> :public_key.pem_entry_decode()
1576 def public_key(_), do: {:error, "not found key"}
1578 def get_public_key_for_ap_id(ap_id) do
1579 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1580 {:ok, public_key} <- public_key(user) do
1587 defp blank?(""), do: nil
1588 defp blank?(n), do: n
1590 def insert_or_update_user(data) do
1592 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1593 |> remote_user_creation()
1594 |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
1598 def ap_enabled?(%User{local: true}), do: true
1599 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1600 def ap_enabled?(_), do: false
1602 @doc "Gets or fetch a user by uri or nickname."
1603 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1604 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1605 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1607 # wait a period of time and return newest version of the User structs
1608 # this is because we have synchronous follow APIs and need to simulate them
1609 # with an async handshake
1610 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1611 with %User{} = a <- get_cached_by_id(a.id),
1612 %User{} = b <- get_cached_by_id(b.id) do
1619 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1620 with :ok <- :timer.sleep(timeout),
1621 %User{} = a <- get_cached_by_id(a.id),
1622 %User{} = b <- get_cached_by_id(b.id) do
1629 def parse_bio(bio) when is_binary(bio) and bio != "" do
1631 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1635 def parse_bio(_), do: ""
1637 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1638 # TODO: get profile URLs other than user.ap_id
1639 profile_urls = [user.ap_id]
1642 |> CommonUtils.format_input("text/plain",
1643 mentions_format: :full,
1644 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1649 def parse_bio(_, _), do: ""
1651 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1652 Repo.transaction(fn ->
1653 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1657 def tag(nickname, tags) when is_binary(nickname),
1658 do: tag(get_by_nickname(nickname), tags)
1660 def tag(%User{} = user, tags),
1661 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1663 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1664 Repo.transaction(fn ->
1665 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1669 def untag(nickname, tags) when is_binary(nickname),
1670 do: untag(get_by_nickname(nickname), tags)
1672 def untag(%User{} = user, tags),
1673 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1675 defp update_tags(%User{} = user, new_tags) do
1676 {:ok, updated_user} =
1678 |> change(%{tags: new_tags})
1679 |> update_and_set_cache()
1684 defp normalize_tags(tags) do
1687 |> Enum.map(&String.downcase/1)
1690 defp local_nickname_regex do
1691 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1692 @extended_local_nickname_regex
1694 @strict_local_nickname_regex
1698 def local_nickname(nickname_or_mention) do
1701 |> String.split("@")
1705 def full_nickname(nickname_or_mention),
1706 do: String.trim_leading(nickname_or_mention, "@")
1708 def error_user(ap_id) do
1712 nickname: "erroruser@example.com",
1713 inserted_at: NaiveDateTime.utc_now()
1717 @spec all_superusers() :: [User.t()]
1718 def all_superusers do
1719 User.Query.build(%{super_users: true, local: true, deactivated: false})
1723 def showing_reblogs?(%User{} = user, %User{} = target) do
1724 not UserRelationship.reblog_mute_exists?(user, target)
1728 The function returns a query to get users with no activity for given interval of days.
1729 Inactive users are those who didn't read any notification, or had any activity where
1730 the user is the activity's actor, during `inactivity_threshold` days.
1731 Deactivated users will not appear in this list.
1735 iex> Pleroma.User.list_inactive_users()
1738 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1739 def list_inactive_users_query(inactivity_threshold \\ 7) do
1740 negative_inactivity_threshold = -inactivity_threshold
1741 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1742 # Subqueries are not supported in `where` clauses, join gets too complicated.
1743 has_read_notifications =
1744 from(n in Pleroma.Notification,
1745 where: n.seen == true,
1747 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1750 |> Pleroma.Repo.all()
1752 from(u in Pleroma.User,
1753 left_join: a in Pleroma.Activity,
1754 on: u.ap_id == a.actor,
1755 where: not is_nil(u.nickname),
1756 where: u.deactivated != ^true,
1757 where: u.id not in ^has_read_notifications,
1760 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1761 is_nil(max(a.inserted_at))
1766 Enable or disable email notifications for user
1770 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1771 Pleroma.User{email_notifications: %{"digest" => true}}
1773 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1774 Pleroma.User{email_notifications: %{"digest" => false}}
1776 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1777 {:ok, t()} | {:error, Ecto.Changeset.t()}
1778 def switch_email_notifications(user, type, status) do
1779 User.update_email_notifications(user, %{type => status})
1783 Set `last_digest_emailed_at` value for the user to current time
1785 @spec touch_last_digest_emailed_at(t()) :: t()
1786 def touch_last_digest_emailed_at(user) do
1787 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1789 {:ok, updated_user} =
1791 |> change(%{last_digest_emailed_at: now})
1792 |> update_and_set_cache()
1797 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1798 def toggle_confirmation(%User{} = user) do
1800 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1801 |> update_and_set_cache()
1804 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1805 def toggle_confirmation(users) do
1806 Enum.map(users, &toggle_confirmation/1)
1809 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1813 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1814 # use instance-default
1815 config = Pleroma.Config.get([:assets, :mascots])
1816 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1817 mascot = Keyword.get(config, default_mascot)
1820 "id" => "default-mascot",
1821 "url" => mascot[:url],
1822 "preview_url" => mascot[:url],
1824 "mime_type" => mascot[:mime_type]
1829 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1831 def ensure_keys_present(%User{} = user) do
1832 with {:ok, pem} <- Keys.generate_rsa_pem() do
1834 |> cast(%{keys: pem}, [:keys])
1835 |> validate_required([:keys])
1836 |> update_and_set_cache()
1840 def get_ap_ids_by_nicknames(nicknames) do
1842 where: u.nickname in ^nicknames,
1848 defdelegate search(query, opts \\ []), to: User.Search
1850 defp put_password_hash(
1851 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1853 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1856 defp put_password_hash(changeset), do: changeset
1858 def is_internal_user?(%User{nickname: nil}), do: true
1859 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1860 def is_internal_user?(_), do: false
1862 # A hack because user delete activities have a fake id for whatever reason
1863 # TODO: Get rid of this
1864 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1866 def get_delivered_users_by_object_id(object_id) do
1868 inner_join: delivery in assoc(u, :deliveries),
1869 where: delivery.object_id == ^object_id
1874 def change_email(user, email) do
1876 |> cast(%{email: email}, [:email])
1877 |> validate_required([:email])
1878 |> unique_constraint(:email)
1879 |> validate_format(:email, @email_regex)
1880 |> update_and_set_cache()
1883 # Internal function; public one is `deactivate/2`
1884 defp set_activation_status(user, deactivated) do
1886 |> cast(%{deactivated: deactivated}, [:deactivated])
1887 |> update_and_set_cache()
1890 def update_banner(user, banner) do
1892 |> cast(%{banner: banner}, [:banner])
1893 |> update_and_set_cache()
1896 def update_background(user, background) do
1898 |> cast(%{background: background}, [:background])
1899 |> update_and_set_cache()
1902 def update_source_data(user, source_data) do
1904 |> cast(%{source_data: source_data}, [:source_data])
1905 |> update_and_set_cache()
1908 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1911 moderator: is_moderator
1915 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1916 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1917 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1918 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1921 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1922 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1926 def fields(%{fields: nil}), do: []
1928 def fields(%{fields: fields}), do: fields
1930 def sanitized_fields(%User{} = user) do
1933 |> Enum.map(fn %{"name" => name, "value" => value} ->
1936 "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
1941 def validate_fields(changeset, remote? \\ false) do
1942 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1943 limit = Pleroma.Config.get([:instance, limit_name], 0)
1946 |> validate_length(:fields, max: limit)
1947 |> validate_change(:fields, fn :fields, fields ->
1948 if Enum.all?(fields, &valid_field?/1) do
1956 defp valid_field?(%{"name" => name, "value" => value}) do
1957 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1958 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1960 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1961 String.length(value) <= value_limit
1964 defp valid_field?(_), do: false
1966 defp truncate_field(%{"name" => name, "value" => value}) do
1968 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1971 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1973 %{"name" => name, "value" => value}
1976 def admin_api_update(user, params) do
1983 |> update_and_set_cache()
1986 @doc "Signs user out of all applications"
1987 def global_sign_out(user) do
1988 OAuth.Authorization.delete_user_authorizations(user)
1989 OAuth.Token.delete_user_tokens(user)
1992 def mascot_update(user, url) do
1994 |> cast(%{mascot: url}, [:mascot])
1995 |> validate_required([:mascot])
1996 |> update_and_set_cache()
1999 def mastodon_settings_update(user, settings) do
2001 |> cast(%{settings: settings}, [:settings])
2002 |> validate_required([:settings])
2003 |> update_and_set_cache()
2006 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2007 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2009 if need_confirmation? do
2011 confirmation_pending: true,
2012 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2016 confirmation_pending: false,
2017 confirmation_token: nil
2021 cast(user, params, [:confirmation_pending, :confirmation_token])
2024 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2025 if id not in user.pinned_activities do
2026 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
2027 params = %{pinned_activities: user.pinned_activities ++ [id]}
2030 |> cast(params, [:pinned_activities])
2031 |> validate_length(:pinned_activities,
2032 max: max_pinned_statuses,
2033 message: "You have already pinned the maximum number of statuses"
2038 |> update_and_set_cache()
2041 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2042 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2045 |> cast(params, [:pinned_activities])
2046 |> update_and_set_cache()
2049 def update_email_notifications(user, settings) do
2050 email_notifications =
2051 user.email_notifications
2052 |> Map.merge(settings)
2053 |> Map.take(["digest"])
2055 params = %{email_notifications: email_notifications}
2056 fields = [:email_notifications]
2059 |> cast(params, fields)
2060 |> validate_required(fields)
2061 |> update_and_set_cache()
2064 defp set_domain_blocks(user, domain_blocks) do
2065 params = %{domain_blocks: domain_blocks}
2068 |> cast(params, [:domain_blocks])
2069 |> validate_required([:domain_blocks])
2070 |> update_and_set_cache()
2073 def block_domain(user, domain_blocked) do
2074 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2077 def unblock_domain(user, domain_blocked) do
2078 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2081 @spec add_to_block(User.t(), User.t()) ::
2082 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2083 defp add_to_block(%User{} = user, %User{} = blocked) do
2084 UserRelationship.create_block(user, blocked)
2087 @spec add_to_block(User.t(), User.t()) ::
2088 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2089 defp remove_from_block(%User{} = user, %User{} = blocked) do
2090 UserRelationship.delete_block(user, blocked)
2093 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2094 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2095 {:ok, user_notification_mute} <-
2096 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2098 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2102 defp remove_from_mutes(user, %User{} = muted_user) do
2103 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2104 {:ok, user_notification_mute} <-
2105 UserRelationship.delete_notification_mute(user, muted_user) do
2106 {:ok, [user_mute, user_notification_mute]}
2110 def set_invisible(user, invisible) do
2111 params = %{invisible: invisible}
2114 |> cast(params, [:invisible])
2115 |> validate_required([:invisible])
2116 |> update_and_set_cache()
2119 def sanitize_html(%User{} = user) do
2120 sanitize_html(user, nil)
2123 # User data that mastodon isn't filtering (treated as plaintext):
2126 def sanitize_html(%User{} = user, filter) do
2130 |> Enum.map(fn %{"name" => name, "value" => value} ->
2133 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2138 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2139 |> Map.put(:fields, fields)