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
534 def maybe_validate_required_email(changeset, _) do
535 if Pleroma.Config.get([:instance, :account_activation_required]) do
536 validate_required(changeset, [:email])
542 defp put_ap_id(changeset) do
543 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
544 put_change(changeset, :ap_id, ap_id)
547 defp put_following_and_follower_address(changeset) do
548 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
551 |> put_change(:follower_address, followers)
554 defp autofollow_users(user) do
555 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
558 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
561 follow_all(user, autofollowed_users)
564 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
565 def register(%Ecto.Changeset{} = changeset) do
566 with {:ok, user} <- Repo.insert(changeset) do
567 post_register_action(user)
571 def post_register_action(%User{} = user) do
572 with {:ok, user} <- autofollow_users(user),
573 {:ok, user} <- set_cache(user),
574 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
575 {:ok, _} <- try_send_confirmation_email(user) do
580 def try_send_confirmation_email(%User{} = user) do
581 if user.confirmation_pending &&
582 Pleroma.Config.get([:instance, :account_activation_required]) do
584 |> Pleroma.Emails.UserEmail.account_confirmation_email()
585 |> Pleroma.Emails.Mailer.deliver_async()
593 def try_send_confirmation_email(users) do
594 Enum.each(users, &try_send_confirmation_email/1)
597 def needs_update?(%User{local: true}), do: false
599 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
601 def needs_update?(%User{local: false} = user) do
602 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
605 def needs_update?(_), do: true
607 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
608 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
609 follow(follower, followed, "pending")
612 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
613 follow(follower, followed)
616 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
617 if not ap_enabled?(followed) do
618 follow(follower, followed)
624 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
625 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
626 def follow_all(follower, followeds) do
628 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
629 |> Enum.each(&follow(follower, &1, "accept"))
634 defdelegate following(user), to: FollowingRelationship
636 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
637 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
640 followed.deactivated ->
641 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
643 deny_follow_blocked and blocks?(followed, follower) ->
644 {:error, "Could not follow user: #{followed.nickname} blocked you."}
647 FollowingRelationship.follow(follower, followed, state)
649 {:ok, _} = update_follower_count(followed)
652 |> update_following_count()
657 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
658 {:error, "Not subscribed!"}
661 def unfollow(%User{} = follower, %User{} = followed) do
662 case get_follow_state(follower, followed) do
663 state when state in ["accept", "pending"] ->
664 FollowingRelationship.unfollow(follower, followed)
665 {:ok, followed} = update_follower_count(followed)
669 |> update_following_count()
672 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
675 {:error, "Not subscribed!"}
679 defdelegate following?(follower, followed), to: FollowingRelationship
681 def get_follow_state(%User{} = follower, %User{} = following) do
682 following_relationship = FollowingRelationship.get(follower, following)
684 case {following_relationship, following.local} do
686 case Utils.fetch_latest_follow(follower, following) do
687 %{data: %{"state" => state}} when state in ["pending", "accept"] -> state
691 {%{state: state}, _} ->
699 def locked?(%User{} = user) do
704 Repo.get_by(User, id: id)
707 def get_by_ap_id(ap_id) do
708 Repo.get_by(User, ap_id: ap_id)
711 def get_all_by_ap_id(ap_ids) do
712 from(u in __MODULE__,
713 where: u.ap_id in ^ap_ids
718 def get_all_by_ids(ids) do
719 from(u in __MODULE__, where: u.id in ^ids)
723 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
724 # of the ap_id and the domain and tries to get that user
725 def get_by_guessed_nickname(ap_id) do
726 domain = URI.parse(ap_id).host
727 name = List.last(String.split(ap_id, "/"))
728 nickname = "#{name}@#{domain}"
730 get_cached_by_nickname(nickname)
733 def set_cache({:ok, user}), do: set_cache(user)
734 def set_cache({:error, err}), do: {:error, err}
736 def set_cache(%User{} = user) do
737 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
738 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
742 def update_and_set_cache(struct, params) do
744 |> update_changeset(params)
745 |> update_and_set_cache()
748 def update_and_set_cache(changeset) do
749 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
754 def invalidate_cache(user) do
755 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
756 Cachex.del(:user_cache, "nickname:#{user.nickname}")
759 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
760 def get_cached_by_ap_id(ap_id) do
761 key = "ap_id:#{ap_id}"
763 with {:ok, nil} <- Cachex.get(:user_cache, key),
764 user when not is_nil(user) <- get_by_ap_id(ap_id),
765 {:ok, true} <- Cachex.put(:user_cache, key, user) do
773 def get_cached_by_id(id) do
777 Cachex.fetch!(:user_cache, key, fn _ ->
781 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
782 {:commit, user.ap_id}
788 get_cached_by_ap_id(ap_id)
791 def get_cached_by_nickname(nickname) do
792 key = "nickname:#{nickname}"
794 Cachex.fetch!(:user_cache, key, fn ->
795 case get_or_fetch_by_nickname(nickname) do
796 {:ok, user} -> {:commit, user}
797 {:error, _error} -> {:ignore, nil}
802 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
803 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
806 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
807 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
809 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
810 get_cached_by_nickname(nickname_or_id)
812 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
813 get_cached_by_nickname(nickname_or_id)
820 def get_by_nickname(nickname) do
821 Repo.get_by(User, nickname: nickname) ||
822 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
823 Repo.get_by(User, nickname: local_nickname(nickname))
827 def get_by_email(email), do: Repo.get_by(User, email: email)
829 def get_by_nickname_or_email(nickname_or_email) do
830 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
833 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
835 def get_or_fetch_by_nickname(nickname) do
836 with %User{} = user <- get_by_nickname(nickname) do
840 with [_nick, _domain] <- String.split(nickname, "@"),
841 {:ok, user} <- fetch_by_nickname(nickname) do
842 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
843 fetch_initial_posts(user)
848 _e -> {:error, "not found " <> nickname}
853 @doc "Fetch some posts when the user has just been federated with"
854 def fetch_initial_posts(user) do
855 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
858 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
859 def get_followers_query(%User{} = user, nil) do
860 User.Query.build(%{followers: user, deactivated: false})
863 def get_followers_query(user, page) do
865 |> get_followers_query(nil)
866 |> User.Query.paginate(page, 20)
869 @spec get_followers_query(User.t()) :: Ecto.Query.t()
870 def get_followers_query(user), do: get_followers_query(user, nil)
872 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
873 def get_followers(user, page \\ nil) do
875 |> get_followers_query(page)
879 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
880 def get_external_followers(user, page \\ nil) do
882 |> get_followers_query(page)
883 |> User.Query.build(%{external: true})
887 def get_followers_ids(user, page \\ nil) do
889 |> get_followers_query(page)
894 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
895 def get_friends_query(%User{} = user, nil) do
896 User.Query.build(%{friends: user, deactivated: false})
899 def get_friends_query(user, page) do
901 |> get_friends_query(nil)
902 |> User.Query.paginate(page, 20)
905 @spec get_friends_query(User.t()) :: Ecto.Query.t()
906 def get_friends_query(user), do: get_friends_query(user, nil)
908 def get_friends(user, page \\ nil) do
910 |> get_friends_query(page)
914 def get_friends_ap_ids(user) do
916 |> get_friends_query(nil)
917 |> select([u], u.ap_id)
921 def get_friends_ids(user, page \\ nil) do
923 |> get_friends_query(page)
928 defdelegate get_follow_requests(user), to: FollowingRelationship
930 def increase_note_count(%User{} = user) do
932 |> where(id: ^user.id)
933 |> update([u], inc: [note_count: 1])
935 |> Repo.update_all([])
937 {1, [user]} -> set_cache(user)
942 def decrease_note_count(%User{} = user) do
944 |> where(id: ^user.id)
947 note_count: fragment("greatest(0, note_count - 1)")
951 |> Repo.update_all([])
953 {1, [user]} -> set_cache(user)
958 def update_note_count(%User{} = user, note_count \\ nil) do
963 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
969 |> cast(%{note_count: note_count}, [:note_count])
970 |> update_and_set_cache()
973 @spec maybe_fetch_follow_information(User.t()) :: User.t()
974 def maybe_fetch_follow_information(user) do
975 with {:ok, user} <- fetch_follow_information(user) do
979 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
985 def fetch_follow_information(user) do
986 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
988 |> follow_information_changeset(info)
989 |> update_and_set_cache()
993 defp follow_information_changeset(user, params) do
1000 :hide_followers_count,
1005 def update_follower_count(%User{} = user) do
1006 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
1007 follower_count_query =
1008 User.Query.build(%{followers: user, deactivated: false})
1009 |> select([u], %{count: count(u.id)})
1012 |> where(id: ^user.id)
1013 |> join(:inner, [u], s in subquery(follower_count_query))
1015 set: [follower_count: s.count]
1018 |> Repo.update_all([])
1020 {1, [user]} -> set_cache(user)
1024 {:ok, maybe_fetch_follow_information(user)}
1028 @spec update_following_count(User.t()) :: User.t()
1029 def update_following_count(%User{local: false} = user) do
1030 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1031 maybe_fetch_follow_information(user)
1037 def update_following_count(%User{local: true} = user) do
1038 following_count = FollowingRelationship.following_count(user)
1041 |> follow_information_changeset(%{following_count: following_count})
1045 def set_unread_conversation_count(%User{local: true} = user) do
1046 unread_query = Participation.unread_conversation_count_for_user(user)
1049 |> join(:inner, [u], p in subquery(unread_query))
1051 set: [unread_conversation_count: p.count]
1053 |> where([u], u.id == ^user.id)
1055 |> Repo.update_all([])
1057 {1, [user]} -> set_cache(user)
1062 def set_unread_conversation_count(user), do: {:ok, user}
1064 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1066 Participation.unread_conversation_count_for_user(user)
1067 |> where([p], p.conversation_id == ^conversation.id)
1070 |> join(:inner, [u], p in subquery(unread_query))
1072 inc: [unread_conversation_count: 1]
1074 |> where([u], u.id == ^user.id)
1075 |> where([u, p], p.count == 0)
1077 |> Repo.update_all([])
1079 {1, [user]} -> set_cache(user)
1084 def increment_unread_conversation_count(_, user), do: {:ok, user}
1086 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1087 def get_users_from_set(ap_ids, local_only \\ true) do
1088 criteria = %{ap_id: ap_ids, deactivated: false}
1089 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1091 User.Query.build(criteria)
1095 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1096 def get_recipients_from_activity(%Activity{recipients: to}) do
1097 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1101 @spec mute(User.t(), User.t(), boolean()) ::
1102 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1103 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1104 add_to_mutes(muter, mutee, notifications?)
1107 def unmute(%User{} = muter, %User{} = mutee) do
1108 remove_from_mutes(muter, mutee)
1111 def subscribe(%User{} = subscriber, %User{} = target) do
1112 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1114 if blocks?(target, subscriber) and deny_follow_blocked do
1115 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1117 # Note: the relationship is inverse: subscriber acts as relationship target
1118 UserRelationship.create_inverse_subscription(target, subscriber)
1122 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1123 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1124 subscribe(subscriber, subscribee)
1128 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1129 # Note: the relationship is inverse: subscriber acts as relationship target
1130 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1133 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1134 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1135 unsubscribe(unsubscriber, user)
1139 def block(%User{} = blocker, %User{} = blocked) do
1140 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1142 if following?(blocker, blocked) do
1143 {:ok, blocker, _} = unfollow(blocker, blocked)
1149 # clear any requested follows as well
1151 case CommonAPI.reject_follow_request(blocked, blocker) do
1152 {:ok, %User{} = updated_blocked} -> updated_blocked
1156 unsubscribe(blocked, blocker)
1158 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1160 {:ok, blocker} = update_follower_count(blocker)
1161 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1162 add_to_block(blocker, blocked)
1165 # helper to handle the block given only an actor's AP id
1166 def block(%User{} = blocker, %{ap_id: ap_id}) do
1167 block(blocker, get_cached_by_ap_id(ap_id))
1170 def unblock(%User{} = blocker, %User{} = blocked) do
1171 remove_from_block(blocker, blocked)
1174 # helper to handle the block given only an actor's AP id
1175 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1176 unblock(blocker, get_cached_by_ap_id(ap_id))
1179 def mutes?(nil, _), do: false
1180 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1182 def mutes_user?(%User{} = user, %User{} = target) do
1183 UserRelationship.mute_exists?(user, target)
1186 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1187 def muted_notifications?(nil, _), do: false
1189 def muted_notifications?(%User{} = user, %User{} = target),
1190 do: UserRelationship.notification_mute_exists?(user, target)
1192 def blocks?(nil, _), do: false
1194 def blocks?(%User{} = user, %User{} = target) do
1195 blocks_user?(user, target) ||
1196 (!User.following?(user, target) && blocks_domain?(user, target))
1199 def blocks_user?(%User{} = user, %User{} = target) do
1200 UserRelationship.block_exists?(user, target)
1203 def blocks_user?(_, _), do: false
1205 def blocks_domain?(%User{} = user, %User{} = target) do
1206 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1207 %{host: host} = URI.parse(target.ap_id)
1208 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1211 def blocks_domain?(_, _), do: false
1213 def subscribed_to?(%User{} = user, %User{} = target) do
1214 # Note: the relationship is inverse: subscriber acts as relationship target
1215 UserRelationship.inverse_subscription_exists?(target, user)
1218 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1219 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1220 subscribed_to?(user, target)
1225 Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
1226 E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1228 @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1229 def outgoing_relations_ap_ids(_, []), do: %{}
1231 def outgoing_relations_ap_ids(%User{} = user, relationship_types)
1232 when is_list(relationship_types) do
1235 |> assoc(:outgoing_relationships)
1236 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1237 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1238 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1239 |> group_by([user_rel, u], user_rel.relationship_type)
1241 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1246 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1250 def deactivate_async(user, status \\ true) do
1251 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1254 def deactivate(user, status \\ true)
1256 def deactivate(users, status) when is_list(users) do
1257 Repo.transaction(fn ->
1258 for user <- users, do: deactivate(user, status)
1262 def deactivate(%User{} = user, status) do
1263 with {:ok, user} <- set_activation_status(user, status) do
1266 |> Enum.filter(& &1.local)
1267 |> Enum.each(fn follower ->
1268 follower |> update_following_count() |> set_cache()
1271 # Only update local user counts, remote will be update during the next pull.
1274 |> Enum.filter(& &1.local)
1275 |> Enum.each(&update_follower_count/1)
1281 def update_notification_settings(%User{} = user, settings) do
1283 |> cast(%{notification_settings: settings}, [])
1284 |> cast_embed(:notification_settings)
1285 |> validate_required([:notification_settings])
1286 |> update_and_set_cache()
1289 def delete(users) when is_list(users) do
1290 for user <- users, do: delete(user)
1293 def delete(%User{} = user) do
1294 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1297 def perform(:force_password_reset, user), do: force_password_reset(user)
1299 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1300 def perform(:delete, %User{} = user) do
1301 {:ok, _user} = ActivityPub.delete(user)
1303 # Remove all relationships
1306 |> Enum.each(fn follower ->
1307 ActivityPub.unfollow(follower, user)
1308 unfollow(follower, user)
1313 |> Enum.each(fn followed ->
1314 ActivityPub.unfollow(user, followed)
1315 unfollow(user, followed)
1318 delete_user_activities(user)
1319 invalidate_cache(user)
1323 def perform(:fetch_initial_posts, %User{} = user) do
1324 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1326 # Insert all the posts in reverse order, so they're in the right order on the timeline
1327 user.source_data["outbox"]
1328 |> Utils.fetch_ordered_collection(pages)
1330 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1333 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1335 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1336 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1337 when is_list(blocked_identifiers) do
1339 blocked_identifiers,
1340 fn blocked_identifier ->
1341 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1342 {:ok, _user_block} <- block(blocker, blocked),
1343 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1347 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1354 def perform(:follow_import, %User{} = follower, followed_identifiers)
1355 when is_list(followed_identifiers) do
1357 followed_identifiers,
1358 fn followed_identifier ->
1359 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1360 {:ok, follower} <- maybe_direct_follow(follower, followed),
1361 {:ok, _} <- ActivityPub.follow(follower, followed) do
1365 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1372 @spec external_users_query() :: Ecto.Query.t()
1373 def external_users_query do
1381 @spec external_users(keyword()) :: [User.t()]
1382 def external_users(opts \\ []) do
1384 external_users_query()
1385 |> select([u], struct(u, [:id, :ap_id]))
1389 do: where(query, [u], u.id > ^opts[:max_id]),
1394 do: limit(query, ^opts[:limit]),
1400 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1401 BackgroundWorker.enqueue("blocks_import", %{
1402 "blocker_id" => blocker.id,
1403 "blocked_identifiers" => blocked_identifiers
1407 def follow_import(%User{} = follower, followed_identifiers)
1408 when is_list(followed_identifiers) do
1409 BackgroundWorker.enqueue("follow_import", %{
1410 "follower_id" => follower.id,
1411 "followed_identifiers" => followed_identifiers
1415 def delete_user_activities(%User{ap_id: ap_id}) do
1417 |> Activity.Queries.by_actor()
1418 |> RepoStreamer.chunk_stream(50)
1419 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1423 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1425 |> Object.normalize()
1426 |> ActivityPub.delete()
1429 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1430 object = Object.normalize(activity)
1433 |> get_cached_by_ap_id()
1434 |> ActivityPub.unlike(object)
1437 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1438 object = Object.normalize(activity)
1441 |> get_cached_by_ap_id()
1442 |> ActivityPub.unannounce(object)
1445 defp delete_activity(_activity), do: "Doing nothing"
1447 def html_filter_policy(%User{no_rich_text: true}) do
1448 Pleroma.HTML.Scrubber.TwitterText
1451 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1453 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1455 def get_or_fetch_by_ap_id(ap_id) do
1456 user = get_cached_by_ap_id(ap_id)
1458 if !is_nil(user) and !needs_update?(user) do
1461 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1462 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1464 resp = fetch_by_ap_id(ap_id)
1466 if should_fetch_initial do
1467 with {:ok, %User{} = user} <- resp do
1468 fetch_initial_posts(user)
1477 Creates an internal service actor by URI if missing.
1478 Optionally takes nickname for addressing.
1480 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1481 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1483 case get_cached_by_ap_id(uri) do
1485 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1486 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1490 %User{invisible: false} = user ->
1500 @spec set_invisible(User.t()) :: {:ok, User.t()}
1501 defp set_invisible(user) do
1503 |> change(%{invisible: true})
1504 |> update_and_set_cache()
1507 @spec create_service_actor(String.t(), String.t()) ::
1508 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1509 defp create_service_actor(uri, nickname) do
1515 follower_address: uri <> "/followers"
1518 |> unique_constraint(:nickname)
1524 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1527 |> :public_key.pem_decode()
1529 |> :public_key.pem_entry_decode()
1534 def public_key(_), do: {:error, "not found key"}
1536 def get_public_key_for_ap_id(ap_id) do
1537 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1538 {:ok, public_key} <- public_key(user) do
1545 defp blank?(""), do: nil
1546 defp blank?(n), do: n
1548 def insert_or_update_user(data) do
1550 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1551 |> remote_user_creation()
1552 |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
1556 def ap_enabled?(%User{local: true}), do: true
1557 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1558 def ap_enabled?(_), do: false
1560 @doc "Gets or fetch a user by uri or nickname."
1561 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1562 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1563 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1565 # wait a period of time and return newest version of the User structs
1566 # this is because we have synchronous follow APIs and need to simulate them
1567 # with an async handshake
1568 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1569 with %User{} = a <- get_cached_by_id(a.id),
1570 %User{} = b <- get_cached_by_id(b.id) do
1577 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1578 with :ok <- :timer.sleep(timeout),
1579 %User{} = a <- get_cached_by_id(a.id),
1580 %User{} = b <- get_cached_by_id(b.id) do
1587 def parse_bio(bio) when is_binary(bio) and bio != "" do
1589 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1593 def parse_bio(_), do: ""
1595 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1596 # TODO: get profile URLs other than user.ap_id
1597 profile_urls = [user.ap_id]
1600 |> CommonUtils.format_input("text/plain",
1601 mentions_format: :full,
1602 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1607 def parse_bio(_, _), do: ""
1609 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1610 Repo.transaction(fn ->
1611 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1615 def tag(nickname, tags) when is_binary(nickname),
1616 do: tag(get_by_nickname(nickname), tags)
1618 def tag(%User{} = user, tags),
1619 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1621 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1622 Repo.transaction(fn ->
1623 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1627 def untag(nickname, tags) when is_binary(nickname),
1628 do: untag(get_by_nickname(nickname), tags)
1630 def untag(%User{} = user, tags),
1631 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1633 defp update_tags(%User{} = user, new_tags) do
1634 {:ok, updated_user} =
1636 |> change(%{tags: new_tags})
1637 |> update_and_set_cache()
1642 defp normalize_tags(tags) do
1645 |> Enum.map(&String.downcase/1)
1648 defp local_nickname_regex do
1649 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1650 @extended_local_nickname_regex
1652 @strict_local_nickname_regex
1656 def local_nickname(nickname_or_mention) do
1659 |> String.split("@")
1663 def full_nickname(nickname_or_mention),
1664 do: String.trim_leading(nickname_or_mention, "@")
1666 def error_user(ap_id) do
1670 nickname: "erroruser@example.com",
1671 inserted_at: NaiveDateTime.utc_now()
1675 @spec all_superusers() :: [User.t()]
1676 def all_superusers do
1677 User.Query.build(%{super_users: true, local: true, deactivated: false})
1681 def showing_reblogs?(%User{} = user, %User{} = target) do
1682 not UserRelationship.reblog_mute_exists?(user, target)
1686 The function returns a query to get users with no activity for given interval of days.
1687 Inactive users are those who didn't read any notification, or had any activity where
1688 the user is the activity's actor, during `inactivity_threshold` days.
1689 Deactivated users will not appear in this list.
1693 iex> Pleroma.User.list_inactive_users()
1696 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1697 def list_inactive_users_query(inactivity_threshold \\ 7) do
1698 negative_inactivity_threshold = -inactivity_threshold
1699 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1700 # Subqueries are not supported in `where` clauses, join gets too complicated.
1701 has_read_notifications =
1702 from(n in Pleroma.Notification,
1703 where: n.seen == true,
1705 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1708 |> Pleroma.Repo.all()
1710 from(u in Pleroma.User,
1711 left_join: a in Pleroma.Activity,
1712 on: u.ap_id == a.actor,
1713 where: not is_nil(u.nickname),
1714 where: u.deactivated != ^true,
1715 where: u.id not in ^has_read_notifications,
1718 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1719 is_nil(max(a.inserted_at))
1724 Enable or disable email notifications for user
1728 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1729 Pleroma.User{email_notifications: %{"digest" => true}}
1731 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1732 Pleroma.User{email_notifications: %{"digest" => false}}
1734 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1735 {:ok, t()} | {:error, Ecto.Changeset.t()}
1736 def switch_email_notifications(user, type, status) do
1737 User.update_email_notifications(user, %{type => status})
1741 Set `last_digest_emailed_at` value for the user to current time
1743 @spec touch_last_digest_emailed_at(t()) :: t()
1744 def touch_last_digest_emailed_at(user) do
1745 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1747 {:ok, updated_user} =
1749 |> change(%{last_digest_emailed_at: now})
1750 |> update_and_set_cache()
1755 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1756 def toggle_confirmation(%User{} = user) do
1758 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1759 |> update_and_set_cache()
1762 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1763 def toggle_confirmation(users) do
1764 Enum.map(users, &toggle_confirmation/1)
1767 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1771 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1772 # use instance-default
1773 config = Pleroma.Config.get([:assets, :mascots])
1774 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1775 mascot = Keyword.get(config, default_mascot)
1778 "id" => "default-mascot",
1779 "url" => mascot[:url],
1780 "preview_url" => mascot[:url],
1782 "mime_type" => mascot[:mime_type]
1787 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1789 def ensure_keys_present(%User{} = user) do
1790 with {:ok, pem} <- Keys.generate_rsa_pem() do
1792 |> cast(%{keys: pem}, [:keys])
1793 |> validate_required([:keys])
1794 |> update_and_set_cache()
1798 def get_ap_ids_by_nicknames(nicknames) do
1800 where: u.nickname in ^nicknames,
1806 defdelegate search(query, opts \\ []), to: User.Search
1808 defp put_password_hash(
1809 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1811 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1814 defp put_password_hash(changeset), do: changeset
1816 def is_internal_user?(%User{nickname: nil}), do: true
1817 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1818 def is_internal_user?(_), do: false
1820 # A hack because user delete activities have a fake id for whatever reason
1821 # TODO: Get rid of this
1822 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1824 def get_delivered_users_by_object_id(object_id) do
1826 inner_join: delivery in assoc(u, :deliveries),
1827 where: delivery.object_id == ^object_id
1832 def change_email(user, email) do
1834 |> cast(%{email: email}, [:email])
1835 |> validate_required([:email])
1836 |> unique_constraint(:email)
1837 |> validate_format(:email, @email_regex)
1838 |> update_and_set_cache()
1841 # Internal function; public one is `deactivate/2`
1842 defp set_activation_status(user, deactivated) do
1844 |> cast(%{deactivated: deactivated}, [:deactivated])
1845 |> update_and_set_cache()
1848 def update_banner(user, banner) do
1850 |> cast(%{banner: banner}, [:banner])
1851 |> update_and_set_cache()
1854 def update_background(user, background) do
1856 |> cast(%{background: background}, [:background])
1857 |> update_and_set_cache()
1860 def update_source_data(user, source_data) do
1862 |> cast(%{source_data: source_data}, [:source_data])
1863 |> update_and_set_cache()
1866 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1869 moderator: is_moderator
1873 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1874 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1875 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1876 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1879 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1880 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1884 def fields(%{fields: nil}), do: []
1886 def fields(%{fields: fields}), do: fields
1888 def validate_fields(changeset, remote? \\ false) do
1889 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1890 limit = Pleroma.Config.get([:instance, limit_name], 0)
1893 |> validate_length(:fields, max: limit)
1894 |> validate_change(:fields, fn :fields, fields ->
1895 if Enum.all?(fields, &valid_field?/1) do
1903 defp valid_field?(%{"name" => name, "value" => value}) do
1904 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1905 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1907 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1908 String.length(value) <= value_limit
1911 defp valid_field?(_), do: false
1913 defp truncate_field(%{"name" => name, "value" => value}) do
1915 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1918 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1920 %{"name" => name, "value" => value}
1923 def admin_api_update(user, params) do
1930 |> update_and_set_cache()
1933 @doc "Signs user out of all applications"
1934 def global_sign_out(user) do
1935 OAuth.Authorization.delete_user_authorizations(user)
1936 OAuth.Token.delete_user_tokens(user)
1939 def mascot_update(user, url) do
1941 |> cast(%{mascot: url}, [:mascot])
1942 |> validate_required([:mascot])
1943 |> update_and_set_cache()
1946 def mastodon_settings_update(user, settings) do
1948 |> cast(%{settings: settings}, [:settings])
1949 |> validate_required([:settings])
1950 |> update_and_set_cache()
1953 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1954 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1956 if need_confirmation? do
1958 confirmation_pending: true,
1959 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1963 confirmation_pending: false,
1964 confirmation_token: nil
1968 cast(user, params, [:confirmation_pending, :confirmation_token])
1971 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1972 if id not in user.pinned_activities do
1973 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1974 params = %{pinned_activities: user.pinned_activities ++ [id]}
1977 |> cast(params, [:pinned_activities])
1978 |> validate_length(:pinned_activities,
1979 max: max_pinned_statuses,
1980 message: "You have already pinned the maximum number of statuses"
1985 |> update_and_set_cache()
1988 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1989 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1992 |> cast(params, [:pinned_activities])
1993 |> update_and_set_cache()
1996 def update_email_notifications(user, settings) do
1997 email_notifications =
1998 user.email_notifications
1999 |> Map.merge(settings)
2000 |> Map.take(["digest"])
2002 params = %{email_notifications: email_notifications}
2003 fields = [:email_notifications]
2006 |> cast(params, fields)
2007 |> validate_required(fields)
2008 |> update_and_set_cache()
2011 defp set_domain_blocks(user, domain_blocks) do
2012 params = %{domain_blocks: domain_blocks}
2015 |> cast(params, [:domain_blocks])
2016 |> validate_required([:domain_blocks])
2017 |> update_and_set_cache()
2020 def block_domain(user, domain_blocked) do
2021 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2024 def unblock_domain(user, domain_blocked) do
2025 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2028 @spec add_to_block(User.t(), User.t()) ::
2029 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2030 defp add_to_block(%User{} = user, %User{} = blocked) do
2031 UserRelationship.create_block(user, blocked)
2034 @spec add_to_block(User.t(), User.t()) ::
2035 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2036 defp remove_from_block(%User{} = user, %User{} = blocked) do
2037 UserRelationship.delete_block(user, blocked)
2040 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2041 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2042 {:ok, user_notification_mute} <-
2043 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2045 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2049 defp remove_from_mutes(user, %User{} = muted_user) do
2050 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2051 {:ok, user_notification_mute} <-
2052 UserRelationship.delete_notification_mute(user, muted_user) do
2053 {:ok, [user_mute, user_notification_mute]}
2057 def set_invisible(user, invisible) do
2058 params = %{invisible: invisible}
2061 |> cast(params, [:invisible])
2062 |> validate_required([:invisible])
2063 |> update_and_set_cache()