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 outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
1162 E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1164 @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1165 def outgoing_relations_ap_ids(_, []), do: %{}
1167 def outgoing_relations_ap_ids(%User{} = user, relationship_types)
1168 when is_list(relationship_types) do
1171 |> assoc(:outgoing_relationships)
1172 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1173 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1174 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1175 |> group_by([user_rel, u], user_rel.relationship_type)
1177 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1182 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1186 @spec subscribers(User.t()) :: [User.t()]
1187 def subscribers(user) do
1188 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1192 def deactivate_async(user, status \\ true) do
1193 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1196 def deactivate(user, status \\ true)
1198 def deactivate(users, status) when is_list(users) do
1199 Repo.transaction(fn ->
1200 for user <- users, do: deactivate(user, status)
1204 def deactivate(%User{} = user, status) do
1205 with {:ok, user} <- set_activation_status(user, status) do
1206 Enum.each(get_followers(user), &invalidate_cache/1)
1208 # Only update local user counts, remote will be update during the next pull.
1211 |> Enum.filter(& &1.local)
1212 |> Enum.each(&update_follower_count/1)
1218 def update_notification_settings(%User{} = user, settings) do
1221 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1224 notification_settings =
1225 user.notification_settings
1226 |> Map.merge(settings)
1227 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1229 params = %{notification_settings: notification_settings}
1232 |> cast(params, [:notification_settings])
1233 |> validate_required([:notification_settings])
1234 |> update_and_set_cache()
1237 def delete(users) when is_list(users) do
1238 for user <- users, do: delete(user)
1241 def delete(%User{} = user) do
1242 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1245 def perform(:force_password_reset, user), do: force_password_reset(user)
1247 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1248 def perform(:delete, %User{} = user) do
1249 {:ok, _user} = ActivityPub.delete(user)
1251 # Remove all relationships
1254 |> Enum.each(fn follower ->
1255 ActivityPub.unfollow(follower, user)
1256 unfollow(follower, user)
1261 |> Enum.each(fn followed ->
1262 ActivityPub.unfollow(user, followed)
1263 unfollow(user, followed)
1266 delete_user_activities(user)
1267 invalidate_cache(user)
1271 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1272 def perform(:fetch_initial_posts, %User{} = user) do
1273 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1275 # Insert all the posts in reverse order, so they're in the right order on the timeline
1276 user.source_data["outbox"]
1277 |> Utils.fetch_ordered_collection(pages)
1279 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1282 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1284 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1285 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1286 when is_list(blocked_identifiers) do
1288 blocked_identifiers,
1289 fn blocked_identifier ->
1290 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1291 {:ok, _user_block} <- block(blocker, blocked),
1292 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1296 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1303 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1304 def perform(:follow_import, %User{} = follower, followed_identifiers)
1305 when is_list(followed_identifiers) do
1307 followed_identifiers,
1308 fn followed_identifier ->
1309 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1310 {:ok, follower} <- maybe_direct_follow(follower, followed),
1311 {:ok, _} <- ActivityPub.follow(follower, followed) do
1315 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1322 @spec external_users_query() :: Ecto.Query.t()
1323 def external_users_query do
1331 @spec external_users(keyword()) :: [User.t()]
1332 def external_users(opts \\ []) do
1334 external_users_query()
1335 |> select([u], struct(u, [:id, :ap_id, :info]))
1339 do: where(query, [u], u.id > ^opts[:max_id]),
1344 do: limit(query, ^opts[:limit]),
1350 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1351 BackgroundWorker.enqueue("blocks_import", %{
1352 "blocker_id" => blocker.id,
1353 "blocked_identifiers" => blocked_identifiers
1357 def follow_import(%User{} = follower, followed_identifiers)
1358 when is_list(followed_identifiers) do
1359 BackgroundWorker.enqueue("follow_import", %{
1360 "follower_id" => follower.id,
1361 "followed_identifiers" => followed_identifiers
1365 def delete_user_activities(%User{ap_id: ap_id}) do
1367 |> Activity.Queries.by_actor()
1368 |> RepoStreamer.chunk_stream(50)
1369 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1373 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1375 |> Object.normalize()
1376 |> ActivityPub.delete()
1379 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1380 object = Object.normalize(activity)
1383 |> get_cached_by_ap_id()
1384 |> ActivityPub.unlike(object)
1387 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1388 object = Object.normalize(activity)
1391 |> get_cached_by_ap_id()
1392 |> ActivityPub.unannounce(object)
1395 defp delete_activity(_activity), do: "Doing nothing"
1397 def html_filter_policy(%User{no_rich_text: true}) do
1398 Pleroma.HTML.Scrubber.TwitterText
1401 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1403 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1405 def get_or_fetch_by_ap_id(ap_id) do
1406 user = get_cached_by_ap_id(ap_id)
1408 if !is_nil(user) and !needs_update?(user) do
1411 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1412 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1414 resp = fetch_by_ap_id(ap_id)
1416 if should_fetch_initial do
1417 with {:ok, %User{} = user} <- resp do
1418 fetch_initial_posts(user)
1426 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1427 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1428 with %User{} = user <- get_cached_by_ap_id(uri) do
1434 |> cast(%{}, [:ap_id, :nickname, :local])
1435 |> put_change(:ap_id, uri)
1436 |> put_change(:nickname, nickname)
1437 |> put_change(:local, true)
1438 |> put_change(:follower_address, uri <> "/followers")
1446 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1449 |> :public_key.pem_decode()
1451 |> :public_key.pem_entry_decode()
1456 def public_key(_), do: {:error, "not found key"}
1458 def get_public_key_for_ap_id(ap_id) do
1459 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1460 {:ok, public_key} <- public_key(user) do
1467 defp blank?(""), do: nil
1468 defp blank?(n), do: n
1470 def insert_or_update_user(data) do
1472 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1473 |> remote_user_creation()
1474 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1478 def ap_enabled?(%User{local: true}), do: true
1479 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1480 def ap_enabled?(_), do: false
1482 @doc "Gets or fetch a user by uri or nickname."
1483 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1484 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1485 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1487 # wait a period of time and return newest version of the User structs
1488 # this is because we have synchronous follow APIs and need to simulate them
1489 # with an async handshake
1490 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1491 with %User{} = a <- get_cached_by_id(a.id),
1492 %User{} = b <- get_cached_by_id(b.id) do
1499 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1500 with :ok <- :timer.sleep(timeout),
1501 %User{} = a <- get_cached_by_id(a.id),
1502 %User{} = b <- get_cached_by_id(b.id) do
1509 def parse_bio(bio) when is_binary(bio) and bio != "" do
1511 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1515 def parse_bio(_), do: ""
1517 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1518 # TODO: get profile URLs other than user.ap_id
1519 profile_urls = [user.ap_id]
1522 |> CommonUtils.format_input("text/plain",
1523 mentions_format: :full,
1524 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1529 def parse_bio(_, _), do: ""
1531 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1532 Repo.transaction(fn ->
1533 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1537 def tag(nickname, tags) when is_binary(nickname),
1538 do: tag(get_by_nickname(nickname), tags)
1540 def tag(%User{} = user, tags),
1541 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1543 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1544 Repo.transaction(fn ->
1545 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1549 def untag(nickname, tags) when is_binary(nickname),
1550 do: untag(get_by_nickname(nickname), tags)
1552 def untag(%User{} = user, tags),
1553 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1555 defp update_tags(%User{} = user, new_tags) do
1556 {:ok, updated_user} =
1558 |> change(%{tags: new_tags})
1559 |> update_and_set_cache()
1564 defp normalize_tags(tags) do
1567 |> Enum.map(&String.downcase/1)
1570 defp local_nickname_regex do
1571 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1572 @extended_local_nickname_regex
1574 @strict_local_nickname_regex
1578 def local_nickname(nickname_or_mention) do
1581 |> String.split("@")
1585 def full_nickname(nickname_or_mention),
1586 do: String.trim_leading(nickname_or_mention, "@")
1588 def error_user(ap_id) do
1592 nickname: "erroruser@example.com",
1593 inserted_at: NaiveDateTime.utc_now()
1597 @spec all_superusers() :: [User.t()]
1598 def all_superusers do
1599 User.Query.build(%{super_users: true, local: true, deactivated: false})
1603 def showing_reblogs?(%User{} = user, %User{} = target) do
1604 not UserRelationship.reblog_mute_exists?(user, target)
1608 The function returns a query to get users with no activity for given interval of days.
1609 Inactive users are those who didn't read any notification, or had any activity where
1610 the user is the activity's actor, during `inactivity_threshold` days.
1611 Deactivated users will not appear in this list.
1615 iex> Pleroma.User.list_inactive_users()
1618 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1619 def list_inactive_users_query(inactivity_threshold \\ 7) do
1620 negative_inactivity_threshold = -inactivity_threshold
1621 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1622 # Subqueries are not supported in `where` clauses, join gets too complicated.
1623 has_read_notifications =
1624 from(n in Pleroma.Notification,
1625 where: n.seen == true,
1627 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1630 |> Pleroma.Repo.all()
1632 from(u in Pleroma.User,
1633 left_join: a in Pleroma.Activity,
1634 on: u.ap_id == a.actor,
1635 where: not is_nil(u.nickname),
1636 where: u.deactivated != ^true,
1637 where: u.id not in ^has_read_notifications,
1640 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1641 is_nil(max(a.inserted_at))
1646 Enable or disable email notifications for user
1650 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1651 Pleroma.User{email_notifications: %{"digest" => true}}
1653 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1654 Pleroma.User{email_notifications: %{"digest" => false}}
1656 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1657 {:ok, t()} | {:error, Ecto.Changeset.t()}
1658 def switch_email_notifications(user, type, status) do
1659 User.update_email_notifications(user, %{type => status})
1663 Set `last_digest_emailed_at` value for the user to current time
1665 @spec touch_last_digest_emailed_at(t()) :: t()
1666 def touch_last_digest_emailed_at(user) do
1667 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1669 {:ok, updated_user} =
1671 |> change(%{last_digest_emailed_at: now})
1672 |> update_and_set_cache()
1677 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1678 def toggle_confirmation(%User{} = user) do
1680 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1681 |> update_and_set_cache()
1684 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1688 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1689 # use instance-default
1690 config = Pleroma.Config.get([:assets, :mascots])
1691 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1692 mascot = Keyword.get(config, default_mascot)
1695 "id" => "default-mascot",
1696 "url" => mascot[:url],
1697 "preview_url" => mascot[:url],
1699 "mime_type" => mascot[:mime_type]
1704 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1706 def ensure_keys_present(%User{} = user) do
1707 with {:ok, pem} <- Keys.generate_rsa_pem() do
1709 |> cast(%{keys: pem}, [:keys])
1710 |> validate_required([:keys])
1711 |> update_and_set_cache()
1715 def get_ap_ids_by_nicknames(nicknames) do
1717 where: u.nickname in ^nicknames,
1723 defdelegate search(query, opts \\ []), to: User.Search
1725 defp put_password_hash(
1726 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1728 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1731 defp put_password_hash(changeset), do: changeset
1733 def is_internal_user?(%User{nickname: nil}), do: true
1734 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1735 def is_internal_user?(_), do: false
1737 # A hack because user delete activities have a fake id for whatever reason
1738 # TODO: Get rid of this
1739 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1741 def get_delivered_users_by_object_id(object_id) do
1743 inner_join: delivery in assoc(u, :deliveries),
1744 where: delivery.object_id == ^object_id
1749 def change_email(user, email) do
1751 |> cast(%{email: email}, [:email])
1752 |> validate_required([:email])
1753 |> unique_constraint(:email)
1754 |> validate_format(:email, @email_regex)
1755 |> update_and_set_cache()
1758 # Internal function; public one is `deactivate/2`
1759 defp set_activation_status(user, deactivated) do
1761 |> cast(%{deactivated: deactivated}, [:deactivated])
1762 |> update_and_set_cache()
1765 def update_banner(user, banner) do
1767 |> cast(%{banner: banner}, [:banner])
1768 |> update_and_set_cache()
1771 def update_background(user, background) do
1773 |> cast(%{background: background}, [:background])
1774 |> update_and_set_cache()
1777 def update_source_data(user, source_data) do
1779 |> cast(%{source_data: source_data}, [:source_data])
1780 |> update_and_set_cache()
1783 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1786 moderator: is_moderator
1790 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1791 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1792 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1793 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1796 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1797 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1801 def fields(%{fields: nil}), do: []
1803 def fields(%{fields: fields}), do: fields
1805 def validate_fields(changeset, remote? \\ false) do
1806 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1807 limit = Pleroma.Config.get([:instance, limit_name], 0)
1810 |> validate_length(:fields, max: limit)
1811 |> validate_change(:fields, fn :fields, fields ->
1812 if Enum.all?(fields, &valid_field?/1) do
1820 defp valid_field?(%{"name" => name, "value" => value}) do
1821 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1822 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1824 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1825 String.length(value) <= value_limit
1828 defp valid_field?(_), do: false
1830 defp truncate_field(%{"name" => name, "value" => value}) do
1832 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1835 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1837 %{"name" => name, "value" => value}
1840 def admin_api_update(user, params) do
1847 |> update_and_set_cache()
1850 def mascot_update(user, url) do
1852 |> cast(%{mascot: url}, [:mascot])
1853 |> validate_required([:mascot])
1854 |> update_and_set_cache()
1857 def mastodon_settings_update(user, settings) do
1859 |> cast(%{settings: settings}, [:settings])
1860 |> validate_required([:settings])
1861 |> update_and_set_cache()
1864 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1865 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1867 if need_confirmation? do
1869 confirmation_pending: true,
1870 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1874 confirmation_pending: false,
1875 confirmation_token: nil
1879 cast(user, params, [:confirmation_pending, :confirmation_token])
1882 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1883 if id not in user.pinned_activities do
1884 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1885 params = %{pinned_activities: user.pinned_activities ++ [id]}
1888 |> cast(params, [:pinned_activities])
1889 |> validate_length(:pinned_activities,
1890 max: max_pinned_statuses,
1891 message: "You have already pinned the maximum number of statuses"
1896 |> update_and_set_cache()
1899 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1900 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1903 |> cast(params, [:pinned_activities])
1904 |> update_and_set_cache()
1907 def update_email_notifications(user, settings) do
1908 email_notifications =
1909 user.email_notifications
1910 |> Map.merge(settings)
1911 |> Map.take(["digest"])
1913 params = %{email_notifications: email_notifications}
1914 fields = [:email_notifications]
1917 |> cast(params, fields)
1918 |> validate_required(fields)
1919 |> update_and_set_cache()
1922 defp set_subscribers(user, subscribers) do
1923 params = %{subscribers: subscribers}
1926 |> cast(params, [:subscribers])
1927 |> validate_required([:subscribers])
1928 |> update_and_set_cache()
1931 def add_to_subscribers(user, subscribed) do
1932 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1935 def remove_from_subscribers(user, subscribed) do
1936 set_subscribers(user, List.delete(user.subscribers, subscribed))
1939 defp set_domain_blocks(user, domain_blocks) do
1940 params = %{domain_blocks: domain_blocks}
1943 |> cast(params, [:domain_blocks])
1944 |> validate_required([:domain_blocks])
1945 |> update_and_set_cache()
1948 def block_domain(user, domain_blocked) do
1949 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1952 def unblock_domain(user, domain_blocked) do
1953 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1956 @spec add_to_block(User.t(), User.t()) ::
1957 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
1958 defp add_to_block(%User{} = user, %User{} = blocked) do
1959 UserRelationship.create_block(user, blocked)
1962 @spec add_to_block(User.t(), User.t()) ::
1963 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
1964 defp remove_from_block(%User{} = user, %User{} = blocked) do
1965 UserRelationship.delete_block(user, blocked)
1968 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
1969 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
1970 {:ok, user_notification_mute} <-
1971 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
1973 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
1977 defp remove_from_mutes(user, %User{} = muted_user) do
1978 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
1979 {:ok, user_notification_mute} <-
1980 UserRelationship.delete_notification_mute(user, muted_user) do
1981 {:ok, [user_mute, user_notification_mute]}
1985 def set_invisible(user, invisible) do
1986 params = %{invisible: invisible}
1989 |> cast(params, [:invisible])
1990 |> validate_required([:invisible])
1991 |> update_and_set_cache()