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 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
296 def restrict_deactivated(query) do
297 from(u in query, where: u.deactivated != ^true)
300 defdelegate following_count(user), to: FollowingRelationship
302 defp truncate_fields_param(params) do
303 if Map.has_key?(params, :fields) do
304 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
310 defp truncate_if_exists(params, key, max_length) do
311 if Map.has_key?(params, key) and is_binary(params[key]) do
312 {value, _chopped} = String.split_at(params[key], max_length)
313 Map.put(params, key, value)
319 def remote_user_creation(params) do
320 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
321 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
325 |> truncate_if_exists(:name, name_limit)
326 |> truncate_if_exists(:bio, bio_limit)
327 |> truncate_fields_param()
347 :hide_followers_count,
358 |> validate_required([:name, :ap_id])
359 |> unique_constraint(:nickname)
360 |> validate_format(:nickname, @email_regex)
361 |> validate_length(:bio, max: bio_limit)
362 |> validate_length(:name, max: name_limit)
363 |> validate_fields(true)
365 case params[:source_data] do
366 %{"followers" => followers, "following" => following} ->
368 |> put_change(:follower_address, followers)
369 |> put_change(:following_address, following)
372 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
373 put_change(changeset, :follower_address, followers)
377 def update_changeset(struct, params \\ %{}) do
378 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
379 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
394 :hide_followers_count,
397 :allow_following_move,
400 :skip_thread_containment,
403 :pleroma_settings_store,
409 |> unique_constraint(:nickname)
410 |> validate_format(:nickname, local_nickname_regex())
411 |> validate_length(:bio, max: bio_limit)
412 |> validate_length(:name, min: 1, max: name_limit)
413 |> validate_fields(false)
416 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
417 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
418 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
420 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
422 params = if remote?, do: truncate_fields_param(params), else: params
444 :allow_following_move,
446 :hide_followers_count,
452 |> unique_constraint(:nickname)
453 |> validate_format(:nickname, local_nickname_regex())
454 |> validate_length(:bio, max: bio_limit)
455 |> validate_length(:name, max: name_limit)
456 |> validate_fields(remote?)
459 def password_update_changeset(struct, params) do
461 |> cast(params, [:password, :password_confirmation])
462 |> validate_required([:password, :password_confirmation])
463 |> validate_confirmation(:password)
464 |> put_password_hash()
465 |> put_change(:password_reset_pending, false)
468 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
469 def reset_password(%User{id: user_id} = user, data) do
472 |> Multi.update(:user, password_update_changeset(user, data))
473 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
474 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
476 case Repo.transaction(multi) do
477 {:ok, %{user: user} = _} -> set_cache(user)
478 {:error, _, changeset, _} -> {:error, changeset}
482 def update_password_reset_pending(user, value) do
485 |> put_change(:password_reset_pending, value)
486 |> update_and_set_cache()
489 def force_password_reset_async(user) do
490 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
493 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
494 def force_password_reset(user), do: update_password_reset_pending(user, true)
496 def register_changeset(struct, params \\ %{}, opts \\ []) do
497 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
498 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
501 if is_nil(opts[:need_confirmation]) do
502 Pleroma.Config.get([:instance, :account_activation_required])
504 opts[:need_confirmation]
508 |> confirmation_changeset(need_confirmation: need_confirmation?)
509 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
510 |> validate_required([:name, :nickname, :password, :password_confirmation])
511 |> validate_confirmation(:password)
512 |> unique_constraint(:email)
513 |> unique_constraint(:nickname)
514 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
515 |> validate_format(:nickname, local_nickname_regex())
516 |> validate_format(:email, @email_regex)
517 |> validate_length(:bio, max: bio_limit)
518 |> validate_length(:name, min: 1, max: name_limit)
519 |> maybe_validate_required_email(opts[:external])
522 |> unique_constraint(:ap_id)
523 |> put_following_and_follower_address()
526 def maybe_validate_required_email(changeset, true), do: changeset
528 def maybe_validate_required_email(changeset, _) do
529 if Pleroma.Config.get([:instance, :account_activation_required]) do
530 validate_required(changeset, [:email])
536 defp put_ap_id(changeset) do
537 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
538 put_change(changeset, :ap_id, ap_id)
541 defp put_following_and_follower_address(changeset) do
542 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
545 |> put_change(:follower_address, followers)
548 defp autofollow_users(user) do
549 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
552 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
555 follow_all(user, autofollowed_users)
558 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
559 def register(%Ecto.Changeset{} = changeset) do
560 with {:ok, user} <- Repo.insert(changeset) do
561 post_register_action(user)
565 def post_register_action(%User{} = user) do
566 with {:ok, user} <- autofollow_users(user),
567 {:ok, user} <- set_cache(user),
568 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
569 {:ok, _} <- try_send_confirmation_email(user) do
574 def try_send_confirmation_email(%User{} = user) do
575 if user.confirmation_pending &&
576 Pleroma.Config.get([:instance, :account_activation_required]) do
578 |> Pleroma.Emails.UserEmail.account_confirmation_email()
579 |> Pleroma.Emails.Mailer.deliver_async()
587 def try_send_confirmation_email(users) do
588 Enum.each(users, &try_send_confirmation_email/1)
591 def needs_update?(%User{local: true}), do: false
593 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
595 def needs_update?(%User{local: false} = user) do
596 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
599 def needs_update?(_), do: true
601 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
602 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
603 follow(follower, followed, "pending")
606 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
607 follow(follower, followed)
610 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
611 if not ap_enabled?(followed) do
612 follow(follower, followed)
618 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
619 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
620 def follow_all(follower, followeds) do
622 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
623 |> Enum.each(&follow(follower, &1, "accept"))
628 defdelegate following(user), to: FollowingRelationship
630 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
631 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
634 followed.deactivated ->
635 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
637 deny_follow_blocked and blocks?(followed, follower) ->
638 {:error, "Could not follow user: #{followed.nickname} blocked you."}
641 FollowingRelationship.follow(follower, followed, state)
643 {:ok, _} = update_follower_count(followed)
646 |> update_following_count()
651 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
652 {:error, "Not subscribed!"}
655 def unfollow(%User{} = follower, %User{} = followed) do
656 case get_follow_state(follower, followed) do
657 state when state in ["accept", "pending"] ->
658 FollowingRelationship.unfollow(follower, followed)
659 {:ok, followed} = update_follower_count(followed)
663 |> update_following_count()
666 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
669 {:error, "Not subscribed!"}
673 defdelegate following?(follower, followed), to: FollowingRelationship
675 def get_follow_state(%User{} = follower, %User{} = following) do
676 following_relationship = FollowingRelationship.get(follower, following)
678 case {following_relationship, following.local} do
680 case Utils.fetch_latest_follow(follower, following) do
681 %{data: %{"state" => state}} when state in ["pending", "accept"] -> state
685 {%{state: state}, _} ->
693 def locked?(%User{} = user) do
698 Repo.get_by(User, id: id)
701 def get_by_ap_id(ap_id) do
702 Repo.get_by(User, ap_id: ap_id)
705 def get_all_by_ap_id(ap_ids) do
706 from(u in __MODULE__,
707 where: u.ap_id in ^ap_ids
712 def get_all_by_ids(ids) do
713 from(u in __MODULE__, where: u.id in ^ids)
717 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
718 # of the ap_id and the domain and tries to get that user
719 def get_by_guessed_nickname(ap_id) do
720 domain = URI.parse(ap_id).host
721 name = List.last(String.split(ap_id, "/"))
722 nickname = "#{name}@#{domain}"
724 get_cached_by_nickname(nickname)
727 def set_cache({:ok, user}), do: set_cache(user)
728 def set_cache({:error, err}), do: {:error, err}
730 def set_cache(%User{} = user) do
731 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
732 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
736 def update_and_set_cache(struct, params) do
738 |> update_changeset(params)
739 |> update_and_set_cache()
742 def update_and_set_cache(changeset) do
743 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
748 def invalidate_cache(user) do
749 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
750 Cachex.del(:user_cache, "nickname:#{user.nickname}")
753 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
754 def get_cached_by_ap_id(ap_id) do
755 key = "ap_id:#{ap_id}"
757 with {:ok, nil} <- Cachex.get(:user_cache, key),
758 user when not is_nil(user) <- get_by_ap_id(ap_id),
759 {:ok, true} <- Cachex.put(:user_cache, key, user) do
767 def get_cached_by_id(id) do
771 Cachex.fetch!(:user_cache, key, fn _ ->
775 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
776 {:commit, user.ap_id}
782 get_cached_by_ap_id(ap_id)
785 def get_cached_by_nickname(nickname) do
786 key = "nickname:#{nickname}"
788 Cachex.fetch!(:user_cache, key, fn ->
789 case get_or_fetch_by_nickname(nickname) do
790 {:ok, user} -> {:commit, user}
791 {:error, _error} -> {:ignore, nil}
796 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
797 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
800 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
801 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
803 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
804 get_cached_by_nickname(nickname_or_id)
806 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
807 get_cached_by_nickname(nickname_or_id)
814 def get_by_nickname(nickname) do
815 Repo.get_by(User, nickname: nickname) ||
816 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
817 Repo.get_by(User, nickname: local_nickname(nickname))
821 def get_by_email(email), do: Repo.get_by(User, email: email)
823 def get_by_nickname_or_email(nickname_or_email) do
824 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
827 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
829 def get_or_fetch_by_nickname(nickname) do
830 with %User{} = user <- get_by_nickname(nickname) do
834 with [_nick, _domain] <- String.split(nickname, "@"),
835 {:ok, user} <- fetch_by_nickname(nickname) do
838 _e -> {:error, "not found " <> nickname}
843 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
844 def get_followers_query(%User{} = user, nil) do
845 User.Query.build(%{followers: user, deactivated: false})
848 def get_followers_query(user, page) do
850 |> get_followers_query(nil)
851 |> User.Query.paginate(page, 20)
854 @spec get_followers_query(User.t()) :: Ecto.Query.t()
855 def get_followers_query(user), do: get_followers_query(user, nil)
857 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
858 def get_followers(user, page \\ nil) do
860 |> get_followers_query(page)
864 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
865 def get_external_followers(user, page \\ nil) do
867 |> get_followers_query(page)
868 |> User.Query.build(%{external: true})
872 def get_followers_ids(user, page \\ nil) do
874 |> get_followers_query(page)
879 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
880 def get_friends_query(%User{} = user, nil) do
881 User.Query.build(%{friends: user, deactivated: false})
884 def get_friends_query(user, page) do
886 |> get_friends_query(nil)
887 |> User.Query.paginate(page, 20)
890 @spec get_friends_query(User.t()) :: Ecto.Query.t()
891 def get_friends_query(user), do: get_friends_query(user, nil)
893 def get_friends(user, page \\ nil) do
895 |> get_friends_query(page)
899 def get_friends_ap_ids(user) do
901 |> get_friends_query(nil)
902 |> select([u], u.ap_id)
906 def get_friends_ids(user, page \\ nil) do
908 |> get_friends_query(page)
913 defdelegate get_follow_requests(user), to: FollowingRelationship
915 def increase_note_count(%User{} = user) do
917 |> where(id: ^user.id)
918 |> update([u], inc: [note_count: 1])
920 |> Repo.update_all([])
922 {1, [user]} -> set_cache(user)
927 def decrease_note_count(%User{} = user) do
929 |> where(id: ^user.id)
932 note_count: fragment("greatest(0, note_count - 1)")
936 |> Repo.update_all([])
938 {1, [user]} -> set_cache(user)
943 def update_note_count(%User{} = user, note_count \\ nil) do
948 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
954 |> cast(%{note_count: note_count}, [:note_count])
955 |> update_and_set_cache()
958 @spec maybe_fetch_follow_information(User.t()) :: User.t()
959 def maybe_fetch_follow_information(user) do
960 with {:ok, user} <- fetch_follow_information(user) do
964 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
970 def fetch_follow_information(user) do
971 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
973 |> follow_information_changeset(info)
974 |> update_and_set_cache()
978 defp follow_information_changeset(user, params) do
985 :hide_followers_count,
990 def update_follower_count(%User{} = user) do
991 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
992 follower_count_query =
993 User.Query.build(%{followers: user, deactivated: false})
994 |> select([u], %{count: count(u.id)})
997 |> where(id: ^user.id)
998 |> join(:inner, [u], s in subquery(follower_count_query))
1000 set: [follower_count: s.count]
1003 |> Repo.update_all([])
1005 {1, [user]} -> set_cache(user)
1009 {:ok, maybe_fetch_follow_information(user)}
1013 @spec update_following_count(User.t()) :: User.t()
1014 def update_following_count(%User{local: false} = user) do
1015 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1016 maybe_fetch_follow_information(user)
1022 def update_following_count(%User{local: true} = user) do
1023 following_count = FollowingRelationship.following_count(user)
1026 |> follow_information_changeset(%{following_count: following_count})
1030 def set_unread_conversation_count(%User{local: true} = user) do
1031 unread_query = Participation.unread_conversation_count_for_user(user)
1034 |> join(:inner, [u], p in subquery(unread_query))
1036 set: [unread_conversation_count: p.count]
1038 |> where([u], u.id == ^user.id)
1040 |> Repo.update_all([])
1042 {1, [user]} -> set_cache(user)
1047 def set_unread_conversation_count(user), do: {:ok, user}
1049 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1051 Participation.unread_conversation_count_for_user(user)
1052 |> where([p], p.conversation_id == ^conversation.id)
1055 |> join(:inner, [u], p in subquery(unread_query))
1057 inc: [unread_conversation_count: 1]
1059 |> where([u], u.id == ^user.id)
1060 |> where([u, p], p.count == 0)
1062 |> Repo.update_all([])
1064 {1, [user]} -> set_cache(user)
1069 def increment_unread_conversation_count(_, user), do: {:ok, user}
1071 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1072 def get_users_from_set(ap_ids, local_only \\ true) do
1073 criteria = %{ap_id: ap_ids, deactivated: false}
1074 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1076 User.Query.build(criteria)
1080 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1081 def get_recipients_from_activity(%Activity{recipients: to}) do
1082 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1086 @spec mute(User.t(), User.t(), boolean()) ::
1087 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1088 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1089 add_to_mutes(muter, mutee, notifications?)
1092 def unmute(%User{} = muter, %User{} = mutee) do
1093 remove_from_mutes(muter, mutee)
1096 def subscribe(%User{} = subscriber, %User{} = target) do
1097 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1099 if blocks?(target, subscriber) and deny_follow_blocked do
1100 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1102 # Note: the relationship is inverse: subscriber acts as relationship target
1103 UserRelationship.create_inverse_subscription(target, subscriber)
1107 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1108 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1109 subscribe(subscriber, subscribee)
1113 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1114 # Note: the relationship is inverse: subscriber acts as relationship target
1115 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1118 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1119 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1120 unsubscribe(unsubscriber, user)
1124 def block(%User{} = blocker, %User{} = blocked) do
1125 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1127 if following?(blocker, blocked) do
1128 {:ok, blocker, _} = unfollow(blocker, blocked)
1134 # clear any requested follows as well
1136 case CommonAPI.reject_follow_request(blocked, blocker) do
1137 {:ok, %User{} = updated_blocked} -> updated_blocked
1141 unsubscribe(blocked, blocker)
1143 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1145 {:ok, blocker} = update_follower_count(blocker)
1146 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1147 add_to_block(blocker, blocked)
1150 # helper to handle the block given only an actor's AP id
1151 def block(%User{} = blocker, %{ap_id: ap_id}) do
1152 block(blocker, get_cached_by_ap_id(ap_id))
1155 def unblock(%User{} = blocker, %User{} = blocked) do
1156 remove_from_block(blocker, blocked)
1159 # helper to handle the block given only an actor's AP id
1160 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1161 unblock(blocker, get_cached_by_ap_id(ap_id))
1164 def mutes?(nil, _), do: false
1165 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1167 def mutes_user?(%User{} = user, %User{} = target) do
1168 UserRelationship.mute_exists?(user, target)
1171 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1172 def muted_notifications?(nil, _), do: false
1174 def muted_notifications?(%User{} = user, %User{} = target),
1175 do: UserRelationship.notification_mute_exists?(user, target)
1177 def blocks?(nil, _), do: false
1179 def blocks?(%User{} = user, %User{} = target) do
1180 blocks_user?(user, target) ||
1181 (!User.following?(user, target) && blocks_domain?(user, target))
1184 def blocks_user?(%User{} = user, %User{} = target) do
1185 UserRelationship.block_exists?(user, target)
1188 def blocks_user?(_, _), do: false
1190 def blocks_domain?(%User{} = user, %User{} = target) do
1191 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1192 %{host: host} = URI.parse(target.ap_id)
1193 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1196 def blocks_domain?(_, _), do: false
1198 def subscribed_to?(%User{} = user, %User{} = target) do
1199 # Note: the relationship is inverse: subscriber acts as relationship target
1200 UserRelationship.inverse_subscription_exists?(target, user)
1203 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1204 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1205 subscribed_to?(user, target)
1210 Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
1211 E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1213 @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1214 def outgoing_relations_ap_ids(_, []), do: %{}
1216 def outgoing_relations_ap_ids(%User{} = user, relationship_types)
1217 when is_list(relationship_types) do
1220 |> assoc(:outgoing_relationships)
1221 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1222 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1223 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1224 |> group_by([user_rel, u], user_rel.relationship_type)
1226 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1231 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1235 def deactivate_async(user, status \\ true) do
1236 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1239 def deactivate(user, status \\ true)
1241 def deactivate(users, status) when is_list(users) do
1242 Repo.transaction(fn ->
1243 for user <- users, do: deactivate(user, status)
1247 def deactivate(%User{} = user, status) do
1248 with {:ok, user} <- set_activation_status(user, status) do
1251 |> Enum.filter(& &1.local)
1252 |> Enum.each(fn follower ->
1253 follower |> update_following_count() |> set_cache()
1256 # Only update local user counts, remote will be update during the next pull.
1259 |> Enum.filter(& &1.local)
1260 |> Enum.each(&update_follower_count/1)
1266 def update_notification_settings(%User{} = user, settings) do
1268 |> cast(%{notification_settings: settings}, [])
1269 |> cast_embed(:notification_settings)
1270 |> validate_required([:notification_settings])
1271 |> update_and_set_cache()
1274 def delete(users) when is_list(users) do
1275 for user <- users, do: delete(user)
1278 def delete(%User{} = user) do
1279 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1282 def perform(:force_password_reset, user), do: force_password_reset(user)
1284 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1285 def perform(:delete, %User{} = user) do
1286 {:ok, _user} = ActivityPub.delete(user)
1288 # Remove all relationships
1291 |> Enum.each(fn follower ->
1292 ActivityPub.unfollow(follower, user)
1293 unfollow(follower, user)
1298 |> Enum.each(fn followed ->
1299 ActivityPub.unfollow(user, followed)
1300 unfollow(user, followed)
1303 delete_user_activities(user)
1304 invalidate_cache(user)
1308 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1310 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1311 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1312 when is_list(blocked_identifiers) do
1314 blocked_identifiers,
1315 fn blocked_identifier ->
1316 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1317 {:ok, _user_block} <- block(blocker, blocked),
1318 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1322 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1329 def perform(:follow_import, %User{} = follower, followed_identifiers)
1330 when is_list(followed_identifiers) do
1332 followed_identifiers,
1333 fn followed_identifier ->
1334 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1335 {:ok, follower} <- maybe_direct_follow(follower, followed),
1336 {:ok, _} <- ActivityPub.follow(follower, followed) do
1340 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1347 @spec external_users_query() :: Ecto.Query.t()
1348 def external_users_query do
1356 @spec external_users(keyword()) :: [User.t()]
1357 def external_users(opts \\ []) do
1359 external_users_query()
1360 |> select([u], struct(u, [:id, :ap_id]))
1364 do: where(query, [u], u.id > ^opts[:max_id]),
1369 do: limit(query, ^opts[:limit]),
1375 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1376 BackgroundWorker.enqueue("blocks_import", %{
1377 "blocker_id" => blocker.id,
1378 "blocked_identifiers" => blocked_identifiers
1382 def follow_import(%User{} = follower, followed_identifiers)
1383 when is_list(followed_identifiers) do
1384 BackgroundWorker.enqueue("follow_import", %{
1385 "follower_id" => follower.id,
1386 "followed_identifiers" => followed_identifiers
1390 def delete_user_activities(%User{ap_id: ap_id}) do
1392 |> Activity.Queries.by_actor()
1393 |> RepoStreamer.chunk_stream(50)
1394 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1398 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1400 |> Object.normalize()
1401 |> ActivityPub.delete()
1404 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1405 object = Object.normalize(activity)
1408 |> get_cached_by_ap_id()
1409 |> ActivityPub.unlike(object)
1412 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1413 object = Object.normalize(activity)
1416 |> get_cached_by_ap_id()
1417 |> ActivityPub.unannounce(object)
1420 defp delete_activity(_activity), do: "Doing nothing"
1422 def html_filter_policy(%User{no_rich_text: true}) do
1423 Pleroma.HTML.Scrubber.TwitterText
1426 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1428 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1430 def get_or_fetch_by_ap_id(ap_id) do
1431 user = get_cached_by_ap_id(ap_id)
1433 if !is_nil(user) and !needs_update?(user) do
1436 fetch_by_ap_id(ap_id)
1441 Creates an internal service actor by URI if missing.
1442 Optionally takes nickname for addressing.
1444 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1445 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1447 case get_cached_by_ap_id(uri) do
1449 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1450 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1454 %User{invisible: false} = user ->
1464 @spec set_invisible(User.t()) :: {:ok, User.t()}
1465 defp set_invisible(user) do
1467 |> change(%{invisible: true})
1468 |> update_and_set_cache()
1471 @spec create_service_actor(String.t(), String.t()) ::
1472 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1473 defp create_service_actor(uri, nickname) do
1479 follower_address: uri <> "/followers"
1482 |> unique_constraint(:nickname)
1488 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1491 |> :public_key.pem_decode()
1493 |> :public_key.pem_entry_decode()
1498 def public_key(_), do: {:error, "not found key"}
1500 def get_public_key_for_ap_id(ap_id) do
1501 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1502 {:ok, public_key} <- public_key(user) do
1509 defp blank?(""), do: nil
1510 defp blank?(n), do: n
1512 def insert_or_update_user(data) do
1514 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1515 |> remote_user_creation()
1516 |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
1520 def ap_enabled?(%User{local: true}), do: true
1521 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1522 def ap_enabled?(_), do: false
1524 @doc "Gets or fetch a user by uri or nickname."
1525 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1526 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1527 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1529 # wait a period of time and return newest version of the User structs
1530 # this is because we have synchronous follow APIs and need to simulate them
1531 # with an async handshake
1532 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1533 with %User{} = a <- get_cached_by_id(a.id),
1534 %User{} = b <- get_cached_by_id(b.id) do
1541 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1542 with :ok <- :timer.sleep(timeout),
1543 %User{} = a <- get_cached_by_id(a.id),
1544 %User{} = b <- get_cached_by_id(b.id) do
1551 def parse_bio(bio) when is_binary(bio) and bio != "" do
1553 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1557 def parse_bio(_), do: ""
1559 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1560 # TODO: get profile URLs other than user.ap_id
1561 profile_urls = [user.ap_id]
1564 |> CommonUtils.format_input("text/plain",
1565 mentions_format: :full,
1566 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1571 def parse_bio(_, _), do: ""
1573 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1574 Repo.transaction(fn ->
1575 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1579 def tag(nickname, tags) when is_binary(nickname),
1580 do: tag(get_by_nickname(nickname), tags)
1582 def tag(%User{} = user, tags),
1583 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1585 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1586 Repo.transaction(fn ->
1587 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1591 def untag(nickname, tags) when is_binary(nickname),
1592 do: untag(get_by_nickname(nickname), tags)
1594 def untag(%User{} = user, tags),
1595 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1597 defp update_tags(%User{} = user, new_tags) do
1598 {:ok, updated_user} =
1600 |> change(%{tags: new_tags})
1601 |> update_and_set_cache()
1606 defp normalize_tags(tags) do
1609 |> Enum.map(&String.downcase/1)
1612 defp local_nickname_regex do
1613 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1614 @extended_local_nickname_regex
1616 @strict_local_nickname_regex
1620 def local_nickname(nickname_or_mention) do
1623 |> String.split("@")
1627 def full_nickname(nickname_or_mention),
1628 do: String.trim_leading(nickname_or_mention, "@")
1630 def error_user(ap_id) do
1634 nickname: "erroruser@example.com",
1635 inserted_at: NaiveDateTime.utc_now()
1639 @spec all_superusers() :: [User.t()]
1640 def all_superusers do
1641 User.Query.build(%{super_users: true, local: true, deactivated: false})
1645 def showing_reblogs?(%User{} = user, %User{} = target) do
1646 not UserRelationship.reblog_mute_exists?(user, target)
1650 The function returns a query to get users with no activity for given interval of days.
1651 Inactive users are those who didn't read any notification, or had any activity where
1652 the user is the activity's actor, during `inactivity_threshold` days.
1653 Deactivated users will not appear in this list.
1657 iex> Pleroma.User.list_inactive_users()
1660 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1661 def list_inactive_users_query(inactivity_threshold \\ 7) do
1662 negative_inactivity_threshold = -inactivity_threshold
1663 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1664 # Subqueries are not supported in `where` clauses, join gets too complicated.
1665 has_read_notifications =
1666 from(n in Pleroma.Notification,
1667 where: n.seen == true,
1669 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1672 |> Pleroma.Repo.all()
1674 from(u in Pleroma.User,
1675 left_join: a in Pleroma.Activity,
1676 on: u.ap_id == a.actor,
1677 where: not is_nil(u.nickname),
1678 where: u.deactivated != ^true,
1679 where: u.id not in ^has_read_notifications,
1682 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1683 is_nil(max(a.inserted_at))
1688 Enable or disable email notifications for user
1692 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1693 Pleroma.User{email_notifications: %{"digest" => true}}
1695 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1696 Pleroma.User{email_notifications: %{"digest" => false}}
1698 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1699 {:ok, t()} | {:error, Ecto.Changeset.t()}
1700 def switch_email_notifications(user, type, status) do
1701 User.update_email_notifications(user, %{type => status})
1705 Set `last_digest_emailed_at` value for the user to current time
1707 @spec touch_last_digest_emailed_at(t()) :: t()
1708 def touch_last_digest_emailed_at(user) do
1709 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1711 {:ok, updated_user} =
1713 |> change(%{last_digest_emailed_at: now})
1714 |> update_and_set_cache()
1719 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1720 def toggle_confirmation(%User{} = user) do
1722 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1723 |> update_and_set_cache()
1726 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1727 def toggle_confirmation(users) do
1728 Enum.map(users, &toggle_confirmation/1)
1731 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1735 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1736 # use instance-default
1737 config = Pleroma.Config.get([:assets, :mascots])
1738 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1739 mascot = Keyword.get(config, default_mascot)
1742 "id" => "default-mascot",
1743 "url" => mascot[:url],
1744 "preview_url" => mascot[:url],
1746 "mime_type" => mascot[:mime_type]
1751 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1753 def ensure_keys_present(%User{} = user) do
1754 with {:ok, pem} <- Keys.generate_rsa_pem() do
1756 |> cast(%{keys: pem}, [:keys])
1757 |> validate_required([:keys])
1758 |> update_and_set_cache()
1762 def get_ap_ids_by_nicknames(nicknames) do
1764 where: u.nickname in ^nicknames,
1770 defdelegate search(query, opts \\ []), to: User.Search
1772 defp put_password_hash(
1773 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1775 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1778 defp put_password_hash(changeset), do: changeset
1780 def is_internal_user?(%User{nickname: nil}), do: true
1781 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1782 def is_internal_user?(_), do: false
1784 # A hack because user delete activities have a fake id for whatever reason
1785 # TODO: Get rid of this
1786 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1788 def get_delivered_users_by_object_id(object_id) do
1790 inner_join: delivery in assoc(u, :deliveries),
1791 where: delivery.object_id == ^object_id
1796 def change_email(user, email) do
1798 |> cast(%{email: email}, [:email])
1799 |> validate_required([:email])
1800 |> unique_constraint(:email)
1801 |> validate_format(:email, @email_regex)
1802 |> update_and_set_cache()
1805 # Internal function; public one is `deactivate/2`
1806 defp set_activation_status(user, deactivated) do
1808 |> cast(%{deactivated: deactivated}, [:deactivated])
1809 |> update_and_set_cache()
1812 def update_banner(user, banner) do
1814 |> cast(%{banner: banner}, [:banner])
1815 |> update_and_set_cache()
1818 def update_background(user, background) do
1820 |> cast(%{background: background}, [:background])
1821 |> update_and_set_cache()
1824 def update_source_data(user, source_data) do
1826 |> cast(%{source_data: source_data}, [:source_data])
1827 |> update_and_set_cache()
1830 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1833 moderator: is_moderator
1837 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1838 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1839 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1840 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1843 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1844 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1848 def fields(%{fields: nil}), do: []
1850 def fields(%{fields: fields}), do: fields
1852 def validate_fields(changeset, remote? \\ false) do
1853 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1854 limit = Pleroma.Config.get([:instance, limit_name], 0)
1857 |> validate_length(:fields, max: limit)
1858 |> validate_change(:fields, fn :fields, fields ->
1859 if Enum.all?(fields, &valid_field?/1) do
1867 defp valid_field?(%{"name" => name, "value" => value}) do
1868 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1869 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1871 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1872 String.length(value) <= value_limit
1875 defp valid_field?(_), do: false
1877 defp truncate_field(%{"name" => name, "value" => value}) do
1879 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1882 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1884 %{"name" => name, "value" => value}
1887 def admin_api_update(user, params) do
1894 |> update_and_set_cache()
1897 @doc "Signs user out of all applications"
1898 def global_sign_out(user) do
1899 OAuth.Authorization.delete_user_authorizations(user)
1900 OAuth.Token.delete_user_tokens(user)
1903 def mascot_update(user, url) do
1905 |> cast(%{mascot: url}, [:mascot])
1906 |> validate_required([:mascot])
1907 |> update_and_set_cache()
1910 def mastodon_settings_update(user, settings) do
1912 |> cast(%{settings: settings}, [:settings])
1913 |> validate_required([:settings])
1914 |> update_and_set_cache()
1917 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1918 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1920 if need_confirmation? do
1922 confirmation_pending: true,
1923 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1927 confirmation_pending: false,
1928 confirmation_token: nil
1932 cast(user, params, [:confirmation_pending, :confirmation_token])
1935 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1936 if id not in user.pinned_activities do
1937 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1938 params = %{pinned_activities: user.pinned_activities ++ [id]}
1941 |> cast(params, [:pinned_activities])
1942 |> validate_length(:pinned_activities,
1943 max: max_pinned_statuses,
1944 message: "You have already pinned the maximum number of statuses"
1949 |> update_and_set_cache()
1952 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1953 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1956 |> cast(params, [:pinned_activities])
1957 |> update_and_set_cache()
1960 def update_email_notifications(user, settings) do
1961 email_notifications =
1962 user.email_notifications
1963 |> Map.merge(settings)
1964 |> Map.take(["digest"])
1966 params = %{email_notifications: email_notifications}
1967 fields = [:email_notifications]
1970 |> cast(params, fields)
1971 |> validate_required(fields)
1972 |> update_and_set_cache()
1975 defp set_domain_blocks(user, domain_blocks) do
1976 params = %{domain_blocks: domain_blocks}
1979 |> cast(params, [:domain_blocks])
1980 |> validate_required([:domain_blocks])
1981 |> update_and_set_cache()
1984 def block_domain(user, domain_blocked) do
1985 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1988 def unblock_domain(user, domain_blocked) do
1989 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1992 @spec add_to_block(User.t(), User.t()) ::
1993 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
1994 defp add_to_block(%User{} = user, %User{} = blocked) do
1995 UserRelationship.create_block(user, blocked)
1998 @spec add_to_block(User.t(), User.t()) ::
1999 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2000 defp remove_from_block(%User{} = user, %User{} = blocked) do
2001 UserRelationship.delete_block(user, blocked)
2004 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2005 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2006 {:ok, user_notification_mute} <-
2007 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2009 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2013 defp remove_from_mutes(user, %User{} = muted_user) do
2014 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2015 {:ok, user_notification_mute} <-
2016 UserRelationship.delete_notification_mute(user, muted_user) do
2017 {:ok, [user_mute, user_notification_mute]}
2021 def set_invisible(user, invisible) do
2022 params = %{invisible: invisible}
2025 |> cast(params, [:invisible])
2026 |> validate_required([:invisible])
2027 |> update_and_set_cache()
2030 def sanitize_html(%User{} = user) do
2031 sanitize_html(user, nil)
2034 # User data that mastodon isn't filtering (treated as plaintext):
2037 def sanitize_html(%User{} = user, filter) do
2041 |> Enum.map(fn %{"name" => name, "value" => value} ->
2044 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2049 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2050 |> Map.put(:fields, fields)