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 # AP ID user relationships (blocks, mutes etc.)
48 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
49 @user_relationships_config [
51 blocker_blocks: :blocked_users,
52 blockee_blocks: :blocker_users
55 muter_mutes: :muted_users,
56 mutee_mutes: :muter_users
59 reblog_muter_mutes: :reblog_muted_users,
60 reblog_mutee_mutes: :reblog_muter_users
63 notification_muter_mutes: :notification_muted_users,
64 notification_mutee_mutes: :notification_muter_users
66 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
67 inverse_subscription: [
68 subscribee_subscriptions: :subscriber_users,
69 subscriber_subscriptions: :subscribee_users
75 field(:email, :string)
77 field(:nickname, :string)
78 field(:password_hash, :string)
79 field(:password, :string, virtual: true)
80 field(:password_confirmation, :string, virtual: true)
82 field(:ap_id, :string)
84 field(:local, :boolean, default: true)
85 field(:follower_address, :string)
86 field(:following_address, :string)
87 field(:search_rank, :float, virtual: true)
88 field(:search_type, :integer, virtual: true)
89 field(:tags, {:array, :string}, default: [])
90 field(:last_refreshed_at, :naive_datetime_usec)
91 field(:last_digest_emailed_at, :naive_datetime)
92 field(:banner, :map, default: %{})
93 field(:background, :map, default: %{})
94 field(:source_data, :map, default: %{})
95 field(:note_count, :integer, default: 0)
96 field(:follower_count, :integer, default: 0)
97 field(:following_count, :integer, default: 0)
98 field(:locked, :boolean, default: false)
99 field(:confirmation_pending, :boolean, default: false)
100 field(:password_reset_pending, :boolean, default: false)
101 field(:confirmation_token, :string, default: nil)
102 field(:default_scope, :string, default: "public")
103 field(:domain_blocks, {:array, :string}, default: [])
104 field(:deactivated, :boolean, default: false)
105 field(:no_rich_text, :boolean, default: false)
106 field(:ap_enabled, :boolean, default: false)
107 field(:is_moderator, :boolean, default: false)
108 field(:is_admin, :boolean, default: false)
109 field(:show_role, :boolean, default: true)
110 field(:settings, :map, default: nil)
111 field(:magic_key, :string, default: nil)
112 field(:uri, :string, default: nil)
113 field(:hide_followers_count, :boolean, default: false)
114 field(:hide_follows_count, :boolean, default: false)
115 field(:hide_followers, :boolean, default: false)
116 field(:hide_follows, :boolean, default: false)
117 field(:hide_favorites, :boolean, default: true)
118 field(:unread_conversation_count, :integer, default: 0)
119 field(:pinned_activities, {:array, :string}, default: [])
120 field(:email_notifications, :map, default: %{"digest" => false})
121 field(:mascot, :map, default: nil)
122 field(:emoji, {:array, :map}, default: [])
123 field(:pleroma_settings_store, :map, default: %{})
124 field(:fields, {:array, :map}, default: [])
125 field(:raw_fields, {:array, :map}, default: [])
126 field(:discoverable, :boolean, default: false)
127 field(:invisible, :boolean, default: false)
128 field(:allow_following_move, :boolean, default: true)
129 field(:skip_thread_containment, :boolean, default: false)
130 field(:also_known_as, {:array, :string}, default: [])
132 field(:notification_settings, :map,
136 "non_follows" => true,
137 "non_followers" => true
141 has_many(:notifications, Notification)
142 has_many(:registrations, Registration)
143 has_many(:deliveries, Delivery)
145 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
146 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
148 for {relationship_type,
150 {outgoing_relation, outgoing_relation_target},
151 {incoming_relation, incoming_relation_source}
152 ]} <- @user_relationships_config do
153 # Definitions of `has_many :blocker_blocks`, `has_many :muter_mutes` etc.
154 has_many(outgoing_relation, UserRelationship,
155 foreign_key: :source_id,
156 where: [relationship_type: relationship_type]
159 # Definitions of `has_many :blockee_blocks`, `has_many :mutee_mutes` etc.
160 has_many(incoming_relation, UserRelationship,
161 foreign_key: :target_id,
162 where: [relationship_type: relationship_type]
165 # Definitions of `has_many :blocked_users`, `has_many :muted_users` etc.
166 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
168 # Definitions of `has_many :blocker_users`, `has_many :muter_users` etc.
169 has_many(incoming_relation_source, through: [incoming_relation, :source])
172 field(:info, :map, default: %{})
174 # `:blocks` is deprecated (replaced with `blocked_users` relation)
175 field(:blocks, {:array, :string}, default: [])
176 # `:mutes` is deprecated (replaced with `muted_users` relation)
177 field(:mutes, {:array, :string}, default: [])
178 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
179 field(:muted_reblogs, {:array, :string}, default: [])
180 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
181 field(:muted_notifications, {:array, :string}, default: [])
182 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
183 field(:subscribers, {:array, :string}, default: [])
188 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
189 @user_relationships_config do
190 # Definitions of `blocked_users_relation/1`, `muted_users_relation/1`, etc.
191 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
192 target_users_query = assoc(user, unquote(outgoing_relation_target))
194 if restrict_deactivated? do
195 restrict_deactivated(target_users_query)
201 # Definitions of `blocked_users/1`, `muted_users/1`, etc.
202 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
204 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
206 restrict_deactivated?
211 # Definitions of `blocked_users_ap_ids/1`, `muted_users_ap_ids/1`, etc.
212 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
214 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
216 restrict_deactivated?
218 |> select([u], u.ap_id)
223 @doc "Returns if the user should be allowed to authenticate"
224 def auth_active?(%User{deactivated: true}), do: false
226 def auth_active?(%User{confirmation_pending: true}),
227 do: !Pleroma.Config.get([:instance, :account_activation_required])
229 def auth_active?(%User{}), do: true
231 def visible_for?(user, for_user \\ nil)
233 def visible_for?(%User{invisible: true}, _), do: false
235 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
237 def visible_for?(%User{} = user, for_user) do
238 auth_active?(user) || superuser?(for_user)
241 def visible_for?(_, _), do: false
243 def superuser?(%User{local: true, is_admin: true}), do: true
244 def superuser?(%User{local: true, is_moderator: true}), do: true
245 def superuser?(_), do: false
247 def invisible?(%User{invisible: true}), do: true
248 def invisible?(_), do: false
250 def avatar_url(user, options \\ []) do
252 %{"url" => [%{"href" => href} | _]} -> href
253 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
257 def banner_url(user, options \\ []) do
259 %{"url" => [%{"href" => href} | _]} -> href
260 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
264 def profile_url(%User{source_data: %{"url" => url}}), do: url
265 def profile_url(%User{ap_id: ap_id}), do: ap_id
266 def profile_url(_), do: nil
268 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
270 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
271 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
273 @spec ap_following(User.t()) :: Sring.t()
274 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
275 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
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 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
296 def restrict_deactivated(query) do
297 from(u in query, where: u.deactivated != ^true)
300 defdelegate following_count(user), to: FollowingRelationship
302 defp truncate_fields_param(params) do
303 if Map.has_key?(params, :fields) do
304 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
310 defp truncate_if_exists(params, key, max_length) do
311 if Map.has_key?(params, key) and is_binary(params[key]) do
312 {value, _chopped} = String.split_at(params[key], max_length)
313 Map.put(params, key, value)
319 def remote_user_creation(params) do
320 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
321 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
325 |> truncate_if_exists(:name, name_limit)
326 |> truncate_if_exists(:bio, bio_limit)
327 |> truncate_fields_param()
347 :hide_followers_count,
357 |> validate_required([:name, :ap_id])
358 |> unique_constraint(:nickname)
359 |> validate_format(:nickname, @email_regex)
360 |> validate_length(:bio, max: bio_limit)
361 |> validate_length(:name, max: name_limit)
362 |> validate_fields(true)
364 case params[:source_data] do
365 %{"followers" => followers, "following" => following} ->
367 |> put_change(:follower_address, followers)
368 |> put_change(:following_address, following)
371 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
372 put_change(changeset, :follower_address, followers)
376 def update_changeset(struct, params \\ %{}) do
377 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
378 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
393 :hide_followers_count,
396 :allow_following_move,
399 :skip_thread_containment,
402 :pleroma_settings_store,
407 |> unique_constraint(:nickname)
408 |> validate_format(:nickname, local_nickname_regex())
409 |> validate_length(:bio, max: bio_limit)
410 |> validate_length(:name, min: 1, max: name_limit)
411 |> validate_fields(false)
414 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
415 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
416 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
418 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
420 params = if remote?, do: truncate_fields_param(params), else: params
442 :allow_following_move,
444 :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 try_send_confirmation_email(users) do
578 Enum.each(users, &try_send_confirmation_email/1)
581 def needs_update?(%User{local: true}), do: false
583 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
585 def needs_update?(%User{local: false} = user) do
586 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
589 def needs_update?(_), do: true
591 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
592 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
593 follow(follower, followed, "pending")
596 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
597 follow(follower, followed)
600 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
601 if not ap_enabled?(followed) do
602 follow(follower, followed)
608 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
609 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
610 def follow_all(follower, followeds) do
612 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
613 |> Enum.each(&follow(follower, &1, "accept"))
618 defdelegate following(user), to: FollowingRelationship
620 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
621 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
624 followed.deactivated ->
625 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
627 deny_follow_blocked and blocks?(followed, follower) ->
628 {:error, "Could not follow user: #{followed.nickname} blocked you."}
631 FollowingRelationship.follow(follower, followed, state)
633 {:ok, _} = update_follower_count(followed)
636 |> update_following_count()
641 def unfollow(%User{} = follower, %User{} = followed) do
642 if following?(follower, followed) and follower.ap_id != followed.ap_id do
643 FollowingRelationship.unfollow(follower, followed)
645 {:ok, followed} = update_follower_count(followed)
649 |> update_following_count()
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)
703 def update_and_set_cache(struct, params) do
705 |> update_changeset(params)
706 |> update_and_set_cache()
709 def update_and_set_cache(changeset) do
710 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
715 def invalidate_cache(user) do
716 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
717 Cachex.del(:user_cache, "nickname:#{user.nickname}")
720 def get_cached_by_ap_id(ap_id) do
721 key = "ap_id:#{ap_id}"
722 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
725 def get_cached_by_id(id) do
729 Cachex.fetch!(:user_cache, key, fn _ ->
733 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
734 {:commit, user.ap_id}
740 get_cached_by_ap_id(ap_id)
743 def get_cached_by_nickname(nickname) do
744 key = "nickname:#{nickname}"
746 Cachex.fetch!(:user_cache, key, fn ->
747 case get_or_fetch_by_nickname(nickname) do
748 {:ok, user} -> {:commit, user}
749 {:error, _error} -> {:ignore, nil}
754 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
755 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
758 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
759 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
761 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
762 get_cached_by_nickname(nickname_or_id)
764 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
765 get_cached_by_nickname(nickname_or_id)
772 def get_by_nickname(nickname) do
773 Repo.get_by(User, nickname: nickname) ||
774 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
775 Repo.get_by(User, nickname: local_nickname(nickname))
779 def get_by_email(email), do: Repo.get_by(User, email: email)
781 def get_by_nickname_or_email(nickname_or_email) do
782 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
785 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
787 def get_or_fetch_by_nickname(nickname) do
788 with %User{} = user <- get_by_nickname(nickname) do
792 with [_nick, _domain] <- String.split(nickname, "@"),
793 {:ok, user} <- fetch_by_nickname(nickname) do
794 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
795 fetch_initial_posts(user)
800 _e -> {:error, "not found " <> nickname}
805 @doc "Fetch some posts when the user has just been federated with"
806 def fetch_initial_posts(user) do
807 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
810 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
811 def get_followers_query(%User{} = user, nil) do
812 User.Query.build(%{followers: user, deactivated: false})
815 def get_followers_query(user, page) do
817 |> get_followers_query(nil)
818 |> User.Query.paginate(page, 20)
821 @spec get_followers_query(User.t()) :: Ecto.Query.t()
822 def get_followers_query(user), do: get_followers_query(user, nil)
824 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
825 def get_followers(user, page \\ nil) do
827 |> get_followers_query(page)
831 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
832 def get_external_followers(user, page \\ nil) do
834 |> get_followers_query(page)
835 |> User.Query.build(%{external: true})
839 def get_followers_ids(user, page \\ nil) do
841 |> get_followers_query(page)
846 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
847 def get_friends_query(%User{} = user, nil) do
848 User.Query.build(%{friends: user, deactivated: false})
851 def get_friends_query(user, page) do
853 |> get_friends_query(nil)
854 |> User.Query.paginate(page, 20)
857 @spec get_friends_query(User.t()) :: Ecto.Query.t()
858 def get_friends_query(user), do: get_friends_query(user, nil)
860 def get_friends(user, page \\ nil) do
862 |> get_friends_query(page)
866 def get_friends_ids(user, page \\ nil) do
868 |> get_friends_query(page)
873 defdelegate get_follow_requests(user), to: FollowingRelationship
875 def increase_note_count(%User{} = user) do
877 |> where(id: ^user.id)
878 |> update([u], inc: [note_count: 1])
880 |> Repo.update_all([])
882 {1, [user]} -> set_cache(user)
887 def decrease_note_count(%User{} = user) do
889 |> where(id: ^user.id)
892 note_count: fragment("greatest(0, note_count - 1)")
896 |> Repo.update_all([])
898 {1, [user]} -> set_cache(user)
903 def update_note_count(%User{} = user, note_count \\ nil) do
908 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
914 |> cast(%{note_count: note_count}, [:note_count])
915 |> update_and_set_cache()
918 @spec maybe_fetch_follow_information(User.t()) :: User.t()
919 def maybe_fetch_follow_information(user) do
920 with {:ok, user} <- fetch_follow_information(user) do
924 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
930 def fetch_follow_information(user) do
931 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
933 |> follow_information_changeset(info)
934 |> update_and_set_cache()
938 defp follow_information_changeset(user, params) do
945 :hide_followers_count,
950 def update_follower_count(%User{} = user) do
951 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
952 follower_count_query =
953 User.Query.build(%{followers: user, deactivated: false})
954 |> select([u], %{count: count(u.id)})
957 |> where(id: ^user.id)
958 |> join(:inner, [u], s in subquery(follower_count_query))
960 set: [follower_count: s.count]
963 |> Repo.update_all([])
965 {1, [user]} -> set_cache(user)
969 {:ok, maybe_fetch_follow_information(user)}
973 @spec update_following_count(User.t()) :: User.t()
974 def update_following_count(%User{local: false} = user) do
975 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
976 maybe_fetch_follow_information(user)
982 def update_following_count(%User{local: true} = user) do
983 following_count = FollowingRelationship.following_count(user)
986 |> follow_information_changeset(%{following_count: following_count})
990 def set_unread_conversation_count(%User{local: true} = user) do
991 unread_query = Participation.unread_conversation_count_for_user(user)
994 |> join(:inner, [u], p in subquery(unread_query))
996 set: [unread_conversation_count: p.count]
998 |> where([u], u.id == ^user.id)
1000 |> Repo.update_all([])
1002 {1, [user]} -> set_cache(user)
1007 def set_unread_conversation_count(user), do: {:ok, user}
1009 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1011 Participation.unread_conversation_count_for_user(user)
1012 |> where([p], p.conversation_id == ^conversation.id)
1015 |> join(:inner, [u], p in subquery(unread_query))
1017 inc: [unread_conversation_count: 1]
1019 |> where([u], u.id == ^user.id)
1020 |> where([u, p], p.count == 0)
1022 |> Repo.update_all([])
1024 {1, [user]} -> set_cache(user)
1029 def increment_unread_conversation_count(_, user), do: {:ok, user}
1031 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1032 def get_users_from_set(ap_ids, local_only \\ true) do
1033 criteria = %{ap_id: ap_ids, deactivated: false}
1034 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1036 User.Query.build(criteria)
1040 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1041 def get_recipients_from_activity(%Activity{recipients: to}) do
1042 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1046 @spec mute(User.t(), User.t(), boolean()) ::
1047 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1048 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1049 add_to_mutes(muter, mutee, notifications?)
1052 def unmute(%User{} = muter, %User{} = mutee) do
1053 remove_from_mutes(muter, mutee)
1056 def subscribe(%User{} = subscriber, %User{} = target) do
1057 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1059 if blocks?(target, subscriber) and deny_follow_blocked do
1060 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1062 # Note: the relationship is inverse: subscriber acts as relationship target
1063 UserRelationship.create_inverse_subscription(target, subscriber)
1067 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1068 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1069 subscribe(subscriber, subscribee)
1073 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1074 # Note: the relationship is inverse: subscriber acts as relationship target
1075 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1078 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1079 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1080 unsubscribe(unsubscriber, user)
1084 def block(%User{} = blocker, %User{} = blocked) do
1085 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1087 if following?(blocker, blocked) do
1088 {:ok, blocker, _} = unfollow(blocker, blocked)
1094 # clear any requested follows as well
1096 case CommonAPI.reject_follow_request(blocked, blocker) do
1097 {:ok, %User{} = updated_blocked} -> updated_blocked
1101 unsubscribe(blocked, blocker)
1103 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1105 {:ok, blocker} = update_follower_count(blocker)
1106 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1107 add_to_block(blocker, blocked)
1110 # helper to handle the block given only an actor's AP id
1111 def block(%User{} = blocker, %{ap_id: ap_id}) do
1112 block(blocker, get_cached_by_ap_id(ap_id))
1115 def unblock(%User{} = blocker, %User{} = blocked) do
1116 remove_from_block(blocker, blocked)
1119 # helper to handle the block given only an actor's AP id
1120 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1121 unblock(blocker, get_cached_by_ap_id(ap_id))
1124 def mutes?(nil, _), do: false
1125 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1127 def mutes_user?(%User{} = user, %User{} = target) do
1128 UserRelationship.mute_exists?(user, target)
1131 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1132 def muted_notifications?(nil, _), do: false
1134 def muted_notifications?(%User{} = user, %User{} = target),
1135 do: UserRelationship.notification_mute_exists?(user, target)
1137 def blocks?(nil, _), do: false
1139 def blocks?(%User{} = user, %User{} = target) do
1140 blocks_user?(user, target) || blocks_domain?(user, target)
1143 def blocks_user?(%User{} = user, %User{} = target) do
1144 UserRelationship.block_exists?(user, target)
1147 def blocks_user?(_, _), do: false
1149 def blocks_domain?(%User{} = user, %User{} = target) do
1150 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1151 %{host: host} = URI.parse(target.ap_id)
1152 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1155 def blocks_domain?(_, _), do: false
1157 def subscribed_to?(%User{} = user, %User{} = target) do
1158 # Note: the relationship is inverse: subscriber acts as relationship target
1159 UserRelationship.inverse_subscription_exists?(target, user)
1162 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1163 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1164 subscribed_to?(user, target)
1169 Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
1170 E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1172 @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1173 def outgoing_relations_ap_ids(_, []), do: %{}
1175 def outgoing_relations_ap_ids(%User{} = user, relationship_types)
1176 when is_list(relationship_types) do
1179 |> assoc(:outgoing_relationships)
1180 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1181 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1182 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1183 |> group_by([user_rel, u], user_rel.relationship_type)
1185 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1190 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1194 def deactivate_async(user, status \\ true) do
1195 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1198 def deactivate(user, status \\ true)
1200 def deactivate(users, status) when is_list(users) do
1201 Repo.transaction(fn ->
1202 for user <- users, do: deactivate(user, status)
1206 def deactivate(%User{} = user, status) do
1207 with {:ok, user} <- set_activation_status(user, status) do
1210 |> Enum.filter(& &1.local)
1211 |> Enum.each(fn follower ->
1212 follower |> update_following_count() |> set_cache()
1215 # Only update local user counts, remote will be update during the next pull.
1218 |> Enum.filter(& &1.local)
1219 |> Enum.each(&update_follower_count/1)
1225 def update_notification_settings(%User{} = user, settings) do
1228 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1231 notification_settings =
1232 user.notification_settings
1233 |> Map.merge(settings)
1234 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1236 params = %{notification_settings: notification_settings}
1239 |> cast(params, [:notification_settings])
1240 |> validate_required([:notification_settings])
1241 |> update_and_set_cache()
1244 def delete(users) when is_list(users) do
1245 for user <- users, do: delete(user)
1248 def delete(%User{} = user) do
1249 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1252 def perform(:force_password_reset, user), do: force_password_reset(user)
1254 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1255 def perform(:delete, %User{} = user) do
1256 {:ok, _user} = ActivityPub.delete(user)
1258 # Remove all relationships
1261 |> Enum.each(fn follower ->
1262 ActivityPub.unfollow(follower, user)
1263 unfollow(follower, user)
1268 |> Enum.each(fn followed ->
1269 ActivityPub.unfollow(user, followed)
1270 unfollow(user, followed)
1273 delete_user_activities(user)
1274 invalidate_cache(user)
1278 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1279 def perform(:fetch_initial_posts, %User{} = user) do
1280 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1282 # Insert all the posts in reverse order, so they're in the right order on the timeline
1283 user.source_data["outbox"]
1284 |> Utils.fetch_ordered_collection(pages)
1286 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1289 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1291 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1292 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1293 when is_list(blocked_identifiers) do
1295 blocked_identifiers,
1296 fn blocked_identifier ->
1297 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1298 {:ok, _user_block} <- block(blocker, blocked),
1299 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1303 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1310 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1311 def perform(:follow_import, %User{} = follower, followed_identifiers)
1312 when is_list(followed_identifiers) do
1314 followed_identifiers,
1315 fn followed_identifier ->
1316 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1317 {:ok, follower} <- maybe_direct_follow(follower, followed),
1318 {:ok, _} <- ActivityPub.follow(follower, followed) do
1322 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1329 @spec external_users_query() :: Ecto.Query.t()
1330 def external_users_query do
1338 @spec external_users(keyword()) :: [User.t()]
1339 def external_users(opts \\ []) do
1341 external_users_query()
1342 |> select([u], struct(u, [:id, :ap_id]))
1346 do: where(query, [u], u.id > ^opts[:max_id]),
1351 do: limit(query, ^opts[:limit]),
1357 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1358 BackgroundWorker.enqueue("blocks_import", %{
1359 "blocker_id" => blocker.id,
1360 "blocked_identifiers" => blocked_identifiers
1364 def follow_import(%User{} = follower, followed_identifiers)
1365 when is_list(followed_identifiers) do
1366 BackgroundWorker.enqueue("follow_import", %{
1367 "follower_id" => follower.id,
1368 "followed_identifiers" => followed_identifiers
1372 def delete_user_activities(%User{ap_id: ap_id}) do
1374 |> Activity.Queries.by_actor()
1375 |> RepoStreamer.chunk_stream(50)
1376 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1380 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1382 |> Object.normalize()
1383 |> ActivityPub.delete()
1386 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1387 object = Object.normalize(activity)
1390 |> get_cached_by_ap_id()
1391 |> ActivityPub.unlike(object)
1394 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1395 object = Object.normalize(activity)
1398 |> get_cached_by_ap_id()
1399 |> ActivityPub.unannounce(object)
1402 defp delete_activity(_activity), do: "Doing nothing"
1404 def html_filter_policy(%User{no_rich_text: true}) do
1405 Pleroma.HTML.Scrubber.TwitterText
1408 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1410 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1412 def get_or_fetch_by_ap_id(ap_id) do
1413 user = get_cached_by_ap_id(ap_id)
1415 if !is_nil(user) and !needs_update?(user) do
1418 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1419 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1421 resp = fetch_by_ap_id(ap_id)
1423 if should_fetch_initial do
1424 with {:ok, %User{} = user} <- resp do
1425 fetch_initial_posts(user)
1434 Creates an internal service actor by URI if missing.
1435 Optionally takes nickname for addressing.
1437 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1438 with user when is_nil(user) <- get_cached_by_ap_id(uri) do
1445 follower_address: uri <> "/followers"
1454 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1457 |> :public_key.pem_decode()
1459 |> :public_key.pem_entry_decode()
1464 def public_key(_), do: {:error, "not found key"}
1466 def get_public_key_for_ap_id(ap_id) do
1467 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1468 {:ok, public_key} <- public_key(user) do
1475 defp blank?(""), do: nil
1476 defp blank?(n), do: n
1478 def insert_or_update_user(data) do
1480 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1481 |> remote_user_creation()
1482 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1486 def ap_enabled?(%User{local: true}), do: true
1487 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1488 def ap_enabled?(_), do: false
1490 @doc "Gets or fetch a user by uri or nickname."
1491 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1492 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1493 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1495 # wait a period of time and return newest version of the User structs
1496 # this is because we have synchronous follow APIs and need to simulate them
1497 # with an async handshake
1498 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1499 with %User{} = a <- get_cached_by_id(a.id),
1500 %User{} = b <- get_cached_by_id(b.id) do
1507 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1508 with :ok <- :timer.sleep(timeout),
1509 %User{} = a <- get_cached_by_id(a.id),
1510 %User{} = b <- get_cached_by_id(b.id) do
1517 def parse_bio(bio) when is_binary(bio) and bio != "" do
1519 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1523 def parse_bio(_), do: ""
1525 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1526 # TODO: get profile URLs other than user.ap_id
1527 profile_urls = [user.ap_id]
1530 |> CommonUtils.format_input("text/plain",
1531 mentions_format: :full,
1532 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1537 def parse_bio(_, _), do: ""
1539 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1540 Repo.transaction(fn ->
1541 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1545 def tag(nickname, tags) when is_binary(nickname),
1546 do: tag(get_by_nickname(nickname), tags)
1548 def tag(%User{} = user, tags),
1549 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1551 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1552 Repo.transaction(fn ->
1553 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1557 def untag(nickname, tags) when is_binary(nickname),
1558 do: untag(get_by_nickname(nickname), tags)
1560 def untag(%User{} = user, tags),
1561 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1563 defp update_tags(%User{} = user, new_tags) do
1564 {:ok, updated_user} =
1566 |> change(%{tags: new_tags})
1567 |> update_and_set_cache()
1572 defp normalize_tags(tags) do
1575 |> Enum.map(&String.downcase/1)
1578 defp local_nickname_regex do
1579 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1580 @extended_local_nickname_regex
1582 @strict_local_nickname_regex
1586 def local_nickname(nickname_or_mention) do
1589 |> String.split("@")
1593 def full_nickname(nickname_or_mention),
1594 do: String.trim_leading(nickname_or_mention, "@")
1596 def error_user(ap_id) do
1600 nickname: "erroruser@example.com",
1601 inserted_at: NaiveDateTime.utc_now()
1605 @spec all_superusers() :: [User.t()]
1606 def all_superusers do
1607 User.Query.build(%{super_users: true, local: true, deactivated: false})
1611 def showing_reblogs?(%User{} = user, %User{} = target) do
1612 not UserRelationship.reblog_mute_exists?(user, target)
1616 The function returns a query to get users with no activity for given interval of days.
1617 Inactive users are those who didn't read any notification, or had any activity where
1618 the user is the activity's actor, during `inactivity_threshold` days.
1619 Deactivated users will not appear in this list.
1623 iex> Pleroma.User.list_inactive_users()
1626 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1627 def list_inactive_users_query(inactivity_threshold \\ 7) do
1628 negative_inactivity_threshold = -inactivity_threshold
1629 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1630 # Subqueries are not supported in `where` clauses, join gets too complicated.
1631 has_read_notifications =
1632 from(n in Pleroma.Notification,
1633 where: n.seen == true,
1635 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1638 |> Pleroma.Repo.all()
1640 from(u in Pleroma.User,
1641 left_join: a in Pleroma.Activity,
1642 on: u.ap_id == a.actor,
1643 where: not is_nil(u.nickname),
1644 where: u.deactivated != ^true,
1645 where: u.id not in ^has_read_notifications,
1648 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1649 is_nil(max(a.inserted_at))
1654 Enable or disable email notifications for user
1658 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1659 Pleroma.User{email_notifications: %{"digest" => true}}
1661 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1662 Pleroma.User{email_notifications: %{"digest" => false}}
1664 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1665 {:ok, t()} | {:error, Ecto.Changeset.t()}
1666 def switch_email_notifications(user, type, status) do
1667 User.update_email_notifications(user, %{type => status})
1671 Set `last_digest_emailed_at` value for the user to current time
1673 @spec touch_last_digest_emailed_at(t()) :: t()
1674 def touch_last_digest_emailed_at(user) do
1675 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1677 {:ok, updated_user} =
1679 |> change(%{last_digest_emailed_at: now})
1680 |> update_and_set_cache()
1685 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1686 def toggle_confirmation(%User{} = user) do
1688 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1689 |> update_and_set_cache()
1692 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1693 def toggle_confirmation(users) do
1694 Enum.map(users, &toggle_confirmation/1)
1697 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1701 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1702 # use instance-default
1703 config = Pleroma.Config.get([:assets, :mascots])
1704 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1705 mascot = Keyword.get(config, default_mascot)
1708 "id" => "default-mascot",
1709 "url" => mascot[:url],
1710 "preview_url" => mascot[:url],
1712 "mime_type" => mascot[:mime_type]
1717 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1719 def ensure_keys_present(%User{} = user) do
1720 with {:ok, pem} <- Keys.generate_rsa_pem() do
1722 |> cast(%{keys: pem}, [:keys])
1723 |> validate_required([:keys])
1724 |> update_and_set_cache()
1728 def get_ap_ids_by_nicknames(nicknames) do
1730 where: u.nickname in ^nicknames,
1736 defdelegate search(query, opts \\ []), to: User.Search
1738 defp put_password_hash(
1739 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1741 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1744 defp put_password_hash(changeset), do: changeset
1746 def is_internal_user?(%User{nickname: nil}), do: true
1747 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1748 def is_internal_user?(_), do: false
1750 # A hack because user delete activities have a fake id for whatever reason
1751 # TODO: Get rid of this
1752 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1754 def get_delivered_users_by_object_id(object_id) do
1756 inner_join: delivery in assoc(u, :deliveries),
1757 where: delivery.object_id == ^object_id
1762 def change_email(user, email) do
1764 |> cast(%{email: email}, [:email])
1765 |> validate_required([:email])
1766 |> unique_constraint(:email)
1767 |> validate_format(:email, @email_regex)
1768 |> update_and_set_cache()
1771 # Internal function; public one is `deactivate/2`
1772 defp set_activation_status(user, deactivated) do
1774 |> cast(%{deactivated: deactivated}, [:deactivated])
1775 |> update_and_set_cache()
1778 def update_banner(user, banner) do
1780 |> cast(%{banner: banner}, [:banner])
1781 |> update_and_set_cache()
1784 def update_background(user, background) do
1786 |> cast(%{background: background}, [:background])
1787 |> update_and_set_cache()
1790 def update_source_data(user, source_data) do
1792 |> cast(%{source_data: source_data}, [:source_data])
1793 |> update_and_set_cache()
1796 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1799 moderator: is_moderator
1803 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1804 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1805 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1806 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1809 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1810 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1814 def fields(%{fields: nil}), do: []
1816 def fields(%{fields: fields}), do: fields
1818 def validate_fields(changeset, remote? \\ false) do
1819 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1820 limit = Pleroma.Config.get([:instance, limit_name], 0)
1823 |> validate_length(:fields, max: limit)
1824 |> validate_change(:fields, fn :fields, fields ->
1825 if Enum.all?(fields, &valid_field?/1) do
1833 defp valid_field?(%{"name" => name, "value" => value}) do
1834 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1835 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1837 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1838 String.length(value) <= value_limit
1841 defp valid_field?(_), do: false
1843 defp truncate_field(%{"name" => name, "value" => value}) do
1845 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1848 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1850 %{"name" => name, "value" => value}
1853 def admin_api_update(user, params) do
1860 |> update_and_set_cache()
1863 def mascot_update(user, url) do
1865 |> cast(%{mascot: url}, [:mascot])
1866 |> validate_required([:mascot])
1867 |> update_and_set_cache()
1870 def mastodon_settings_update(user, settings) do
1872 |> cast(%{settings: settings}, [:settings])
1873 |> validate_required([:settings])
1874 |> update_and_set_cache()
1877 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1878 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1880 if need_confirmation? do
1882 confirmation_pending: true,
1883 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1887 confirmation_pending: false,
1888 confirmation_token: nil
1892 cast(user, params, [:confirmation_pending, :confirmation_token])
1895 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1896 if id not in user.pinned_activities do
1897 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1898 params = %{pinned_activities: user.pinned_activities ++ [id]}
1901 |> cast(params, [:pinned_activities])
1902 |> validate_length(:pinned_activities,
1903 max: max_pinned_statuses,
1904 message: "You have already pinned the maximum number of statuses"
1909 |> update_and_set_cache()
1912 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1913 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1916 |> cast(params, [:pinned_activities])
1917 |> update_and_set_cache()
1920 def update_email_notifications(user, settings) do
1921 email_notifications =
1922 user.email_notifications
1923 |> Map.merge(settings)
1924 |> Map.take(["digest"])
1926 params = %{email_notifications: email_notifications}
1927 fields = [:email_notifications]
1930 |> cast(params, fields)
1931 |> validate_required(fields)
1932 |> update_and_set_cache()
1935 defp set_domain_blocks(user, domain_blocks) do
1936 params = %{domain_blocks: domain_blocks}
1939 |> cast(params, [:domain_blocks])
1940 |> validate_required([:domain_blocks])
1941 |> update_and_set_cache()
1944 def block_domain(user, domain_blocked) do
1945 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1948 def unblock_domain(user, domain_blocked) do
1949 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1952 @spec add_to_block(User.t(), User.t()) ::
1953 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
1954 defp add_to_block(%User{} = user, %User{} = blocked) do
1955 UserRelationship.create_block(user, blocked)
1958 @spec add_to_block(User.t(), User.t()) ::
1959 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
1960 defp remove_from_block(%User{} = user, %User{} = blocked) do
1961 UserRelationship.delete_block(user, blocked)
1964 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
1965 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
1966 {:ok, user_notification_mute} <-
1967 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
1969 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
1973 defp remove_from_mutes(user, %User{} = muted_user) do
1974 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
1975 {:ok, user_notification_mute} <-
1976 UserRelationship.delete_notification_mute(user, muted_user) do
1977 {:ok, [user_mute, user_notification_mute]}
1981 def set_invisible(user, invisible) do
1982 params = %{invisible: invisible}
1985 |> cast(params, [:invisible])
1986 |> validate_required([:invisible])
1987 |> update_and_set_cache()