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: user_id}), do: true
242 def visible_for?(%User{local: local} = user, nil) do
248 if Config.get([:restrict_unauthenticated, :profiles, cfg_key]),
250 else: account_status(user) == :active
253 def visible_for?(%User{} = user, for_user) do
254 account_status(user) == :active || superuser?(for_user)
257 def visible_for?(_, _), do: false
259 @spec superuser?(User.t()) :: boolean()
260 def superuser?(%User{local: true, is_admin: true}), do: true
261 def superuser?(%User{local: true, is_moderator: true}), do: true
262 def superuser?(_), do: false
264 @spec invisible?(User.t()) :: boolean()
265 def invisible?(%User{invisible: true}), do: true
266 def invisible?(_), do: false
268 def avatar_url(user, options \\ []) do
270 %{"url" => [%{"href" => href} | _]} -> href
271 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
275 def banner_url(user, options \\ []) do
277 %{"url" => [%{"href" => href} | _]} -> href
278 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
282 def profile_url(%User{source_data: %{"url" => url}}), do: url
283 def profile_url(%User{ap_id: ap_id}), do: ap_id
284 def profile_url(_), do: nil
286 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
288 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
289 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
291 @spec ap_following(User.t()) :: Sring.t()
292 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
293 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
295 def follow_state(%User{} = user, %User{} = target) do
296 case Utils.fetch_latest_follow(user, target) do
297 %{data: %{"state" => state}} -> state
298 # Ideally this would be nil, but then Cachex does not commit the value
303 def get_cached_follow_state(user, target) do
304 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
305 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
308 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
309 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
310 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
313 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
314 def restrict_deactivated(query) do
315 from(u in query, where: u.deactivated != ^true)
318 defdelegate following_count(user), to: FollowingRelationship
320 defp truncate_fields_param(params) do
321 if Map.has_key?(params, :fields) do
322 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
328 defp truncate_if_exists(params, key, max_length) do
329 if Map.has_key?(params, key) and is_binary(params[key]) do
330 {value, _chopped} = String.split_at(params[key], max_length)
331 Map.put(params, key, value)
337 def remote_user_creation(params) do
338 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
339 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
343 |> truncate_if_exists(:name, name_limit)
344 |> truncate_if_exists(:bio, bio_limit)
345 |> truncate_fields_param()
365 :hide_followers_count,
376 |> validate_required([:name, :ap_id])
377 |> unique_constraint(:nickname)
378 |> validate_format(:nickname, @email_regex)
379 |> validate_length(:bio, max: bio_limit)
380 |> validate_length(:name, max: name_limit)
381 |> validate_fields(true)
383 case params[:source_data] do
384 %{"followers" => followers, "following" => following} ->
386 |> put_change(:follower_address, followers)
387 |> put_change(:following_address, following)
390 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
391 put_change(changeset, :follower_address, followers)
395 def update_changeset(struct, params \\ %{}) do
396 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
397 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
412 :hide_followers_count,
415 :allow_following_move,
418 :skip_thread_containment,
421 :pleroma_settings_store,
427 |> unique_constraint(:nickname)
428 |> validate_format(:nickname, local_nickname_regex())
429 |> validate_length(:bio, max: bio_limit)
430 |> validate_length(:name, min: 1, max: name_limit)
431 |> validate_fields(false)
434 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
435 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
436 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
438 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
440 params = if remote?, do: truncate_fields_param(params), else: params
462 :allow_following_move,
464 :hide_followers_count,
470 |> unique_constraint(:nickname)
471 |> validate_format(:nickname, local_nickname_regex())
472 |> validate_length(:bio, max: bio_limit)
473 |> validate_length(:name, max: name_limit)
474 |> validate_fields(remote?)
477 def password_update_changeset(struct, params) do
479 |> cast(params, [:password, :password_confirmation])
480 |> validate_required([:password, :password_confirmation])
481 |> validate_confirmation(:password)
482 |> put_password_hash()
483 |> put_change(:password_reset_pending, false)
486 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
487 def reset_password(%User{id: user_id} = user, data) do
490 |> Multi.update(:user, password_update_changeset(user, data))
491 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
492 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
494 case Repo.transaction(multi) do
495 {:ok, %{user: user} = _} -> set_cache(user)
496 {:error, _, changeset, _} -> {:error, changeset}
500 def update_password_reset_pending(user, value) do
503 |> put_change(:password_reset_pending, value)
504 |> update_and_set_cache()
507 def force_password_reset_async(user) do
508 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
511 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
512 def force_password_reset(user), do: update_password_reset_pending(user, true)
514 def register_changeset(struct, params \\ %{}, opts \\ []) do
515 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
516 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
519 if is_nil(opts[:need_confirmation]) do
520 Pleroma.Config.get([:instance, :account_activation_required])
522 opts[:need_confirmation]
526 |> confirmation_changeset(need_confirmation: need_confirmation?)
527 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
528 |> validate_required([:name, :nickname, :password, :password_confirmation])
529 |> validate_confirmation(:password)
530 |> unique_constraint(:email)
531 |> unique_constraint(:nickname)
532 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
533 |> validate_format(:nickname, local_nickname_regex())
534 |> validate_format(:email, @email_regex)
535 |> validate_length(:bio, max: bio_limit)
536 |> validate_length(:name, min: 1, max: name_limit)
537 |> maybe_validate_required_email(opts[:external])
540 |> unique_constraint(:ap_id)
541 |> put_following_and_follower_address()
544 def maybe_validate_required_email(changeset, true), do: changeset
546 def maybe_validate_required_email(changeset, _) do
547 if Pleroma.Config.get([:instance, :account_activation_required]) do
548 validate_required(changeset, [:email])
554 defp put_ap_id(changeset) do
555 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
556 put_change(changeset, :ap_id, ap_id)
559 defp put_following_and_follower_address(changeset) do
560 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
563 |> put_change(:follower_address, followers)
566 defp autofollow_users(user) do
567 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
570 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
573 follow_all(user, autofollowed_users)
576 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
577 def register(%Ecto.Changeset{} = changeset) do
578 with {:ok, user} <- Repo.insert(changeset) do
579 post_register_action(user)
583 def post_register_action(%User{} = user) do
584 with {:ok, user} <- autofollow_users(user),
585 {:ok, user} <- set_cache(user),
586 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
587 {:ok, _} <- try_send_confirmation_email(user) do
592 def try_send_confirmation_email(%User{} = user) do
593 if user.confirmation_pending &&
594 Pleroma.Config.get([:instance, :account_activation_required]) do
596 |> Pleroma.Emails.UserEmail.account_confirmation_email()
597 |> Pleroma.Emails.Mailer.deliver_async()
605 def try_send_confirmation_email(users) do
606 Enum.each(users, &try_send_confirmation_email/1)
609 def needs_update?(%User{local: true}), do: false
611 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
613 def needs_update?(%User{local: false} = user) do
614 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
617 def needs_update?(_), do: true
619 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
620 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
621 follow(follower, followed, "pending")
624 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
625 follow(follower, followed)
628 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
629 if not ap_enabled?(followed) do
630 follow(follower, followed)
636 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
637 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
638 def follow_all(follower, followeds) do
640 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
641 |> Enum.each(&follow(follower, &1, "accept"))
646 defdelegate following(user), to: FollowingRelationship
648 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
649 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
652 followed.deactivated ->
653 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
655 deny_follow_blocked and blocks?(followed, follower) ->
656 {:error, "Could not follow user: #{followed.nickname} blocked you."}
659 FollowingRelationship.follow(follower, followed, state)
661 {:ok, _} = update_follower_count(followed)
664 |> update_following_count()
669 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
670 {:error, "Not subscribed!"}
673 def unfollow(%User{} = follower, %User{} = followed) do
674 case get_follow_state(follower, followed) do
675 state when state in ["accept", "pending"] ->
676 FollowingRelationship.unfollow(follower, followed)
677 {:ok, followed} = update_follower_count(followed)
681 |> update_following_count()
684 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
687 {:error, "Not subscribed!"}
691 defdelegate following?(follower, followed), to: FollowingRelationship
693 def get_follow_state(%User{} = follower, %User{} = following) do
694 following_relationship = FollowingRelationship.get(follower, following)
696 case {following_relationship, following.local} do
698 case Utils.fetch_latest_follow(follower, following) do
699 %{data: %{"state" => state}} when state in ["pending", "accept"] -> state
703 {%{state: state}, _} ->
711 def locked?(%User{} = user) do
716 Repo.get_by(User, id: id)
719 def get_by_ap_id(ap_id) do
720 Repo.get_by(User, ap_id: ap_id)
723 def get_all_by_ap_id(ap_ids) do
724 from(u in __MODULE__,
725 where: u.ap_id in ^ap_ids
730 def get_all_by_ids(ids) do
731 from(u in __MODULE__, where: u.id in ^ids)
735 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
736 # of the ap_id and the domain and tries to get that user
737 def get_by_guessed_nickname(ap_id) do
738 domain = URI.parse(ap_id).host
739 name = List.last(String.split(ap_id, "/"))
740 nickname = "#{name}@#{domain}"
742 get_cached_by_nickname(nickname)
745 def set_cache({:ok, user}), do: set_cache(user)
746 def set_cache({:error, err}), do: {:error, err}
748 def set_cache(%User{} = user) do
749 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
750 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
754 def update_and_set_cache(struct, params) do
756 |> update_changeset(params)
757 |> update_and_set_cache()
760 def update_and_set_cache(changeset) do
761 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
766 def invalidate_cache(user) do
767 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
768 Cachex.del(:user_cache, "nickname:#{user.nickname}")
771 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
772 def get_cached_by_ap_id(ap_id) do
773 key = "ap_id:#{ap_id}"
775 with {:ok, nil} <- Cachex.get(:user_cache, key),
776 user when not is_nil(user) <- get_by_ap_id(ap_id),
777 {:ok, true} <- Cachex.put(:user_cache, key, user) do
785 def get_cached_by_id(id) do
789 Cachex.fetch!(:user_cache, key, fn _ ->
793 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
794 {:commit, user.ap_id}
800 get_cached_by_ap_id(ap_id)
803 def get_cached_by_nickname(nickname) do
804 key = "nickname:#{nickname}"
806 Cachex.fetch!(:user_cache, key, fn ->
807 case get_or_fetch_by_nickname(nickname) do
808 {:ok, user} -> {:commit, user}
809 {:error, _error} -> {:ignore, nil}
814 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
815 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
818 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
819 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
821 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
822 get_cached_by_nickname(nickname_or_id)
824 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
825 get_cached_by_nickname(nickname_or_id)
832 def get_by_nickname(nickname) do
833 Repo.get_by(User, nickname: nickname) ||
834 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
835 Repo.get_by(User, nickname: local_nickname(nickname))
839 def get_by_email(email), do: Repo.get_by(User, email: email)
841 def get_by_nickname_or_email(nickname_or_email) do
842 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
845 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
847 def get_or_fetch_by_nickname(nickname) do
848 with %User{} = user <- get_by_nickname(nickname) do
852 with [_nick, _domain] <- String.split(nickname, "@"),
853 {:ok, user} <- fetch_by_nickname(nickname) do
856 _e -> {:error, "not found " <> nickname}
861 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
862 def get_followers_query(%User{} = user, nil) do
863 User.Query.build(%{followers: user, deactivated: false})
866 def get_followers_query(user, page) do
868 |> get_followers_query(nil)
869 |> User.Query.paginate(page, 20)
872 @spec get_followers_query(User.t()) :: Ecto.Query.t()
873 def get_followers_query(user), do: get_followers_query(user, nil)
875 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
876 def get_followers(user, page \\ nil) do
878 |> get_followers_query(page)
882 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
883 def get_external_followers(user, page \\ nil) do
885 |> get_followers_query(page)
886 |> User.Query.build(%{external: true})
890 def get_followers_ids(user, page \\ nil) do
892 |> get_followers_query(page)
897 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
898 def get_friends_query(%User{} = user, nil) do
899 User.Query.build(%{friends: user, deactivated: false})
902 def get_friends_query(user, page) do
904 |> get_friends_query(nil)
905 |> User.Query.paginate(page, 20)
908 @spec get_friends_query(User.t()) :: Ecto.Query.t()
909 def get_friends_query(user), do: get_friends_query(user, nil)
911 def get_friends(user, page \\ nil) do
913 |> get_friends_query(page)
917 def get_friends_ap_ids(user) do
919 |> get_friends_query(nil)
920 |> select([u], u.ap_id)
924 def get_friends_ids(user, page \\ nil) do
926 |> get_friends_query(page)
931 defdelegate get_follow_requests(user), to: FollowingRelationship
933 def increase_note_count(%User{} = user) do
935 |> where(id: ^user.id)
936 |> update([u], inc: [note_count: 1])
938 |> Repo.update_all([])
940 {1, [user]} -> set_cache(user)
945 def decrease_note_count(%User{} = user) do
947 |> where(id: ^user.id)
950 note_count: fragment("greatest(0, note_count - 1)")
954 |> Repo.update_all([])
956 {1, [user]} -> set_cache(user)
961 def update_note_count(%User{} = user, note_count \\ nil) do
966 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
972 |> cast(%{note_count: note_count}, [:note_count])
973 |> update_and_set_cache()
976 @spec maybe_fetch_follow_information(User.t()) :: User.t()
977 def maybe_fetch_follow_information(user) do
978 with {:ok, user} <- fetch_follow_information(user) do
982 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
988 def fetch_follow_information(user) do
989 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
991 |> follow_information_changeset(info)
992 |> update_and_set_cache()
996 defp follow_information_changeset(user, params) do
1003 :hide_followers_count,
1008 def update_follower_count(%User{} = user) do
1009 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
1010 follower_count_query =
1011 User.Query.build(%{followers: user, deactivated: false})
1012 |> select([u], %{count: count(u.id)})
1015 |> where(id: ^user.id)
1016 |> join(:inner, [u], s in subquery(follower_count_query))
1018 set: [follower_count: s.count]
1021 |> Repo.update_all([])
1023 {1, [user]} -> set_cache(user)
1027 {:ok, maybe_fetch_follow_information(user)}
1031 @spec update_following_count(User.t()) :: User.t()
1032 def update_following_count(%User{local: false} = user) do
1033 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1034 maybe_fetch_follow_information(user)
1040 def update_following_count(%User{local: true} = user) do
1041 following_count = FollowingRelationship.following_count(user)
1044 |> follow_information_changeset(%{following_count: following_count})
1048 def set_unread_conversation_count(%User{local: true} = user) do
1049 unread_query = Participation.unread_conversation_count_for_user(user)
1052 |> join(:inner, [u], p in subquery(unread_query))
1054 set: [unread_conversation_count: p.count]
1056 |> where([u], u.id == ^user.id)
1058 |> Repo.update_all([])
1060 {1, [user]} -> set_cache(user)
1065 def set_unread_conversation_count(user), do: {:ok, user}
1067 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1069 Participation.unread_conversation_count_for_user(user)
1070 |> where([p], p.conversation_id == ^conversation.id)
1073 |> join(:inner, [u], p in subquery(unread_query))
1075 inc: [unread_conversation_count: 1]
1077 |> where([u], u.id == ^user.id)
1078 |> where([u, p], p.count == 0)
1080 |> Repo.update_all([])
1082 {1, [user]} -> set_cache(user)
1087 def increment_unread_conversation_count(_, user), do: {:ok, user}
1089 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1090 def get_users_from_set(ap_ids, local_only \\ true) do
1091 criteria = %{ap_id: ap_ids, deactivated: false}
1092 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1094 User.Query.build(criteria)
1098 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1099 def get_recipients_from_activity(%Activity{recipients: to}) do
1100 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1104 @spec mute(User.t(), User.t(), boolean()) ::
1105 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1106 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1107 add_to_mutes(muter, mutee, notifications?)
1110 def unmute(%User{} = muter, %User{} = mutee) do
1111 remove_from_mutes(muter, mutee)
1114 def subscribe(%User{} = subscriber, %User{} = target) do
1115 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1117 if blocks?(target, subscriber) and deny_follow_blocked do
1118 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1120 # Note: the relationship is inverse: subscriber acts as relationship target
1121 UserRelationship.create_inverse_subscription(target, subscriber)
1125 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1126 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1127 subscribe(subscriber, subscribee)
1131 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1132 # Note: the relationship is inverse: subscriber acts as relationship target
1133 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1136 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1137 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1138 unsubscribe(unsubscriber, user)
1142 def block(%User{} = blocker, %User{} = blocked) do
1143 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1145 if following?(blocker, blocked) do
1146 {:ok, blocker, _} = unfollow(blocker, blocked)
1152 # clear any requested follows as well
1154 case CommonAPI.reject_follow_request(blocked, blocker) do
1155 {:ok, %User{} = updated_blocked} -> updated_blocked
1159 unsubscribe(blocked, blocker)
1161 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1163 {:ok, blocker} = update_follower_count(blocker)
1164 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1165 add_to_block(blocker, blocked)
1168 # helper to handle the block given only an actor's AP id
1169 def block(%User{} = blocker, %{ap_id: ap_id}) do
1170 block(blocker, get_cached_by_ap_id(ap_id))
1173 def unblock(%User{} = blocker, %User{} = blocked) do
1174 remove_from_block(blocker, blocked)
1177 # helper to handle the block given only an actor's AP id
1178 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1179 unblock(blocker, get_cached_by_ap_id(ap_id))
1182 def mutes?(nil, _), do: false
1183 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1185 def mutes_user?(%User{} = user, %User{} = target) do
1186 UserRelationship.mute_exists?(user, target)
1189 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1190 def muted_notifications?(nil, _), do: false
1192 def muted_notifications?(%User{} = user, %User{} = target),
1193 do: UserRelationship.notification_mute_exists?(user, target)
1195 def blocks?(nil, _), do: false
1197 def blocks?(%User{} = user, %User{} = target) do
1198 blocks_user?(user, target) ||
1199 (!User.following?(user, target) && blocks_domain?(user, target))
1202 def blocks_user?(%User{} = user, %User{} = target) do
1203 UserRelationship.block_exists?(user, target)
1206 def blocks_user?(_, _), do: false
1208 def blocks_domain?(%User{} = user, %User{} = target) do
1209 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1210 %{host: host} = URI.parse(target.ap_id)
1211 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1214 def blocks_domain?(_, _), do: false
1216 def subscribed_to?(%User{} = user, %User{} = target) do
1217 # Note: the relationship is inverse: subscriber acts as relationship target
1218 UserRelationship.inverse_subscription_exists?(target, user)
1221 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1222 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1223 subscribed_to?(user, target)
1228 Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
1229 E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1231 @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1232 def outgoing_relations_ap_ids(_, []), do: %{}
1234 def outgoing_relations_ap_ids(%User{} = user, relationship_types)
1235 when is_list(relationship_types) do
1238 |> assoc(:outgoing_relationships)
1239 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1240 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1241 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1242 |> group_by([user_rel, u], user_rel.relationship_type)
1244 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1249 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1253 def deactivate_async(user, status \\ true) do
1254 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1257 def deactivate(user, status \\ true)
1259 def deactivate(users, status) when is_list(users) do
1260 Repo.transaction(fn ->
1261 for user <- users, do: deactivate(user, status)
1265 def deactivate(%User{} = user, status) do
1266 with {:ok, user} <- set_activation_status(user, status) do
1269 |> Enum.filter(& &1.local)
1270 |> Enum.each(fn follower ->
1271 follower |> update_following_count() |> set_cache()
1274 # Only update local user counts, remote will be update during the next pull.
1277 |> Enum.filter(& &1.local)
1278 |> Enum.each(&update_follower_count/1)
1284 def update_notification_settings(%User{} = user, settings) do
1286 |> cast(%{notification_settings: settings}, [])
1287 |> cast_embed(:notification_settings)
1288 |> validate_required([:notification_settings])
1289 |> update_and_set_cache()
1292 def delete(users) when is_list(users) do
1293 for user <- users, do: delete(user)
1296 def delete(%User{} = user) do
1297 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1300 def perform(:force_password_reset, user), do: force_password_reset(user)
1302 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1303 def perform(:delete, %User{} = user) do
1304 {:ok, _user} = ActivityPub.delete(user)
1306 # Remove all relationships
1309 |> Enum.each(fn follower ->
1310 ActivityPub.unfollow(follower, user)
1311 unfollow(follower, user)
1316 |> Enum.each(fn followed ->
1317 ActivityPub.unfollow(user, followed)
1318 unfollow(user, followed)
1321 delete_user_activities(user)
1322 invalidate_cache(user)
1326 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1328 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1329 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1330 when is_list(blocked_identifiers) do
1332 blocked_identifiers,
1333 fn blocked_identifier ->
1334 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1335 {:ok, _user_block} <- block(blocker, blocked),
1336 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1340 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1347 def perform(:follow_import, %User{} = follower, followed_identifiers)
1348 when is_list(followed_identifiers) do
1350 followed_identifiers,
1351 fn followed_identifier ->
1352 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1353 {:ok, follower} <- maybe_direct_follow(follower, followed),
1354 {:ok, _} <- ActivityPub.follow(follower, followed) do
1358 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1365 @spec external_users_query() :: Ecto.Query.t()
1366 def external_users_query do
1374 @spec external_users(keyword()) :: [User.t()]
1375 def external_users(opts \\ []) do
1377 external_users_query()
1378 |> select([u], struct(u, [:id, :ap_id]))
1382 do: where(query, [u], u.id > ^opts[:max_id]),
1387 do: limit(query, ^opts[:limit]),
1393 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1394 BackgroundWorker.enqueue("blocks_import", %{
1395 "blocker_id" => blocker.id,
1396 "blocked_identifiers" => blocked_identifiers
1400 def follow_import(%User{} = follower, followed_identifiers)
1401 when is_list(followed_identifiers) do
1402 BackgroundWorker.enqueue("follow_import", %{
1403 "follower_id" => follower.id,
1404 "followed_identifiers" => followed_identifiers
1408 def delete_user_activities(%User{ap_id: ap_id}) do
1410 |> Activity.Queries.by_actor()
1411 |> RepoStreamer.chunk_stream(50)
1412 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1416 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1418 |> Object.normalize()
1419 |> ActivityPub.delete()
1422 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1423 object = Object.normalize(activity)
1426 |> get_cached_by_ap_id()
1427 |> ActivityPub.unlike(object)
1430 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1431 object = Object.normalize(activity)
1434 |> get_cached_by_ap_id()
1435 |> ActivityPub.unannounce(object)
1438 defp delete_activity(_activity), do: "Doing nothing"
1440 def html_filter_policy(%User{no_rich_text: true}) do
1441 Pleroma.HTML.Scrubber.TwitterText
1444 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1446 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1448 def get_or_fetch_by_ap_id(ap_id) do
1449 user = get_cached_by_ap_id(ap_id)
1451 if !is_nil(user) and !needs_update?(user) do
1454 fetch_by_ap_id(ap_id)
1459 Creates an internal service actor by URI if missing.
1460 Optionally takes nickname for addressing.
1462 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1463 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1465 case get_cached_by_ap_id(uri) do
1467 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1468 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1472 %User{invisible: false} = user ->
1482 @spec set_invisible(User.t()) :: {:ok, User.t()}
1483 defp set_invisible(user) do
1485 |> change(%{invisible: true})
1486 |> update_and_set_cache()
1489 @spec create_service_actor(String.t(), String.t()) ::
1490 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1491 defp create_service_actor(uri, nickname) do
1497 follower_address: uri <> "/followers"
1500 |> unique_constraint(:nickname)
1506 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1509 |> :public_key.pem_decode()
1511 |> :public_key.pem_entry_decode()
1516 def public_key(_), do: {:error, "not found key"}
1518 def get_public_key_for_ap_id(ap_id) do
1519 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1520 {:ok, public_key} <- public_key(user) do
1527 defp blank?(""), do: nil
1528 defp blank?(n), do: n
1530 def insert_or_update_user(data) do
1532 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1533 |> remote_user_creation()
1534 |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
1538 def ap_enabled?(%User{local: true}), do: true
1539 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1540 def ap_enabled?(_), do: false
1542 @doc "Gets or fetch a user by uri or nickname."
1543 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1544 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1545 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1547 # wait a period of time and return newest version of the User structs
1548 # this is because we have synchronous follow APIs and need to simulate them
1549 # with an async handshake
1550 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1551 with %User{} = a <- get_cached_by_id(a.id),
1552 %User{} = b <- get_cached_by_id(b.id) do
1559 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1560 with :ok <- :timer.sleep(timeout),
1561 %User{} = a <- get_cached_by_id(a.id),
1562 %User{} = b <- get_cached_by_id(b.id) do
1569 def parse_bio(bio) when is_binary(bio) and bio != "" do
1571 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1575 def parse_bio(_), do: ""
1577 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1578 # TODO: get profile URLs other than user.ap_id
1579 profile_urls = [user.ap_id]
1582 |> CommonUtils.format_input("text/plain",
1583 mentions_format: :full,
1584 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1589 def parse_bio(_, _), do: ""
1591 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1592 Repo.transaction(fn ->
1593 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1597 def tag(nickname, tags) when is_binary(nickname),
1598 do: tag(get_by_nickname(nickname), tags)
1600 def tag(%User{} = user, tags),
1601 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1603 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1604 Repo.transaction(fn ->
1605 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1609 def untag(nickname, tags) when is_binary(nickname),
1610 do: untag(get_by_nickname(nickname), tags)
1612 def untag(%User{} = user, tags),
1613 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1615 defp update_tags(%User{} = user, new_tags) do
1616 {:ok, updated_user} =
1618 |> change(%{tags: new_tags})
1619 |> update_and_set_cache()
1624 defp normalize_tags(tags) do
1627 |> Enum.map(&String.downcase/1)
1630 defp local_nickname_regex do
1631 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1632 @extended_local_nickname_regex
1634 @strict_local_nickname_regex
1638 def local_nickname(nickname_or_mention) do
1641 |> String.split("@")
1645 def full_nickname(nickname_or_mention),
1646 do: String.trim_leading(nickname_or_mention, "@")
1648 def error_user(ap_id) do
1652 nickname: "erroruser@example.com",
1653 inserted_at: NaiveDateTime.utc_now()
1657 @spec all_superusers() :: [User.t()]
1658 def all_superusers do
1659 User.Query.build(%{super_users: true, local: true, deactivated: false})
1663 def showing_reblogs?(%User{} = user, %User{} = target) do
1664 not UserRelationship.reblog_mute_exists?(user, target)
1668 The function returns a query to get users with no activity for given interval of days.
1669 Inactive users are those who didn't read any notification, or had any activity where
1670 the user is the activity's actor, during `inactivity_threshold` days.
1671 Deactivated users will not appear in this list.
1675 iex> Pleroma.User.list_inactive_users()
1678 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1679 def list_inactive_users_query(inactivity_threshold \\ 7) do
1680 negative_inactivity_threshold = -inactivity_threshold
1681 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1682 # Subqueries are not supported in `where` clauses, join gets too complicated.
1683 has_read_notifications =
1684 from(n in Pleroma.Notification,
1685 where: n.seen == true,
1687 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1690 |> Pleroma.Repo.all()
1692 from(u in Pleroma.User,
1693 left_join: a in Pleroma.Activity,
1694 on: u.ap_id == a.actor,
1695 where: not is_nil(u.nickname),
1696 where: u.deactivated != ^true,
1697 where: u.id not in ^has_read_notifications,
1700 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1701 is_nil(max(a.inserted_at))
1706 Enable or disable email notifications for user
1710 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1711 Pleroma.User{email_notifications: %{"digest" => true}}
1713 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1714 Pleroma.User{email_notifications: %{"digest" => false}}
1716 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1717 {:ok, t()} | {:error, Ecto.Changeset.t()}
1718 def switch_email_notifications(user, type, status) do
1719 User.update_email_notifications(user, %{type => status})
1723 Set `last_digest_emailed_at` value for the user to current time
1725 @spec touch_last_digest_emailed_at(t()) :: t()
1726 def touch_last_digest_emailed_at(user) do
1727 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1729 {:ok, updated_user} =
1731 |> change(%{last_digest_emailed_at: now})
1732 |> update_and_set_cache()
1737 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1738 def toggle_confirmation(%User{} = user) do
1740 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1741 |> update_and_set_cache()
1744 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1745 def toggle_confirmation(users) do
1746 Enum.map(users, &toggle_confirmation/1)
1749 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1753 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1754 # use instance-default
1755 config = Pleroma.Config.get([:assets, :mascots])
1756 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1757 mascot = Keyword.get(config, default_mascot)
1760 "id" => "default-mascot",
1761 "url" => mascot[:url],
1762 "preview_url" => mascot[:url],
1764 "mime_type" => mascot[:mime_type]
1769 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1771 def ensure_keys_present(%User{} = user) do
1772 with {:ok, pem} <- Keys.generate_rsa_pem() do
1774 |> cast(%{keys: pem}, [:keys])
1775 |> validate_required([:keys])
1776 |> update_and_set_cache()
1780 def get_ap_ids_by_nicknames(nicknames) do
1782 where: u.nickname in ^nicknames,
1788 defdelegate search(query, opts \\ []), to: User.Search
1790 defp put_password_hash(
1791 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1793 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1796 defp put_password_hash(changeset), do: changeset
1798 def is_internal_user?(%User{nickname: nil}), do: true
1799 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1800 def is_internal_user?(_), do: false
1802 # A hack because user delete activities have a fake id for whatever reason
1803 # TODO: Get rid of this
1804 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1806 def get_delivered_users_by_object_id(object_id) do
1808 inner_join: delivery in assoc(u, :deliveries),
1809 where: delivery.object_id == ^object_id
1814 def change_email(user, email) do
1816 |> cast(%{email: email}, [:email])
1817 |> validate_required([:email])
1818 |> unique_constraint(:email)
1819 |> validate_format(:email, @email_regex)
1820 |> update_and_set_cache()
1823 # Internal function; public one is `deactivate/2`
1824 defp set_activation_status(user, deactivated) do
1826 |> cast(%{deactivated: deactivated}, [:deactivated])
1827 |> update_and_set_cache()
1830 def update_banner(user, banner) do
1832 |> cast(%{banner: banner}, [:banner])
1833 |> update_and_set_cache()
1836 def update_background(user, background) do
1838 |> cast(%{background: background}, [:background])
1839 |> update_and_set_cache()
1842 def update_source_data(user, source_data) do
1844 |> cast(%{source_data: source_data}, [:source_data])
1845 |> update_and_set_cache()
1848 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1851 moderator: is_moderator
1855 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1856 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1857 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1858 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1861 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1862 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1866 def fields(%{fields: nil}), do: []
1868 def fields(%{fields: fields}), do: fields
1870 def validate_fields(changeset, remote? \\ false) do
1871 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1872 limit = Pleroma.Config.get([:instance, limit_name], 0)
1875 |> validate_length(:fields, max: limit)
1876 |> validate_change(:fields, fn :fields, fields ->
1877 if Enum.all?(fields, &valid_field?/1) do
1885 defp valid_field?(%{"name" => name, "value" => value}) do
1886 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1887 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1889 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1890 String.length(value) <= value_limit
1893 defp valid_field?(_), do: false
1895 defp truncate_field(%{"name" => name, "value" => value}) do
1897 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1900 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1902 %{"name" => name, "value" => value}
1905 def admin_api_update(user, params) do
1912 |> update_and_set_cache()
1915 @doc "Signs user out of all applications"
1916 def global_sign_out(user) do
1917 OAuth.Authorization.delete_user_authorizations(user)
1918 OAuth.Token.delete_user_tokens(user)
1921 def mascot_update(user, url) do
1923 |> cast(%{mascot: url}, [:mascot])
1924 |> validate_required([:mascot])
1925 |> update_and_set_cache()
1928 def mastodon_settings_update(user, settings) do
1930 |> cast(%{settings: settings}, [:settings])
1931 |> validate_required([:settings])
1932 |> update_and_set_cache()
1935 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1936 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1938 if need_confirmation? do
1940 confirmation_pending: true,
1941 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1945 confirmation_pending: false,
1946 confirmation_token: nil
1950 cast(user, params, [:confirmation_pending, :confirmation_token])
1953 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1954 if id not in user.pinned_activities do
1955 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1956 params = %{pinned_activities: user.pinned_activities ++ [id]}
1959 |> cast(params, [:pinned_activities])
1960 |> validate_length(:pinned_activities,
1961 max: max_pinned_statuses,
1962 message: "You have already pinned the maximum number of statuses"
1967 |> update_and_set_cache()
1970 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1971 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1974 |> cast(params, [:pinned_activities])
1975 |> update_and_set_cache()
1978 def update_email_notifications(user, settings) do
1979 email_notifications =
1980 user.email_notifications
1981 |> Map.merge(settings)
1982 |> Map.take(["digest"])
1984 params = %{email_notifications: email_notifications}
1985 fields = [:email_notifications]
1988 |> cast(params, fields)
1989 |> validate_required(fields)
1990 |> update_and_set_cache()
1993 defp set_domain_blocks(user, domain_blocks) do
1994 params = %{domain_blocks: domain_blocks}
1997 |> cast(params, [:domain_blocks])
1998 |> validate_required([:domain_blocks])
1999 |> update_and_set_cache()
2002 def block_domain(user, domain_blocked) do
2003 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2006 def unblock_domain(user, domain_blocked) do
2007 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2010 @spec add_to_block(User.t(), User.t()) ::
2011 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2012 defp add_to_block(%User{} = user, %User{} = blocked) do
2013 UserRelationship.create_block(user, blocked)
2016 @spec add_to_block(User.t(), User.t()) ::
2017 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2018 defp remove_from_block(%User{} = user, %User{} = blocked) do
2019 UserRelationship.delete_block(user, blocked)
2022 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2023 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2024 {:ok, user_notification_mute} <-
2025 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2027 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2031 defp remove_from_mutes(user, %User{} = muted_user) do
2032 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2033 {:ok, user_notification_mute} <-
2034 UserRelationship.delete_notification_mute(user, muted_user) do
2035 {:ok, [user_mute, user_notification_mute]}
2039 def set_invisible(user, invisible) do
2040 params = %{invisible: invisible}
2043 |> cast(params, [:invisible])
2044 |> validate_required([:invisible])
2045 |> update_and_set_cache()
2048 def sanitize_html(%User{} = user) do
2049 sanitize_html(user, nil)
2052 # User data that mastodon isn't filtering (treated as plaintext):
2055 def sanitize_html(%User{} = user, filter) do
2059 |> Enum.map(fn %{"name" => name, "value" => value} ->
2062 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2067 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2068 |> Map.put(:fields, fields)