1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 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
15 alias Pleroma.Conversation.Participation
16 alias Pleroma.Delivery
17 alias Pleroma.FollowingRelationship
19 alias Pleroma.Notification
21 alias Pleroma.Registration
23 alias Pleroma.RepoStreamer
25 alias Pleroma.UserRelationship
27 alias Pleroma.Web.ActivityPub.ActivityPub
28 alias Pleroma.Web.ActivityPub.Utils
29 alias Pleroma.Web.CommonAPI
30 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
31 alias Pleroma.Web.OAuth
32 alias Pleroma.Web.RelMe
33 alias Pleroma.Workers.BackgroundWorker
37 @type t :: %__MODULE__{}
39 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
41 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
42 @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])?)*$/
44 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
45 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
47 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
48 @user_relationships_config [
49 block: [blocker_blocks: :blocked_users, blockee_blocks: :blocker_users],
50 mute: [muter_mutes: :muted_users, mutee_mutes: :muter_users],
52 reblog_muter_mutes: :reblog_muted_users,
53 reblog_mutee_mutes: :reblog_muter_users
56 notification_muter_mutes: :notification_muted_users,
57 notification_mutee_mutes: :notification_muter_users
63 field(:email, :string)
65 field(:nickname, :string)
66 field(:password_hash, :string)
67 field(:password, :string, virtual: true)
68 field(:password_confirmation, :string, virtual: true)
70 field(:ap_id, :string)
72 field(:local, :boolean, default: true)
73 field(:follower_address, :string)
74 field(:following_address, :string)
75 field(:search_rank, :float, virtual: true)
76 field(:search_type, :integer, virtual: true)
77 field(:tags, {:array, :string}, default: [])
78 field(:last_refreshed_at, :naive_datetime_usec)
79 field(:last_digest_emailed_at, :naive_datetime)
80 field(:banner, :map, default: %{})
81 field(:background, :map, default: %{})
82 field(:source_data, :map, default: %{})
83 field(:note_count, :integer, default: 0)
84 field(:follower_count, :integer, default: 0)
85 # Should be filled in only for remote users
86 field(:following_count, :integer, default: nil)
87 field(:locked, :boolean, default: false)
88 field(:confirmation_pending, :boolean, default: false)
89 field(:password_reset_pending, :boolean, default: false)
90 field(:confirmation_token, :string, default: nil)
91 field(:default_scope, :string, default: "public")
92 field(:domain_blocks, {:array, :string}, default: [])
93 field(:subscribers, {:array, :string}, default: [])
94 field(:deactivated, :boolean, default: false)
95 field(:no_rich_text, :boolean, default: false)
96 field(:ap_enabled, :boolean, default: false)
97 field(:is_moderator, :boolean, default: false)
98 field(:is_admin, :boolean, default: false)
99 field(:show_role, :boolean, default: true)
100 field(:settings, :map, default: nil)
101 field(:magic_key, :string, default: nil)
102 field(:uri, :string, default: nil)
103 field(:hide_followers_count, :boolean, default: false)
104 field(:hide_follows_count, :boolean, default: false)
105 field(:hide_followers, :boolean, default: false)
106 field(:hide_follows, :boolean, default: false)
107 field(:hide_favorites, :boolean, default: true)
108 field(:unread_conversation_count, :integer, default: 0)
109 field(:pinned_activities, {:array, :string}, default: [])
110 field(:email_notifications, :map, default: %{"digest" => false})
111 field(:mascot, :map, default: nil)
112 field(:emoji, {:array, :map}, default: [])
113 field(:pleroma_settings_store, :map, default: %{})
114 field(:fields, {:array, :map}, default: [])
115 field(:raw_fields, {:array, :map}, default: [])
116 field(:discoverable, :boolean, default: false)
117 field(:invisible, :boolean, default: false)
118 field(:skip_thread_containment, :boolean, default: false)
120 field(:notification_settings, :map,
124 "non_follows" => true,
125 "non_followers" => true
129 has_many(:notifications, Notification)
130 has_many(:registrations, Registration)
131 has_many(:deliveries, Delivery)
133 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
134 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
136 for {relationship_type,
138 {outgoing_relation, outgoing_relation_target},
139 {incoming_relation, incoming_relation_source}
140 ]} <- @user_relationships_config do
141 # Definitions of `has_many :blocker_blocks`, `has_many :muter_mutes` etc.
142 has_many(outgoing_relation, UserRelationship,
143 foreign_key: :source_id,
144 where: [relationship_type: relationship_type]
147 # Definitions of `has_many :blockee_blocks`, `has_many :mutee_mutes` etc.
148 has_many(incoming_relation, UserRelationship,
149 foreign_key: :target_id,
150 where: [relationship_type: relationship_type]
153 # Definitions of `has_many :blocked_users`, `has_many :muted_users` etc.
154 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
156 # Definitions of `has_many :blocker_users`, `has_many :muter_users` etc.
157 has_many(incoming_relation_source, through: [incoming_relation, :source])
160 field(:info, :map, default: %{})
162 # `:blocks` is deprecated (replaced with `blocked_users` relation)
163 field(:blocks, {:array, :string}, default: [])
164 # `:mutes` is deprecated (replaced with `muted_users` relation)
165 field(:mutes, {:array, :string}, default: [])
166 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
167 field(:muted_reblogs, {:array, :string}, default: [])
168 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
169 field(:muted_notifications, {:array, :string}, default: [])
174 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
175 @user_relationships_config do
176 # Definitions of `blocked_users_relation/1`, `muted_users_relation/1`, etc.
177 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
178 target_users_query = assoc(user, unquote(outgoing_relation_target))
180 if restrict_deactivated? do
181 restrict_deactivated(target_users_query)
187 # Definitions of `blocked_users/1`, `muted_users/1`, etc.
188 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
190 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
192 restrict_deactivated?
197 # Definitions of `blocked_users_ap_ids/1`, `muted_users_ap_ids/1`, etc.
198 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
200 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
202 restrict_deactivated?
204 |> select([u], u.ap_id)
209 @doc "Returns if the user should be allowed to authenticate"
210 def auth_active?(%User{deactivated: true}), do: false
212 def auth_active?(%User{confirmation_pending: true}),
213 do: !Pleroma.Config.get([:instance, :account_activation_required])
215 def auth_active?(%User{}), do: true
217 def visible_for?(user, for_user \\ nil)
219 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
221 def visible_for?(%User{} = user, for_user) do
222 auth_active?(user) || superuser?(for_user)
225 def visible_for?(_, _), do: false
227 def superuser?(%User{local: true, is_admin: true}), do: true
228 def superuser?(%User{local: true, is_moderator: true}), do: true
229 def superuser?(_), do: false
231 def invisible?(%User{invisible: true}), do: true
232 def invisible?(_), do: false
234 def avatar_url(user, options \\ []) do
236 %{"url" => [%{"href" => href} | _]} -> href
237 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
241 def banner_url(user, options \\ []) do
243 %{"url" => [%{"href" => href} | _]} -> href
244 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
248 def profile_url(%User{source_data: %{"url" => url}}), do: url
249 def profile_url(%User{ap_id: ap_id}), do: ap_id
250 def profile_url(_), do: nil
252 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
254 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
255 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
257 @spec ap_following(User.t()) :: Sring.t()
258 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
259 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
261 def user_info(%User{} = user, args \\ %{}) do
263 Map.get(args, :following_count, user.following_count || following_count(user))
265 follower_count = Map.get(args, :follower_count, user.follower_count)
268 note_count: user.note_count,
270 confirmation_pending: user.confirmation_pending,
271 default_scope: user.default_scope
273 |> Map.put(:following_count, following_count)
274 |> Map.put(:follower_count, follower_count)
277 def follow_state(%User{} = user, %User{} = target) do
278 case Utils.fetch_latest_follow(user, target) do
279 %{data: %{"state" => state}} -> state
280 # Ideally this would be nil, but then Cachex does not commit the value
285 def get_cached_follow_state(user, target) do
286 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
287 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
290 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
291 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
292 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
295 def set_info_cache(user, args) do
296 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
299 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
300 def restrict_deactivated(query) do
301 from(u in query, where: u.deactivated != ^true)
304 defdelegate following_count(user), to: FollowingRelationship
306 defp truncate_fields_param(params) do
307 if Map.has_key?(params, :fields) do
308 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
314 defp truncate_if_exists(params, key, max_length) do
315 if Map.has_key?(params, key) and is_binary(params[key]) do
316 {value, _chopped} = String.split_at(params[key], max_length)
317 Map.put(params, key, value)
323 def remote_user_creation(params) do
324 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
325 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
329 |> Map.put(:info, params[:info] || %{})
330 |> truncate_if_exists(:name, name_limit)
331 |> truncate_if_exists(:bio, bio_limit)
332 |> truncate_fields_param()
352 :hide_followers_count,
361 |> validate_required([:name, :ap_id])
362 |> unique_constraint(:nickname)
363 |> validate_format(:nickname, @email_regex)
364 |> validate_length(:bio, max: bio_limit)
365 |> validate_length(:name, max: name_limit)
366 |> validate_fields(true)
368 case params[:source_data] do
369 %{"followers" => followers, "following" => following} ->
371 |> put_change(:follower_address, followers)
372 |> put_change(:following_address, following)
375 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
376 put_change(changeset, :follower_address, followers)
380 def update_changeset(struct, params \\ %{}) do
381 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
382 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
397 :hide_followers_count,
402 :skip_thread_containment,
405 :pleroma_settings_store,
409 |> unique_constraint(:nickname)
410 |> validate_format(:nickname, local_nickname_regex())
411 |> validate_length(:bio, max: bio_limit)
412 |> validate_length(:name, min: 1, max: name_limit)
413 |> validate_fields(false)
416 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
417 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
418 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
420 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
422 params = if remote?, do: truncate_fields_param(params), else: params
445 :hide_followers_count,
449 |> unique_constraint(:nickname)
450 |> validate_format(:nickname, local_nickname_regex())
451 |> validate_length(:bio, max: bio_limit)
452 |> validate_length(:name, max: name_limit)
453 |> validate_fields(remote?)
456 def password_update_changeset(struct, params) do
458 |> cast(params, [:password, :password_confirmation])
459 |> validate_required([:password, :password_confirmation])
460 |> validate_confirmation(:password)
461 |> put_password_hash()
462 |> put_change(:password_reset_pending, false)
465 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
466 def reset_password(%User{id: user_id} = user, data) do
469 |> Multi.update(:user, password_update_changeset(user, data))
470 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
471 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
473 case Repo.transaction(multi) do
474 {:ok, %{user: user} = _} -> set_cache(user)
475 {:error, _, changeset, _} -> {:error, changeset}
479 def update_password_reset_pending(user, value) do
482 |> put_change(:password_reset_pending, value)
483 |> update_and_set_cache()
486 def force_password_reset_async(user) do
487 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
490 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
491 def force_password_reset(user), do: update_password_reset_pending(user, true)
493 def register_changeset(struct, params \\ %{}, opts \\ []) do
494 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
495 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
498 if is_nil(opts[:need_confirmation]) do
499 Pleroma.Config.get([:instance, :account_activation_required])
501 opts[:need_confirmation]
505 |> confirmation_changeset(need_confirmation: need_confirmation?)
506 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
507 |> validate_required([:name, :nickname, :password, :password_confirmation])
508 |> validate_confirmation(:password)
509 |> unique_constraint(:email)
510 |> unique_constraint(:nickname)
511 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
512 |> validate_format(:nickname, local_nickname_regex())
513 |> validate_format(:email, @email_regex)
514 |> validate_length(:bio, max: bio_limit)
515 |> validate_length(:name, min: 1, max: name_limit)
516 |> maybe_validate_required_email(opts[:external])
519 |> unique_constraint(:ap_id)
520 |> put_following_and_follower_address()
523 def maybe_validate_required_email(changeset, true), do: changeset
524 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
526 defp put_ap_id(changeset) do
527 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
528 put_change(changeset, :ap_id, ap_id)
531 defp put_following_and_follower_address(changeset) do
532 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
535 |> put_change(:follower_address, followers)
538 defp autofollow_users(user) do
539 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
542 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
545 follow_all(user, autofollowed_users)
548 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
549 def register(%Ecto.Changeset{} = changeset) do
550 with {:ok, user} <- Repo.insert(changeset) do
551 post_register_action(user)
555 def post_register_action(%User{} = user) do
556 with {:ok, user} <- autofollow_users(user),
557 {:ok, user} <- set_cache(user),
558 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
559 {:ok, _} <- try_send_confirmation_email(user) do
564 def try_send_confirmation_email(%User{} = user) do
565 if user.confirmation_pending &&
566 Pleroma.Config.get([:instance, :account_activation_required]) do
568 |> Pleroma.Emails.UserEmail.account_confirmation_email()
569 |> Pleroma.Emails.Mailer.deliver_async()
577 def needs_update?(%User{local: true}), do: false
579 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
581 def needs_update?(%User{local: false} = user) do
582 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
585 def needs_update?(_), do: true
587 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
588 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
589 follow(follower, followed, "pending")
592 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
593 follow(follower, followed)
596 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
597 if not ap_enabled?(followed) do
598 follow(follower, followed)
604 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
605 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
606 def follow_all(follower, followeds) do
608 Enum.reject(followeds, fn followed ->
609 blocks?(follower, followed) || blocks?(followed, follower)
612 Enum.each(followeds, &follow(follower, &1, "accept"))
614 Enum.each(followeds, &update_follower_count/1)
619 defdelegate following(user), to: FollowingRelationship
621 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
622 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
625 followed.deactivated ->
626 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
628 deny_follow_blocked and blocks?(followed, follower) ->
629 {:error, "Could not follow user: #{followed.nickname} blocked you."}
632 FollowingRelationship.follow(follower, followed, state)
634 follower = maybe_update_following_count(follower)
636 {:ok, _} = update_follower_count(followed)
642 def unfollow(%User{} = follower, %User{} = followed) do
643 if following?(follower, followed) and follower.ap_id != followed.ap_id do
644 FollowingRelationship.unfollow(follower, followed)
646 follower = maybe_update_following_count(follower)
648 {:ok, followed} = update_follower_count(followed)
652 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
654 {:error, "Not subscribed!"}
658 defdelegate following?(follower, followed), to: FollowingRelationship
660 def locked?(%User{} = user) do
665 Repo.get_by(User, id: id)
668 def get_by_ap_id(ap_id) do
669 Repo.get_by(User, ap_id: ap_id)
672 def get_all_by_ap_id(ap_ids) do
673 from(u in __MODULE__,
674 where: u.ap_id in ^ap_ids
679 def get_all_by_ids(ids) do
680 from(u in __MODULE__, where: u.id in ^ids)
684 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
685 # of the ap_id and the domain and tries to get that user
686 def get_by_guessed_nickname(ap_id) do
687 domain = URI.parse(ap_id).host
688 name = List.last(String.split(ap_id, "/"))
689 nickname = "#{name}@#{domain}"
691 get_cached_by_nickname(nickname)
694 def set_cache({:ok, user}), do: set_cache(user)
695 def set_cache({:error, err}), do: {:error, err}
697 def set_cache(%User{} = user) do
698 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
699 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
700 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
704 def update_and_set_cache(struct, params) do
706 |> update_changeset(params)
707 |> update_and_set_cache()
710 def update_and_set_cache(changeset) do
711 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
716 def invalidate_cache(user) do
717 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
718 Cachex.del(:user_cache, "nickname:#{user.nickname}")
719 Cachex.del(:user_cache, "user_info:#{user.id}")
722 def get_cached_by_ap_id(ap_id) do
723 key = "ap_id:#{ap_id}"
724 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
727 def get_cached_by_id(id) do
731 Cachex.fetch!(:user_cache, key, fn _ ->
735 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
736 {:commit, user.ap_id}
742 get_cached_by_ap_id(ap_id)
745 def get_cached_by_nickname(nickname) do
746 key = "nickname:#{nickname}"
748 Cachex.fetch!(:user_cache, key, fn ->
749 case get_or_fetch_by_nickname(nickname) do
750 {:ok, user} -> {:commit, user}
751 {:error, _error} -> {:ignore, nil}
756 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
757 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
760 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
761 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
763 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
764 get_cached_by_nickname(nickname_or_id)
766 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
767 get_cached_by_nickname(nickname_or_id)
774 def get_by_nickname(nickname) do
775 Repo.get_by(User, nickname: nickname) ||
776 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
777 Repo.get_by(User, nickname: local_nickname(nickname))
781 def get_by_email(email), do: Repo.get_by(User, email: email)
783 def get_by_nickname_or_email(nickname_or_email) do
784 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
787 def get_cached_user_info(user) do
788 key = "user_info:#{user.id}"
789 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
792 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
794 def get_or_fetch_by_nickname(nickname) do
795 with %User{} = user <- get_by_nickname(nickname) do
799 with [_nick, _domain] <- String.split(nickname, "@"),
800 {:ok, user} <- fetch_by_nickname(nickname) do
801 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
802 fetch_initial_posts(user)
807 _e -> {:error, "not found " <> nickname}
812 @doc "Fetch some posts when the user has just been federated with"
813 def fetch_initial_posts(user) do
814 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
817 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
818 def get_followers_query(%User{} = user, nil) do
819 User.Query.build(%{followers: user, deactivated: false})
822 def get_followers_query(user, page) do
824 |> get_followers_query(nil)
825 |> User.Query.paginate(page, 20)
828 @spec get_followers_query(User.t()) :: Ecto.Query.t()
829 def get_followers_query(user), do: get_followers_query(user, nil)
831 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
832 def get_followers(user, page \\ nil) do
834 |> get_followers_query(page)
838 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
839 def get_external_followers(user, page \\ nil) do
841 |> get_followers_query(page)
842 |> User.Query.build(%{external: true})
846 def get_followers_ids(user, page \\ nil) do
848 |> get_followers_query(page)
853 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
854 def get_friends_query(%User{} = user, nil) do
855 User.Query.build(%{friends: user, deactivated: false})
858 def get_friends_query(user, page) do
860 |> get_friends_query(nil)
861 |> User.Query.paginate(page, 20)
864 @spec get_friends_query(User.t()) :: Ecto.Query.t()
865 def get_friends_query(user), do: get_friends_query(user, nil)
867 def get_friends(user, page \\ nil) do
869 |> get_friends_query(page)
873 def get_friends_ids(user, page \\ nil) do
875 |> get_friends_query(page)
880 defdelegate get_follow_requests(user), to: FollowingRelationship
882 def increase_note_count(%User{} = user) do
884 |> where(id: ^user.id)
885 |> update([u], inc: [note_count: 1])
887 |> Repo.update_all([])
889 {1, [user]} -> set_cache(user)
894 def decrease_note_count(%User{} = user) do
896 |> where(id: ^user.id)
899 note_count: fragment("greatest(0, note_count - 1)")
903 |> Repo.update_all([])
905 {1, [user]} -> set_cache(user)
910 def update_note_count(%User{} = user, note_count \\ nil) do
915 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
921 |> cast(%{note_count: note_count}, [:note_count])
922 |> update_and_set_cache()
925 @spec maybe_fetch_follow_information(User.t()) :: User.t()
926 def maybe_fetch_follow_information(user) do
927 with {:ok, user} <- fetch_follow_information(user) do
931 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
937 def fetch_follow_information(user) do
938 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
940 |> follow_information_changeset(info)
941 |> update_and_set_cache()
945 defp follow_information_changeset(user, params) do
952 :hide_followers_count,
957 def update_follower_count(%User{} = user) do
958 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
959 follower_count_query =
960 User.Query.build(%{followers: user, deactivated: false})
961 |> select([u], %{count: count(u.id)})
964 |> where(id: ^user.id)
965 |> join(:inner, [u], s in subquery(follower_count_query))
967 set: [follower_count: s.count]
970 |> Repo.update_all([])
972 {1, [user]} -> set_cache(user)
976 {:ok, maybe_fetch_follow_information(user)}
980 @spec maybe_update_following_count(User.t()) :: User.t()
981 def maybe_update_following_count(%User{local: false} = user) do
982 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
983 maybe_fetch_follow_information(user)
989 def maybe_update_following_count(user), do: user
991 def set_unread_conversation_count(%User{local: true} = user) do
992 unread_query = Participation.unread_conversation_count_for_user(user)
995 |> join(:inner, [u], p in subquery(unread_query))
997 set: [unread_conversation_count: p.count]
999 |> where([u], u.id == ^user.id)
1001 |> Repo.update_all([])
1003 {1, [user]} -> set_cache(user)
1008 def set_unread_conversation_count(user), do: {:ok, user}
1010 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1012 Participation.unread_conversation_count_for_user(user)
1013 |> where([p], p.conversation_id == ^conversation.id)
1016 |> join(:inner, [u], p in subquery(unread_query))
1018 inc: [unread_conversation_count: 1]
1020 |> where([u], u.id == ^user.id)
1021 |> where([u, p], p.count == 0)
1023 |> Repo.update_all([])
1025 {1, [user]} -> set_cache(user)
1030 def increment_unread_conversation_count(_, user), do: {:ok, user}
1032 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1033 def get_users_from_set(ap_ids, local_only \\ true) do
1034 criteria = %{ap_id: ap_ids, deactivated: false}
1035 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1037 User.Query.build(criteria)
1041 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1042 def get_recipients_from_activity(%Activity{recipients: to}) do
1043 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1047 @spec mute(User.t(), User.t(), boolean()) ::
1048 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1049 def mute(muter, %User{} = mutee, notifications? \\ true) do
1050 add_to_mutes(muter, mutee, notifications?)
1053 def unmute(muter, %User{} = mutee) do
1054 remove_from_mutes(muter, mutee)
1057 def subscribe(subscriber, %{ap_id: ap_id}) do
1058 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
1059 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1061 if blocks?(subscribed, subscriber) and deny_follow_blocked do
1062 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
1064 User.add_to_subscribers(subscribed, subscriber.ap_id)
1069 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
1070 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1071 User.remove_from_subscribers(user, unsubscriber.ap_id)
1075 def block(blocker, %User{} = blocked) do
1076 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1078 if following?(blocker, blocked) do
1079 {:ok, blocker, _} = unfollow(blocker, blocked)
1085 # clear any requested follows as well
1087 case CommonAPI.reject_follow_request(blocked, blocker) do
1088 {:ok, %User{} = updated_blocked} -> updated_blocked
1093 if subscribed_to?(blocked, blocker) do
1094 {:ok, blocker} = unsubscribe(blocked, blocker)
1100 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1102 {:ok, blocker} = update_follower_count(blocker)
1103 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1104 add_to_block(blocker, blocked)
1107 # helper to handle the block given only an actor's AP id
1108 def block(blocker, %{ap_id: ap_id}) do
1109 block(blocker, get_cached_by_ap_id(ap_id))
1112 def unblock(blocker, %User{} = blocked) do
1113 remove_from_block(blocker, blocked)
1116 # helper to handle the block given only an actor's AP id
1117 def unblock(blocker, %{ap_id: ap_id}) do
1118 unblock(blocker, get_cached_by_ap_id(ap_id))
1121 def mutes?(nil, _), do: false
1122 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1124 def mutes_user?(%User{} = user, %User{} = target) do
1125 UserRelationship.mute_exists?(user, target)
1128 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1129 def muted_notifications?(nil, _), do: false
1131 def muted_notifications?(user, %User{} = target),
1132 do: UserRelationship.notification_mute_exists?(user, target)
1134 def blocks?(nil, _), do: false
1136 def blocks?(%User{} = user, %User{} = target) do
1137 blocks_user?(user, target) || blocks_domain?(user, target)
1140 def blocks_user?(%User{} = user, %User{} = target) do
1141 UserRelationship.block_exists?(user, target)
1144 def blocks_user?(_, _), do: false
1146 def blocks_domain?(%User{} = user, %User{} = target) do
1147 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1148 %{host: host} = URI.parse(target.ap_id)
1149 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1152 def blocks_domain?(_, _), do: false
1154 def subscribed_to?(user, %{ap_id: ap_id}) do
1155 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1156 Enum.member?(target.subscribers, user.ap_id)
1161 Returns map of related AP IDs list by relation type.
1162 E.g. `related_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1164 @spec related_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1165 def related_ap_ids(%User{} = user, relationship_types) when is_list(relationship_types) do
1168 |> assoc(:outgoing_relationships)
1169 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1170 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1171 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1172 |> group_by([user_rel, u], user_rel.relationship_type)
1174 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1179 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1183 @spec subscribers(User.t()) :: [User.t()]
1184 def subscribers(user) do
1185 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1189 def deactivate_async(user, status \\ true) do
1190 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1193 def deactivate(user, status \\ true)
1195 def deactivate(users, status) when is_list(users) do
1196 Repo.transaction(fn ->
1197 for user <- users, do: deactivate(user, status)
1201 def deactivate(%User{} = user, status) do
1202 with {:ok, user} <- set_activation_status(user, status) do
1203 Enum.each(get_followers(user), &invalidate_cache/1)
1205 # Only update local user counts, remote will be update during the next pull.
1208 |> Enum.filter(& &1.local)
1209 |> Enum.each(&update_follower_count/1)
1215 def update_notification_settings(%User{} = user, settings) do
1218 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1221 notification_settings =
1222 user.notification_settings
1223 |> Map.merge(settings)
1224 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1226 params = %{notification_settings: notification_settings}
1229 |> cast(params, [:notification_settings])
1230 |> validate_required([:notification_settings])
1231 |> update_and_set_cache()
1234 def delete(users) when is_list(users) do
1235 for user <- users, do: delete(user)
1238 def delete(%User{} = user) do
1239 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1242 def perform(:force_password_reset, user), do: force_password_reset(user)
1244 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1245 def perform(:delete, %User{} = user) do
1246 {:ok, _user} = ActivityPub.delete(user)
1248 # Remove all relationships
1251 |> Enum.each(fn follower ->
1252 ActivityPub.unfollow(follower, user)
1253 unfollow(follower, user)
1258 |> Enum.each(fn followed ->
1259 ActivityPub.unfollow(user, followed)
1260 unfollow(user, followed)
1263 delete_user_activities(user)
1264 invalidate_cache(user)
1268 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1269 def perform(:fetch_initial_posts, %User{} = user) do
1270 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1272 # Insert all the posts in reverse order, so they're in the right order on the timeline
1273 user.source_data["outbox"]
1274 |> Utils.fetch_ordered_collection(pages)
1276 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1279 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1281 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1282 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1283 when is_list(blocked_identifiers) do
1285 blocked_identifiers,
1286 fn blocked_identifier ->
1287 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1288 {:ok, _user_block} <- block(blocker, blocked),
1289 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1293 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1300 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1301 def perform(:follow_import, %User{} = follower, followed_identifiers)
1302 when is_list(followed_identifiers) do
1304 followed_identifiers,
1305 fn followed_identifier ->
1306 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1307 {:ok, follower} <- maybe_direct_follow(follower, followed),
1308 {:ok, _} <- ActivityPub.follow(follower, followed) do
1312 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1319 @spec external_users_query() :: Ecto.Query.t()
1320 def external_users_query do
1328 @spec external_users(keyword()) :: [User.t()]
1329 def external_users(opts \\ []) do
1331 external_users_query()
1332 |> select([u], struct(u, [:id, :ap_id, :info]))
1336 do: where(query, [u], u.id > ^opts[:max_id]),
1341 do: limit(query, ^opts[:limit]),
1347 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1348 BackgroundWorker.enqueue("blocks_import", %{
1349 "blocker_id" => blocker.id,
1350 "blocked_identifiers" => blocked_identifiers
1354 def follow_import(%User{} = follower, followed_identifiers)
1355 when is_list(followed_identifiers) do
1356 BackgroundWorker.enqueue("follow_import", %{
1357 "follower_id" => follower.id,
1358 "followed_identifiers" => followed_identifiers
1362 def delete_user_activities(%User{ap_id: ap_id}) do
1364 |> Activity.Queries.by_actor()
1365 |> RepoStreamer.chunk_stream(50)
1366 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1370 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1372 |> Object.normalize()
1373 |> ActivityPub.delete()
1376 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1377 object = Object.normalize(activity)
1380 |> get_cached_by_ap_id()
1381 |> ActivityPub.unlike(object)
1384 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1385 object = Object.normalize(activity)
1388 |> get_cached_by_ap_id()
1389 |> ActivityPub.unannounce(object)
1392 defp delete_activity(_activity), do: "Doing nothing"
1394 def html_filter_policy(%User{no_rich_text: true}) do
1395 Pleroma.HTML.Scrubber.TwitterText
1398 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1400 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1402 def get_or_fetch_by_ap_id(ap_id) do
1403 user = get_cached_by_ap_id(ap_id)
1405 if !is_nil(user) and !needs_update?(user) do
1408 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1409 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1411 resp = fetch_by_ap_id(ap_id)
1413 if should_fetch_initial do
1414 with {:ok, %User{} = user} <- resp do
1415 fetch_initial_posts(user)
1423 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1424 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1425 with %User{} = user <- get_cached_by_ap_id(uri) do
1431 |> cast(%{}, [:ap_id, :nickname, :local])
1432 |> put_change(:ap_id, uri)
1433 |> put_change(:nickname, nickname)
1434 |> put_change(:local, true)
1435 |> put_change(:follower_address, uri <> "/followers")
1443 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1446 |> :public_key.pem_decode()
1448 |> :public_key.pem_entry_decode()
1453 def public_key(_), do: {:error, "not found key"}
1455 def get_public_key_for_ap_id(ap_id) do
1456 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1457 {:ok, public_key} <- public_key(user) do
1464 defp blank?(""), do: nil
1465 defp blank?(n), do: n
1467 def insert_or_update_user(data) do
1469 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1470 |> remote_user_creation()
1471 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1475 def ap_enabled?(%User{local: true}), do: true
1476 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1477 def ap_enabled?(_), do: false
1479 @doc "Gets or fetch a user by uri or nickname."
1480 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1481 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1482 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1484 # wait a period of time and return newest version of the User structs
1485 # this is because we have synchronous follow APIs and need to simulate them
1486 # with an async handshake
1487 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1488 with %User{} = a <- get_cached_by_id(a.id),
1489 %User{} = b <- get_cached_by_id(b.id) do
1496 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1497 with :ok <- :timer.sleep(timeout),
1498 %User{} = a <- get_cached_by_id(a.id),
1499 %User{} = b <- get_cached_by_id(b.id) do
1506 def parse_bio(bio) when is_binary(bio) and bio != "" do
1508 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1512 def parse_bio(_), do: ""
1514 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1515 # TODO: get profile URLs other than user.ap_id
1516 profile_urls = [user.ap_id]
1519 |> CommonUtils.format_input("text/plain",
1520 mentions_format: :full,
1521 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1526 def parse_bio(_, _), do: ""
1528 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1529 Repo.transaction(fn ->
1530 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1534 def tag(nickname, tags) when is_binary(nickname),
1535 do: tag(get_by_nickname(nickname), tags)
1537 def tag(%User{} = user, tags),
1538 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1540 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1541 Repo.transaction(fn ->
1542 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1546 def untag(nickname, tags) when is_binary(nickname),
1547 do: untag(get_by_nickname(nickname), tags)
1549 def untag(%User{} = user, tags),
1550 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1552 defp update_tags(%User{} = user, new_tags) do
1553 {:ok, updated_user} =
1555 |> change(%{tags: new_tags})
1556 |> update_and_set_cache()
1561 defp normalize_tags(tags) do
1564 |> Enum.map(&String.downcase/1)
1567 defp local_nickname_regex do
1568 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1569 @extended_local_nickname_regex
1571 @strict_local_nickname_regex
1575 def local_nickname(nickname_or_mention) do
1578 |> String.split("@")
1582 def full_nickname(nickname_or_mention),
1583 do: String.trim_leading(nickname_or_mention, "@")
1585 def error_user(ap_id) do
1589 nickname: "erroruser@example.com",
1590 inserted_at: NaiveDateTime.utc_now()
1594 @spec all_superusers() :: [User.t()]
1595 def all_superusers do
1596 User.Query.build(%{super_users: true, local: true, deactivated: false})
1600 def showing_reblogs?(%User{} = user, %User{} = target) do
1601 not UserRelationship.reblog_mute_exists?(user, target)
1605 The function returns a query to get users with no activity for given interval of days.
1606 Inactive users are those who didn't read any notification, or had any activity where
1607 the user is the activity's actor, during `inactivity_threshold` days.
1608 Deactivated users will not appear in this list.
1612 iex> Pleroma.User.list_inactive_users()
1615 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1616 def list_inactive_users_query(inactivity_threshold \\ 7) do
1617 negative_inactivity_threshold = -inactivity_threshold
1618 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1619 # Subqueries are not supported in `where` clauses, join gets too complicated.
1620 has_read_notifications =
1621 from(n in Pleroma.Notification,
1622 where: n.seen == true,
1624 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1627 |> Pleroma.Repo.all()
1629 from(u in Pleroma.User,
1630 left_join: a in Pleroma.Activity,
1631 on: u.ap_id == a.actor,
1632 where: not is_nil(u.nickname),
1633 where: u.deactivated != ^true,
1634 where: u.id not in ^has_read_notifications,
1637 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1638 is_nil(max(a.inserted_at))
1643 Enable or disable email notifications for user
1647 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1648 Pleroma.User{email_notifications: %{"digest" => true}}
1650 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1651 Pleroma.User{email_notifications: %{"digest" => false}}
1653 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1654 {:ok, t()} | {:error, Ecto.Changeset.t()}
1655 def switch_email_notifications(user, type, status) do
1656 User.update_email_notifications(user, %{type => status})
1660 Set `last_digest_emailed_at` value for the user to current time
1662 @spec touch_last_digest_emailed_at(t()) :: t()
1663 def touch_last_digest_emailed_at(user) do
1664 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1666 {:ok, updated_user} =
1668 |> change(%{last_digest_emailed_at: now})
1669 |> update_and_set_cache()
1674 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1675 def toggle_confirmation(%User{} = user) do
1677 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1678 |> update_and_set_cache()
1681 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1685 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1686 # use instance-default
1687 config = Pleroma.Config.get([:assets, :mascots])
1688 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1689 mascot = Keyword.get(config, default_mascot)
1692 "id" => "default-mascot",
1693 "url" => mascot[:url],
1694 "preview_url" => mascot[:url],
1696 "mime_type" => mascot[:mime_type]
1701 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1703 def ensure_keys_present(%User{} = user) do
1704 with {:ok, pem} <- Keys.generate_rsa_pem() do
1706 |> cast(%{keys: pem}, [:keys])
1707 |> validate_required([:keys])
1708 |> update_and_set_cache()
1712 def get_ap_ids_by_nicknames(nicknames) do
1714 where: u.nickname in ^nicknames,
1720 defdelegate search(query, opts \\ []), to: User.Search
1722 defp put_password_hash(
1723 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1725 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1728 defp put_password_hash(changeset), do: changeset
1730 def is_internal_user?(%User{nickname: nil}), do: true
1731 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1732 def is_internal_user?(_), do: false
1734 # A hack because user delete activities have a fake id for whatever reason
1735 # TODO: Get rid of this
1736 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1738 def get_delivered_users_by_object_id(object_id) do
1740 inner_join: delivery in assoc(u, :deliveries),
1741 where: delivery.object_id == ^object_id
1746 def change_email(user, email) do
1748 |> cast(%{email: email}, [:email])
1749 |> validate_required([:email])
1750 |> unique_constraint(:email)
1751 |> validate_format(:email, @email_regex)
1752 |> update_and_set_cache()
1755 # Internal function; public one is `deactivate/2`
1756 defp set_activation_status(user, deactivated) do
1758 |> cast(%{deactivated: deactivated}, [:deactivated])
1759 |> update_and_set_cache()
1762 def update_banner(user, banner) do
1764 |> cast(%{banner: banner}, [:banner])
1765 |> update_and_set_cache()
1768 def update_background(user, background) do
1770 |> cast(%{background: background}, [:background])
1771 |> update_and_set_cache()
1774 def update_source_data(user, source_data) do
1776 |> cast(%{source_data: source_data}, [:source_data])
1777 |> update_and_set_cache()
1780 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1783 moderator: is_moderator
1787 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1788 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1789 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1790 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1793 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1794 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1798 def fields(%{fields: nil}), do: []
1800 def fields(%{fields: fields}), do: fields
1802 def validate_fields(changeset, remote? \\ false) do
1803 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1804 limit = Pleroma.Config.get([:instance, limit_name], 0)
1807 |> validate_length(:fields, max: limit)
1808 |> validate_change(:fields, fn :fields, fields ->
1809 if Enum.all?(fields, &valid_field?/1) do
1817 defp valid_field?(%{"name" => name, "value" => value}) do
1818 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1819 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1821 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1822 String.length(value) <= value_limit
1825 defp valid_field?(_), do: false
1827 defp truncate_field(%{"name" => name, "value" => value}) do
1829 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1832 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1834 %{"name" => name, "value" => value}
1837 def admin_api_update(user, params) do
1844 |> update_and_set_cache()
1847 def mascot_update(user, url) do
1849 |> cast(%{mascot: url}, [:mascot])
1850 |> validate_required([:mascot])
1851 |> update_and_set_cache()
1854 def mastodon_settings_update(user, settings) do
1856 |> cast(%{settings: settings}, [:settings])
1857 |> validate_required([:settings])
1858 |> update_and_set_cache()
1861 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1862 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1864 if need_confirmation? do
1866 confirmation_pending: true,
1867 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1871 confirmation_pending: false,
1872 confirmation_token: nil
1876 cast(user, params, [:confirmation_pending, :confirmation_token])
1879 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1880 if id not in user.pinned_activities do
1881 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1882 params = %{pinned_activities: user.pinned_activities ++ [id]}
1885 |> cast(params, [:pinned_activities])
1886 |> validate_length(:pinned_activities,
1887 max: max_pinned_statuses,
1888 message: "You have already pinned the maximum number of statuses"
1893 |> update_and_set_cache()
1896 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1897 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1900 |> cast(params, [:pinned_activities])
1901 |> update_and_set_cache()
1904 def update_email_notifications(user, settings) do
1905 email_notifications =
1906 user.email_notifications
1907 |> Map.merge(settings)
1908 |> Map.take(["digest"])
1910 params = %{email_notifications: email_notifications}
1911 fields = [:email_notifications]
1914 |> cast(params, fields)
1915 |> validate_required(fields)
1916 |> update_and_set_cache()
1919 defp set_subscribers(user, subscribers) do
1920 params = %{subscribers: subscribers}
1923 |> cast(params, [:subscribers])
1924 |> validate_required([:subscribers])
1925 |> update_and_set_cache()
1928 def add_to_subscribers(user, subscribed) do
1929 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1932 def remove_from_subscribers(user, subscribed) do
1933 set_subscribers(user, List.delete(user.subscribers, subscribed))
1936 defp set_domain_blocks(user, domain_blocks) do
1937 params = %{domain_blocks: domain_blocks}
1940 |> cast(params, [:domain_blocks])
1941 |> validate_required([:domain_blocks])
1942 |> update_and_set_cache()
1945 def block_domain(user, domain_blocked) do
1946 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1949 def unblock_domain(user, domain_blocked) do
1950 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1953 @spec add_to_block(User.t(), User.t()) ::
1954 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
1955 defp add_to_block(%User{} = user, %User{} = blocked) do
1956 UserRelationship.create_block(user, blocked)
1959 @spec add_to_block(User.t(), User.t()) ::
1960 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
1961 defp remove_from_block(%User{} = user, %User{} = blocked) do
1962 UserRelationship.delete_block(user, blocked)
1965 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
1966 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
1967 {:ok, user_notification_mute} <-
1968 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
1970 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
1974 defp remove_from_mutes(user, %User{} = muted_user) do
1975 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
1976 {:ok, user_notification_mute} <-
1977 UserRelationship.delete_notification_mute(user, muted_user) do
1978 {:ok, [user_mute, user_notification_mute]}
1982 def set_invisible(user, invisible) do
1983 params = %{invisible: invisible}
1986 |> cast(params, [:invisible])
1987 |> validate_required([:invisible])
1988 |> update_and_set_cache()