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
20 alias Pleroma.Notification
22 alias Pleroma.Registration
24 alias Pleroma.RepoStreamer
26 alias Pleroma.UserRelationship
28 alias Pleroma.Web.ActivityPub.ActivityPub
29 alias Pleroma.Web.ActivityPub.Utils
30 alias Pleroma.Web.CommonAPI
31 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
32 alias Pleroma.Web.OAuth
33 alias Pleroma.Web.RelMe
34 alias Pleroma.Workers.BackgroundWorker
38 @type t :: %__MODULE__{}
39 @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
40 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
42 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
43 @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
45 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
46 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
48 # AP ID user relationships (blocks, mutes etc.)
49 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
50 @user_relationships_config [
52 blocker_blocks: :blocked_users,
53 blockee_blocks: :blocker_users
56 muter_mutes: :muted_users,
57 mutee_mutes: :muter_users
60 reblog_muter_mutes: :reblog_muted_users,
61 reblog_mutee_mutes: :reblog_muter_users
64 notification_muter_mutes: :notification_muted_users,
65 notification_mutee_mutes: :notification_muter_users
67 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
68 inverse_subscription: [
69 subscribee_subscriptions: :subscriber_users,
70 subscriber_subscriptions: :subscribee_users
76 field(:email, :string)
78 field(:nickname, :string)
79 field(:password_hash, :string)
80 field(:password, :string, virtual: true)
81 field(:password_confirmation, :string, virtual: true)
83 field(:ap_id, :string)
85 field(:local, :boolean, default: true)
86 field(:follower_address, :string)
87 field(:following_address, :string)
88 field(:search_rank, :float, virtual: true)
89 field(:search_type, :integer, virtual: true)
90 field(:tags, {:array, :string}, default: [])
91 field(:last_refreshed_at, :naive_datetime_usec)
92 field(:last_digest_emailed_at, :naive_datetime)
93 field(:banner, :map, default: %{})
94 field(:background, :map, default: %{})
95 field(:source_data, :map, default: %{})
96 field(:note_count, :integer, default: 0)
97 field(:follower_count, :integer, default: 0)
98 field(:following_count, :integer, default: 0)
99 field(:locked, :boolean, default: false)
100 field(:confirmation_pending, :boolean, default: false)
101 field(:password_reset_pending, :boolean, default: false)
102 field(:confirmation_token, :string, default: nil)
103 field(:default_scope, :string, default: "public")
104 field(:domain_blocks, {:array, :string}, default: [])
105 field(:deactivated, :boolean, default: false)
106 field(:no_rich_text, :boolean, default: false)
107 field(:ap_enabled, :boolean, default: false)
108 field(:is_moderator, :boolean, default: false)
109 field(:is_admin, :boolean, default: false)
110 field(:show_role, :boolean, default: true)
111 field(:settings, :map, default: nil)
112 field(:magic_key, :string, default: nil)
113 field(:uri, :string, default: nil)
114 field(:hide_followers_count, :boolean, default: false)
115 field(:hide_follows_count, :boolean, default: false)
116 field(:hide_followers, :boolean, default: false)
117 field(:hide_follows, :boolean, default: false)
118 field(:hide_favorites, :boolean, default: true)
119 field(:unread_conversation_count, :integer, default: 0)
120 field(:pinned_activities, {:array, :string}, default: [])
121 field(:email_notifications, :map, default: %{"digest" => false})
122 field(:mascot, :map, default: nil)
123 field(:emoji, {:array, :map}, default: [])
124 field(:pleroma_settings_store, :map, default: %{})
125 field(:fields, {:array, :map}, default: [])
126 field(:raw_fields, {:array, :map}, default: [])
127 field(:discoverable, :boolean, default: false)
128 field(:invisible, :boolean, default: false)
129 field(:allow_following_move, :boolean, default: true)
130 field(:skip_thread_containment, :boolean, default: false)
131 field(:actor_type, :string, default: "Person")
132 field(:also_known_as, {:array, :string}, default: [])
135 :notification_settings,
136 Pleroma.User.NotificationSetting,
140 has_many(:notifications, Notification)
141 has_many(:registrations, Registration)
142 has_many(:deliveries, Delivery)
144 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
145 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
147 for {relationship_type,
149 {outgoing_relation, outgoing_relation_target},
150 {incoming_relation, incoming_relation_source}
151 ]} <- @user_relationships_config do
152 # Definitions of `has_many :blocker_blocks`, `has_many :muter_mutes` etc.
153 has_many(outgoing_relation, UserRelationship,
154 foreign_key: :source_id,
155 where: [relationship_type: relationship_type]
158 # Definitions of `has_many :blockee_blocks`, `has_many :mutee_mutes` etc.
159 has_many(incoming_relation, UserRelationship,
160 foreign_key: :target_id,
161 where: [relationship_type: relationship_type]
164 # Definitions of `has_many :blocked_users`, `has_many :muted_users` etc.
165 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
167 # Definitions of `has_many :blocker_users`, `has_many :muter_users` etc.
168 has_many(incoming_relation_source, through: [incoming_relation, :source])
171 # `:blocks` is deprecated (replaced with `blocked_users` relation)
172 field(:blocks, {:array, :string}, default: [])
173 # `:mutes` is deprecated (replaced with `muted_users` relation)
174 field(:mutes, {:array, :string}, default: [])
175 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
176 field(:muted_reblogs, {:array, :string}, default: [])
177 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
178 field(:muted_notifications, {:array, :string}, default: [])
179 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
180 field(:subscribers, {:array, :string}, default: [])
185 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
186 @user_relationships_config do
187 # Definitions of `blocked_users_relation/1`, `muted_users_relation/1`, etc.
188 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
189 target_users_query = assoc(user, unquote(outgoing_relation_target))
191 if restrict_deactivated? do
192 restrict_deactivated(target_users_query)
198 # Definitions of `blocked_users/1`, `muted_users/1`, etc.
199 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
201 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
203 restrict_deactivated?
208 # Definitions of `blocked_users_ap_ids/1`, `muted_users_ap_ids/1`, etc.
209 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
211 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
213 restrict_deactivated?
215 |> select([u], u.ap_id)
220 @doc "Returns status account"
221 @spec account_status(User.t()) :: account_status()
222 def account_status(%User{deactivated: true}), do: :deactivated
223 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
225 def account_status(%User{confirmation_pending: true}) do
226 case Config.get([:instance, :account_activation_required]) do
227 true -> :confirmation_pending
232 def account_status(%User{}), do: :active
234 @spec visible_for?(User.t(), User.t() | nil) :: boolean()
235 def visible_for?(user, for_user \\ nil)
237 def visible_for?(%User{invisible: true}, _), do: false
239 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
241 def visible_for?(%User{} = user, for_user) do
242 account_status(user) == :active || superuser?(for_user)
245 def visible_for?(_, _), do: false
247 @spec superuser?(User.t()) :: boolean()
248 def superuser?(%User{local: true, is_admin: true}), do: true
249 def superuser?(%User{local: true, is_moderator: true}), do: true
250 def superuser?(_), do: false
252 @spec invisible?(User.t()) :: boolean()
253 def invisible?(%User{invisible: true}), do: true
254 def invisible?(_), do: false
256 def avatar_url(user, options \\ []) do
258 %{"url" => [%{"href" => href} | _]} -> href
259 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
263 def banner_url(user, options \\ []) do
265 %{"url" => [%{"href" => href} | _]} -> href
266 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
270 def profile_url(%User{source_data: %{"url" => url}}), do: url
271 def profile_url(%User{ap_id: ap_id}), do: ap_id
272 def profile_url(_), do: nil
274 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
276 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
277 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
279 @spec ap_following(User.t()) :: Sring.t()
280 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
281 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
283 def follow_state(%User{} = user, %User{} = target) do
284 case Utils.fetch_latest_follow(user, target) do
285 %{data: %{"state" => state}} -> state
286 # Ideally this would be nil, but then Cachex does not commit the value
291 def get_cached_follow_state(user, target) do
292 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
293 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
296 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
297 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
298 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
301 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
302 def restrict_deactivated(query) do
303 from(u in query, where: u.deactivated != ^true)
306 defdelegate following_count(user), to: FollowingRelationship
308 defp truncate_fields_param(params) do
309 if Map.has_key?(params, :fields) do
310 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
316 defp truncate_if_exists(params, key, max_length) do
317 if Map.has_key?(params, key) and is_binary(params[key]) do
318 {value, _chopped} = String.split_at(params[key], max_length)
319 Map.put(params, key, value)
325 def remote_user_creation(params) do
326 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
327 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
331 |> truncate_if_exists(:name, name_limit)
332 |> truncate_if_exists(:bio, bio_limit)
333 |> truncate_fields_param()
353 :hide_followers_count,
364 |> validate_required([:name, :ap_id])
365 |> unique_constraint(:nickname)
366 |> validate_format(:nickname, @email_regex)
367 |> validate_length(:bio, max: bio_limit)
368 |> validate_length(:name, max: name_limit)
369 |> validate_fields(true)
371 case params[:source_data] do
372 %{"followers" => followers, "following" => following} ->
374 |> put_change(:follower_address, followers)
375 |> put_change(:following_address, following)
378 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
379 put_change(changeset, :follower_address, followers)
383 def update_changeset(struct, params \\ %{}) do
384 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
385 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
400 :hide_followers_count,
403 :allow_following_move,
406 :skip_thread_containment,
409 :pleroma_settings_store,
415 |> unique_constraint(:nickname)
416 |> validate_format(:nickname, local_nickname_regex())
417 |> validate_length(:bio, max: bio_limit)
418 |> validate_length(:name, min: 1, max: name_limit)
419 |> validate_fields(false)
422 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
423 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
424 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
426 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
428 params = if remote?, do: truncate_fields_param(params), else: params
450 :allow_following_move,
452 :hide_followers_count,
458 |> unique_constraint(:nickname)
459 |> validate_format(:nickname, local_nickname_regex())
460 |> validate_length(:bio, max: bio_limit)
461 |> validate_length(:name, max: name_limit)
462 |> validate_fields(remote?)
465 def password_update_changeset(struct, params) do
467 |> cast(params, [:password, :password_confirmation])
468 |> validate_required([:password, :password_confirmation])
469 |> validate_confirmation(:password)
470 |> put_password_hash()
471 |> put_change(:password_reset_pending, false)
474 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
475 def reset_password(%User{id: user_id} = user, data) do
478 |> Multi.update(:user, password_update_changeset(user, data))
479 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
480 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
482 case Repo.transaction(multi) do
483 {:ok, %{user: user} = _} -> set_cache(user)
484 {:error, _, changeset, _} -> {:error, changeset}
488 def update_password_reset_pending(user, value) do
491 |> put_change(:password_reset_pending, value)
492 |> update_and_set_cache()
495 def force_password_reset_async(user) do
496 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
499 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
500 def force_password_reset(user), do: update_password_reset_pending(user, true)
502 def register_changeset(struct, params \\ %{}, opts \\ []) do
503 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
504 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
507 if is_nil(opts[:need_confirmation]) do
508 Pleroma.Config.get([:instance, :account_activation_required])
510 opts[:need_confirmation]
514 |> confirmation_changeset(need_confirmation: need_confirmation?)
515 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
516 |> validate_required([:name, :nickname, :password, :password_confirmation])
517 |> validate_confirmation(:password)
518 |> unique_constraint(:email)
519 |> unique_constraint(:nickname)
520 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
521 |> validate_format(:nickname, local_nickname_regex())
522 |> validate_format(:email, @email_regex)
523 |> validate_length(:bio, max: bio_limit)
524 |> validate_length(:name, min: 1, max: name_limit)
525 |> maybe_validate_required_email(opts[:external])
528 |> unique_constraint(:ap_id)
529 |> put_following_and_follower_address()
532 def maybe_validate_required_email(changeset, true), do: changeset
533 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
535 defp put_ap_id(changeset) do
536 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
537 put_change(changeset, :ap_id, ap_id)
540 defp put_following_and_follower_address(changeset) do
541 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
544 |> put_change(:follower_address, followers)
547 defp autofollow_users(user) do
548 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
551 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
554 follow_all(user, autofollowed_users)
557 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
558 def register(%Ecto.Changeset{} = changeset) do
559 with {:ok, user} <- Repo.insert(changeset) do
560 post_register_action(user)
564 def post_register_action(%User{} = user) do
565 with {:ok, user} <- autofollow_users(user),
566 {:ok, user} <- set_cache(user),
567 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
568 {:ok, _} <- try_send_confirmation_email(user) do
573 def try_send_confirmation_email(%User{} = user) do
574 if user.confirmation_pending &&
575 Pleroma.Config.get([:instance, :account_activation_required]) do
577 |> Pleroma.Emails.UserEmail.account_confirmation_email()
578 |> Pleroma.Emails.Mailer.deliver_async()
586 def try_send_confirmation_email(users) do
587 Enum.each(users, &try_send_confirmation_email/1)
590 def needs_update?(%User{local: true}), do: false
592 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
594 def needs_update?(%User{local: false} = user) do
595 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
598 def needs_update?(_), do: true
600 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
601 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
602 follow(follower, followed, "pending")
605 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
606 follow(follower, followed)
609 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
610 if not ap_enabled?(followed) do
611 follow(follower, followed)
617 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
618 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
619 def follow_all(follower, followeds) do
621 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
622 |> Enum.each(&follow(follower, &1, "accept"))
627 defdelegate following(user), to: FollowingRelationship
629 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
630 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
633 followed.deactivated ->
634 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
636 deny_follow_blocked and blocks?(followed, follower) ->
637 {:error, "Could not follow user: #{followed.nickname} blocked you."}
640 FollowingRelationship.follow(follower, followed, state)
642 {:ok, _} = update_follower_count(followed)
645 |> update_following_count()
650 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
651 {:error, "Not subscribed!"}
654 def unfollow(%User{} = follower, %User{} = followed) do
655 case get_follow_state(follower, followed) do
656 state when state in ["accept", "pending"] ->
657 FollowingRelationship.unfollow(follower, followed)
658 {:ok, followed} = update_follower_count(followed)
662 |> update_following_count()
665 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
668 {:error, "Not subscribed!"}
672 defdelegate following?(follower, followed), to: FollowingRelationship
674 def get_follow_state(%User{} = follower, %User{} = following) do
675 following_relationship = FollowingRelationship.get(follower, following)
677 case {following_relationship, following.local} do
679 case Utils.fetch_latest_follow(follower, following) do
680 %{data: %{"state" => state}} when state in ["pending", "accept"] -> state
684 {%{state: state}, _} ->
692 def locked?(%User{} = user) do
697 Repo.get_by(User, id: id)
700 def get_by_ap_id(ap_id) do
701 Repo.get_by(User, ap_id: ap_id)
704 def get_all_by_ap_id(ap_ids) do
705 from(u in __MODULE__,
706 where: u.ap_id in ^ap_ids
711 def get_all_by_ids(ids) do
712 from(u in __MODULE__, where: u.id in ^ids)
716 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
717 # of the ap_id and the domain and tries to get that user
718 def get_by_guessed_nickname(ap_id) do
719 domain = URI.parse(ap_id).host
720 name = List.last(String.split(ap_id, "/"))
721 nickname = "#{name}@#{domain}"
723 get_cached_by_nickname(nickname)
726 def set_cache({:ok, user}), do: set_cache(user)
727 def set_cache({:error, err}), do: {:error, err}
729 def set_cache(%User{} = user) do
730 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
731 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
735 def update_and_set_cache(struct, params) do
737 |> update_changeset(params)
738 |> update_and_set_cache()
741 def update_and_set_cache(changeset) do
742 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
747 def invalidate_cache(user) do
748 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
749 Cachex.del(:user_cache, "nickname:#{user.nickname}")
752 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
753 def get_cached_by_ap_id(ap_id) do
754 key = "ap_id:#{ap_id}"
756 with {:ok, nil} <- Cachex.get(:user_cache, key),
757 user when not is_nil(user) <- get_by_ap_id(ap_id),
758 {:ok, true} <- Cachex.put(:user_cache, key, user) do
766 def get_cached_by_id(id) do
770 Cachex.fetch!(:user_cache, key, fn _ ->
774 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
775 {:commit, user.ap_id}
781 get_cached_by_ap_id(ap_id)
784 def get_cached_by_nickname(nickname) do
785 key = "nickname:#{nickname}"
787 Cachex.fetch!(:user_cache, key, fn ->
788 case get_or_fetch_by_nickname(nickname) do
789 {:ok, user} -> {:commit, user}
790 {:error, _error} -> {:ignore, nil}
795 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
796 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
799 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
800 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
802 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
803 get_cached_by_nickname(nickname_or_id)
805 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
806 get_cached_by_nickname(nickname_or_id)
813 def get_by_nickname(nickname) do
814 Repo.get_by(User, nickname: nickname) ||
815 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
816 Repo.get_by(User, nickname: local_nickname(nickname))
820 def get_by_email(email), do: Repo.get_by(User, email: email)
822 def get_by_nickname_or_email(nickname_or_email) do
823 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
826 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
828 def get_or_fetch_by_nickname(nickname) do
829 with %User{} = user <- get_by_nickname(nickname) do
833 with [_nick, _domain] <- String.split(nickname, "@"),
834 {:ok, user} <- fetch_by_nickname(nickname) do
835 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
836 fetch_initial_posts(user)
841 _e -> {:error, "not found " <> nickname}
846 @doc "Fetch some posts when the user has just been federated with"
847 def fetch_initial_posts(user) do
848 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
851 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
852 def get_followers_query(%User{} = user, nil) do
853 User.Query.build(%{followers: user, deactivated: false})
856 def get_followers_query(user, page) do
858 |> get_followers_query(nil)
859 |> User.Query.paginate(page, 20)
862 @spec get_followers_query(User.t()) :: Ecto.Query.t()
863 def get_followers_query(user), do: get_followers_query(user, nil)
865 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
866 def get_followers(user, page \\ nil) do
868 |> get_followers_query(page)
872 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
873 def get_external_followers(user, page \\ nil) do
875 |> get_followers_query(page)
876 |> User.Query.build(%{external: true})
880 def get_followers_ids(user, page \\ nil) do
882 |> get_followers_query(page)
887 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
888 def get_friends_query(%User{} = user, nil) do
889 User.Query.build(%{friends: user, deactivated: false})
892 def get_friends_query(user, page) do
894 |> get_friends_query(nil)
895 |> User.Query.paginate(page, 20)
898 @spec get_friends_query(User.t()) :: Ecto.Query.t()
899 def get_friends_query(user), do: get_friends_query(user, nil)
901 def get_friends(user, page \\ nil) do
903 |> get_friends_query(page)
907 def get_friends_ap_ids(user) do
909 |> get_friends_query(nil)
910 |> select([u], u.ap_id)
914 def get_friends_ids(user, page \\ nil) do
916 |> get_friends_query(page)
921 defdelegate get_follow_requests(user), to: FollowingRelationship
923 def increase_note_count(%User{} = user) do
925 |> where(id: ^user.id)
926 |> update([u], inc: [note_count: 1])
928 |> Repo.update_all([])
930 {1, [user]} -> set_cache(user)
935 def decrease_note_count(%User{} = user) do
937 |> where(id: ^user.id)
940 note_count: fragment("greatest(0, note_count - 1)")
944 |> Repo.update_all([])
946 {1, [user]} -> set_cache(user)
951 def update_note_count(%User{} = user, note_count \\ nil) do
956 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
962 |> cast(%{note_count: note_count}, [:note_count])
963 |> update_and_set_cache()
966 @spec maybe_fetch_follow_information(User.t()) :: User.t()
967 def maybe_fetch_follow_information(user) do
968 with {:ok, user} <- fetch_follow_information(user) do
972 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
978 def fetch_follow_information(user) do
979 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
981 |> follow_information_changeset(info)
982 |> update_and_set_cache()
986 defp follow_information_changeset(user, params) do
993 :hide_followers_count,
998 def update_follower_count(%User{} = user) do
999 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
1000 follower_count_query =
1001 User.Query.build(%{followers: user, deactivated: false})
1002 |> select([u], %{count: count(u.id)})
1005 |> where(id: ^user.id)
1006 |> join(:inner, [u], s in subquery(follower_count_query))
1008 set: [follower_count: s.count]
1011 |> Repo.update_all([])
1013 {1, [user]} -> set_cache(user)
1017 {:ok, maybe_fetch_follow_information(user)}
1021 @spec update_following_count(User.t()) :: User.t()
1022 def update_following_count(%User{local: false} = user) do
1023 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1024 maybe_fetch_follow_information(user)
1030 def update_following_count(%User{local: true} = user) do
1031 following_count = FollowingRelationship.following_count(user)
1034 |> follow_information_changeset(%{following_count: following_count})
1038 def set_unread_conversation_count(%User{local: true} = user) do
1039 unread_query = Participation.unread_conversation_count_for_user(user)
1042 |> join(:inner, [u], p in subquery(unread_query))
1044 set: [unread_conversation_count: p.count]
1046 |> where([u], u.id == ^user.id)
1048 |> Repo.update_all([])
1050 {1, [user]} -> set_cache(user)
1055 def set_unread_conversation_count(user), do: {:ok, user}
1057 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1059 Participation.unread_conversation_count_for_user(user)
1060 |> where([p], p.conversation_id == ^conversation.id)
1063 |> join(:inner, [u], p in subquery(unread_query))
1065 inc: [unread_conversation_count: 1]
1067 |> where([u], u.id == ^user.id)
1068 |> where([u, p], p.count == 0)
1070 |> Repo.update_all([])
1072 {1, [user]} -> set_cache(user)
1077 def increment_unread_conversation_count(_, user), do: {:ok, user}
1079 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1080 def get_users_from_set(ap_ids, local_only \\ true) do
1081 criteria = %{ap_id: ap_ids, deactivated: false}
1082 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1084 User.Query.build(criteria)
1088 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1089 def get_recipients_from_activity(%Activity{recipients: to}) do
1090 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1094 @spec mute(User.t(), User.t(), boolean()) ::
1095 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1096 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1097 add_to_mutes(muter, mutee, notifications?)
1100 def unmute(%User{} = muter, %User{} = mutee) do
1101 remove_from_mutes(muter, mutee)
1104 def subscribe(%User{} = subscriber, %User{} = target) do
1105 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1107 if blocks?(target, subscriber) and deny_follow_blocked do
1108 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1110 # Note: the relationship is inverse: subscriber acts as relationship target
1111 UserRelationship.create_inverse_subscription(target, subscriber)
1115 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1116 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1117 subscribe(subscriber, subscribee)
1121 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1122 # Note: the relationship is inverse: subscriber acts as relationship target
1123 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1126 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1127 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1128 unsubscribe(unsubscriber, user)
1132 def block(%User{} = blocker, %User{} = blocked) do
1133 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1135 if following?(blocker, blocked) do
1136 {:ok, blocker, _} = unfollow(blocker, blocked)
1142 # clear any requested follows as well
1144 case CommonAPI.reject_follow_request(blocked, blocker) do
1145 {:ok, %User{} = updated_blocked} -> updated_blocked
1149 unsubscribe(blocked, blocker)
1151 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1153 {:ok, blocker} = update_follower_count(blocker)
1154 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1155 add_to_block(blocker, blocked)
1158 # helper to handle the block given only an actor's AP id
1159 def block(%User{} = blocker, %{ap_id: ap_id}) do
1160 block(blocker, get_cached_by_ap_id(ap_id))
1163 def unblock(%User{} = blocker, %User{} = blocked) do
1164 remove_from_block(blocker, blocked)
1167 # helper to handle the block given only an actor's AP id
1168 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1169 unblock(blocker, get_cached_by_ap_id(ap_id))
1172 def mutes?(nil, _), do: false
1173 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1175 def mutes_user?(%User{} = user, %User{} = target) do
1176 UserRelationship.mute_exists?(user, target)
1179 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1180 def muted_notifications?(nil, _), do: false
1182 def muted_notifications?(%User{} = user, %User{} = target),
1183 do: UserRelationship.notification_mute_exists?(user, target)
1185 def blocks?(nil, _), do: false
1187 def blocks?(%User{} = user, %User{} = target) do
1188 blocks_user?(user, target) ||
1189 (!User.following?(user, target) && blocks_domain?(user, target))
1192 def blocks_user?(%User{} = user, %User{} = target) do
1193 UserRelationship.block_exists?(user, target)
1196 def blocks_user?(_, _), do: false
1198 def blocks_domain?(%User{} = user, %User{} = target) do
1199 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1200 %{host: host} = URI.parse(target.ap_id)
1201 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1204 def blocks_domain?(_, _), do: false
1206 def subscribed_to?(%User{} = user, %User{} = target) do
1207 # Note: the relationship is inverse: subscriber acts as relationship target
1208 UserRelationship.inverse_subscription_exists?(target, user)
1211 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1212 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1213 subscribed_to?(user, target)
1218 Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
1219 E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1221 @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1222 def outgoing_relations_ap_ids(_, []), do: %{}
1224 def outgoing_relations_ap_ids(%User{} = user, relationship_types)
1225 when is_list(relationship_types) do
1228 |> assoc(:outgoing_relationships)
1229 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1230 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1231 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1232 |> group_by([user_rel, u], user_rel.relationship_type)
1234 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1239 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1243 def deactivate_async(user, status \\ true) do
1244 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1247 def deactivate(user, status \\ true)
1249 def deactivate(users, status) when is_list(users) do
1250 Repo.transaction(fn ->
1251 for user <- users, do: deactivate(user, status)
1255 def deactivate(%User{} = user, status) do
1256 with {:ok, user} <- set_activation_status(user, status) do
1259 |> Enum.filter(& &1.local)
1260 |> Enum.each(fn follower ->
1261 follower |> update_following_count() |> set_cache()
1264 # Only update local user counts, remote will be update during the next pull.
1267 |> Enum.filter(& &1.local)
1268 |> Enum.each(&update_follower_count/1)
1274 def update_notification_settings(%User{} = user, settings) do
1276 |> cast(%{notification_settings: settings}, [])
1277 |> cast_embed(:notification_settings)
1278 |> validate_required([:notification_settings])
1279 |> update_and_set_cache()
1282 def delete(users) when is_list(users) do
1283 for user <- users, do: delete(user)
1286 def delete(%User{} = user) do
1287 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1290 def perform(:force_password_reset, user), do: force_password_reset(user)
1292 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1293 def perform(:delete, %User{} = user) do
1294 {:ok, _user} = ActivityPub.delete(user)
1296 # Remove all relationships
1299 |> Enum.each(fn follower ->
1300 ActivityPub.unfollow(follower, user)
1301 unfollow(follower, user)
1306 |> Enum.each(fn followed ->
1307 ActivityPub.unfollow(user, followed)
1308 unfollow(user, followed)
1311 delete_user_activities(user)
1312 invalidate_cache(user)
1316 def perform(:fetch_initial_posts, %User{} = user) do
1317 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1319 # Insert all the posts in reverse order, so they're in the right order on the timeline
1320 user.source_data["outbox"]
1321 |> Utils.fetch_ordered_collection(pages)
1323 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
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 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1455 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1457 resp = fetch_by_ap_id(ap_id)
1459 if should_fetch_initial do
1460 with {:ok, %User{} = user} <- resp do
1461 fetch_initial_posts(user)
1470 Creates an internal service actor by URI if missing.
1471 Optionally takes nickname for addressing.
1473 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1474 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1476 case get_cached_by_ap_id(uri) do
1478 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1479 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1483 %User{invisible: false} = user ->
1493 @spec set_invisible(User.t()) :: {:ok, User.t()}
1494 defp set_invisible(user) do
1496 |> change(%{invisible: true})
1497 |> update_and_set_cache()
1500 @spec create_service_actor(String.t(), String.t()) ::
1501 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1502 defp create_service_actor(uri, nickname) do
1508 follower_address: uri <> "/followers"
1511 |> unique_constraint(:nickname)
1517 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1520 |> :public_key.pem_decode()
1522 |> :public_key.pem_entry_decode()
1527 def public_key(_), do: {:error, "not found key"}
1529 def get_public_key_for_ap_id(ap_id) do
1530 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1531 {:ok, public_key} <- public_key(user) do
1538 defp blank?(""), do: nil
1539 defp blank?(n), do: n
1541 def insert_or_update_user(data) do
1543 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1544 |> remote_user_creation()
1545 |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
1549 def ap_enabled?(%User{local: true}), do: true
1550 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1551 def ap_enabled?(_), do: false
1553 @doc "Gets or fetch a user by uri or nickname."
1554 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1555 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1556 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1558 # wait a period of time and return newest version of the User structs
1559 # this is because we have synchronous follow APIs and need to simulate them
1560 # with an async handshake
1561 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1562 with %User{} = a <- get_cached_by_id(a.id),
1563 %User{} = b <- get_cached_by_id(b.id) do
1570 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1571 with :ok <- :timer.sleep(timeout),
1572 %User{} = a <- get_cached_by_id(a.id),
1573 %User{} = b <- get_cached_by_id(b.id) do
1580 def parse_bio(bio) when is_binary(bio) and bio != "" do
1582 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1586 def parse_bio(_), do: ""
1588 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1589 # TODO: get profile URLs other than user.ap_id
1590 profile_urls = [user.ap_id]
1593 |> CommonUtils.format_input("text/plain",
1594 mentions_format: :full,
1595 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1600 def parse_bio(_, _), do: ""
1602 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1603 Repo.transaction(fn ->
1604 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1608 def tag(nickname, tags) when is_binary(nickname),
1609 do: tag(get_by_nickname(nickname), tags)
1611 def tag(%User{} = user, tags),
1612 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1614 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1615 Repo.transaction(fn ->
1616 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1620 def untag(nickname, tags) when is_binary(nickname),
1621 do: untag(get_by_nickname(nickname), tags)
1623 def untag(%User{} = user, tags),
1624 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1626 defp update_tags(%User{} = user, new_tags) do
1627 {:ok, updated_user} =
1629 |> change(%{tags: new_tags})
1630 |> update_and_set_cache()
1635 defp normalize_tags(tags) do
1638 |> Enum.map(&String.downcase/1)
1641 defp local_nickname_regex do
1642 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1643 @extended_local_nickname_regex
1645 @strict_local_nickname_regex
1649 def local_nickname(nickname_or_mention) do
1652 |> String.split("@")
1656 def full_nickname(nickname_or_mention),
1657 do: String.trim_leading(nickname_or_mention, "@")
1659 def error_user(ap_id) do
1663 nickname: "erroruser@example.com",
1664 inserted_at: NaiveDateTime.utc_now()
1668 @spec all_superusers() :: [User.t()]
1669 def all_superusers do
1670 User.Query.build(%{super_users: true, local: true, deactivated: false})
1674 def showing_reblogs?(%User{} = user, %User{} = target) do
1675 not UserRelationship.reblog_mute_exists?(user, target)
1679 The function returns a query to get users with no activity for given interval of days.
1680 Inactive users are those who didn't read any notification, or had any activity where
1681 the user is the activity's actor, during `inactivity_threshold` days.
1682 Deactivated users will not appear in this list.
1686 iex> Pleroma.User.list_inactive_users()
1689 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1690 def list_inactive_users_query(inactivity_threshold \\ 7) do
1691 negative_inactivity_threshold = -inactivity_threshold
1692 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1693 # Subqueries are not supported in `where` clauses, join gets too complicated.
1694 has_read_notifications =
1695 from(n in Pleroma.Notification,
1696 where: n.seen == true,
1698 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1701 |> Pleroma.Repo.all()
1703 from(u in Pleroma.User,
1704 left_join: a in Pleroma.Activity,
1705 on: u.ap_id == a.actor,
1706 where: not is_nil(u.nickname),
1707 where: u.deactivated != ^true,
1708 where: u.id not in ^has_read_notifications,
1711 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1712 is_nil(max(a.inserted_at))
1717 Enable or disable email notifications for user
1721 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1722 Pleroma.User{email_notifications: %{"digest" => true}}
1724 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1725 Pleroma.User{email_notifications: %{"digest" => false}}
1727 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1728 {:ok, t()} | {:error, Ecto.Changeset.t()}
1729 def switch_email_notifications(user, type, status) do
1730 User.update_email_notifications(user, %{type => status})
1734 Set `last_digest_emailed_at` value for the user to current time
1736 @spec touch_last_digest_emailed_at(t()) :: t()
1737 def touch_last_digest_emailed_at(user) do
1738 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1740 {:ok, updated_user} =
1742 |> change(%{last_digest_emailed_at: now})
1743 |> update_and_set_cache()
1748 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1749 def toggle_confirmation(%User{} = user) do
1751 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1752 |> update_and_set_cache()
1755 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1756 def toggle_confirmation(users) do
1757 Enum.map(users, &toggle_confirmation/1)
1760 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1764 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1765 # use instance-default
1766 config = Pleroma.Config.get([:assets, :mascots])
1767 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1768 mascot = Keyword.get(config, default_mascot)
1771 "id" => "default-mascot",
1772 "url" => mascot[:url],
1773 "preview_url" => mascot[:url],
1775 "mime_type" => mascot[:mime_type]
1780 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1782 def ensure_keys_present(%User{} = user) do
1783 with {:ok, pem} <- Keys.generate_rsa_pem() do
1785 |> cast(%{keys: pem}, [:keys])
1786 |> validate_required([:keys])
1787 |> update_and_set_cache()
1791 def get_ap_ids_by_nicknames(nicknames) do
1793 where: u.nickname in ^nicknames,
1799 defdelegate search(query, opts \\ []), to: User.Search
1801 defp put_password_hash(
1802 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1804 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1807 defp put_password_hash(changeset), do: changeset
1809 def is_internal_user?(%User{nickname: nil}), do: true
1810 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1811 def is_internal_user?(_), do: false
1813 # A hack because user delete activities have a fake id for whatever reason
1814 # TODO: Get rid of this
1815 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1817 def get_delivered_users_by_object_id(object_id) do
1819 inner_join: delivery in assoc(u, :deliveries),
1820 where: delivery.object_id == ^object_id
1825 def change_email(user, email) do
1827 |> cast(%{email: email}, [:email])
1828 |> validate_required([:email])
1829 |> unique_constraint(:email)
1830 |> validate_format(:email, @email_regex)
1831 |> update_and_set_cache()
1834 # Internal function; public one is `deactivate/2`
1835 defp set_activation_status(user, deactivated) do
1837 |> cast(%{deactivated: deactivated}, [:deactivated])
1838 |> update_and_set_cache()
1841 def update_banner(user, banner) do
1843 |> cast(%{banner: banner}, [:banner])
1844 |> update_and_set_cache()
1847 def update_background(user, background) do
1849 |> cast(%{background: background}, [:background])
1850 |> update_and_set_cache()
1853 def update_source_data(user, source_data) do
1855 |> cast(%{source_data: source_data}, [:source_data])
1856 |> update_and_set_cache()
1859 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1862 moderator: is_moderator
1866 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1867 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1868 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1869 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1872 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1873 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1877 def fields(%{fields: nil}), do: []
1879 def fields(%{fields: fields}), do: fields
1881 def validate_fields(changeset, remote? \\ false) do
1882 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1883 limit = Pleroma.Config.get([:instance, limit_name], 0)
1886 |> validate_length(:fields, max: limit)
1887 |> validate_change(:fields, fn :fields, fields ->
1888 if Enum.all?(fields, &valid_field?/1) do
1896 defp valid_field?(%{"name" => name, "value" => value}) do
1897 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1898 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1900 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1901 String.length(value) <= value_limit
1904 defp valid_field?(_), do: false
1906 defp truncate_field(%{"name" => name, "value" => value}) do
1908 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1911 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1913 %{"name" => name, "value" => value}
1916 def admin_api_update(user, params) do
1923 |> update_and_set_cache()
1926 @doc "Signs user out of all applications"
1927 def global_sign_out(user) do
1928 OAuth.Authorization.delete_user_authorizations(user)
1929 OAuth.Token.delete_user_tokens(user)
1932 def mascot_update(user, url) do
1934 |> cast(%{mascot: url}, [:mascot])
1935 |> validate_required([:mascot])
1936 |> update_and_set_cache()
1939 def mastodon_settings_update(user, settings) do
1941 |> cast(%{settings: settings}, [:settings])
1942 |> validate_required([:settings])
1943 |> update_and_set_cache()
1946 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1947 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1949 if need_confirmation? do
1951 confirmation_pending: true,
1952 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1956 confirmation_pending: false,
1957 confirmation_token: nil
1961 cast(user, params, [:confirmation_pending, :confirmation_token])
1964 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1965 if id not in user.pinned_activities do
1966 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1967 params = %{pinned_activities: user.pinned_activities ++ [id]}
1970 |> cast(params, [:pinned_activities])
1971 |> validate_length(:pinned_activities,
1972 max: max_pinned_statuses,
1973 message: "You have already pinned the maximum number of statuses"
1978 |> update_and_set_cache()
1981 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1982 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1985 |> cast(params, [:pinned_activities])
1986 |> update_and_set_cache()
1989 def update_email_notifications(user, settings) do
1990 email_notifications =
1991 user.email_notifications
1992 |> Map.merge(settings)
1993 |> Map.take(["digest"])
1995 params = %{email_notifications: email_notifications}
1996 fields = [:email_notifications]
1999 |> cast(params, fields)
2000 |> validate_required(fields)
2001 |> update_and_set_cache()
2004 defp set_domain_blocks(user, domain_blocks) do
2005 params = %{domain_blocks: domain_blocks}
2008 |> cast(params, [:domain_blocks])
2009 |> validate_required([:domain_blocks])
2010 |> update_and_set_cache()
2013 def block_domain(user, domain_blocked) do
2014 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2017 def unblock_domain(user, domain_blocked) do
2018 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2021 @spec add_to_block(User.t(), User.t()) ::
2022 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2023 defp add_to_block(%User{} = user, %User{} = blocked) do
2024 UserRelationship.create_block(user, blocked)
2027 @spec add_to_block(User.t(), User.t()) ::
2028 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2029 defp remove_from_block(%User{} = user, %User{} = blocked) do
2030 UserRelationship.delete_block(user, blocked)
2033 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2034 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2035 {:ok, user_notification_mute} <-
2036 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2038 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2042 defp remove_from_mutes(user, %User{} = muted_user) do
2043 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2044 {:ok, user_notification_mute} <-
2045 UserRelationship.delete_notification_mute(user, muted_user) do
2046 {:ok, [user_mute, user_notification_mute]}
2050 def set_invisible(user, invisible) do
2051 params = %{invisible: invisible}
2054 |> cast(params, [:invisible])
2055 |> validate_required([:invisible])
2056 |> update_and_set_cache()