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: [])
133 :notification_settings,
134 Pleroma.User.NotificationSetting,
138 has_many(:notifications, Notification)
139 has_many(:registrations, Registration)
140 has_many(:deliveries, Delivery)
142 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
143 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
145 for {relationship_type,
147 {outgoing_relation, outgoing_relation_target},
148 {incoming_relation, incoming_relation_source}
149 ]} <- @user_relationships_config do
150 # Definitions of `has_many :blocker_blocks`, `has_many :muter_mutes` etc.
151 has_many(outgoing_relation, UserRelationship,
152 foreign_key: :source_id,
153 where: [relationship_type: relationship_type]
156 # Definitions of `has_many :blockee_blocks`, `has_many :mutee_mutes` etc.
157 has_many(incoming_relation, UserRelationship,
158 foreign_key: :target_id,
159 where: [relationship_type: relationship_type]
162 # Definitions of `has_many :blocked_users`, `has_many :muted_users` etc.
163 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
165 # Definitions of `has_many :blocker_users`, `has_many :muter_users` etc.
166 has_many(incoming_relation_source, through: [incoming_relation, :source])
169 # `:blocks` is deprecated (replaced with `blocked_users` relation)
170 field(:blocks, {:array, :string}, default: [])
171 # `:mutes` is deprecated (replaced with `muted_users` relation)
172 field(:mutes, {:array, :string}, default: [])
173 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
174 field(:muted_reblogs, {:array, :string}, default: [])
175 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
176 field(:muted_notifications, {:array, :string}, default: [])
177 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
178 field(:subscribers, {:array, :string}, default: [])
183 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
184 @user_relationships_config do
185 # Definitions of `blocked_users_relation/1`, `muted_users_relation/1`, etc.
186 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
187 target_users_query = assoc(user, unquote(outgoing_relation_target))
189 if restrict_deactivated? do
190 restrict_deactivated(target_users_query)
196 # Definitions of `blocked_users/1`, `muted_users/1`, etc.
197 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
199 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
201 restrict_deactivated?
206 # Definitions of `blocked_users_ap_ids/1`, `muted_users_ap_ids/1`, etc.
207 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
209 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
211 restrict_deactivated?
213 |> select([u], u.ap_id)
218 @doc "Returns if the user should be allowed to authenticate"
219 def auth_active?(%User{deactivated: true}), do: false
221 def auth_active?(%User{confirmation_pending: true}),
222 do: !Pleroma.Config.get([:instance, :account_activation_required])
224 def auth_active?(%User{}), do: true
226 def visible_for?(user, for_user \\ nil)
228 def visible_for?(%User{invisible: true}, _), do: false
230 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
232 def visible_for?(%User{} = user, for_user) do
233 auth_active?(user) || superuser?(for_user)
236 def visible_for?(_, _), do: false
238 def superuser?(%User{local: true, is_admin: true}), do: true
239 def superuser?(%User{local: true, is_moderator: true}), do: true
240 def superuser?(_), do: false
242 def invisible?(%User{invisible: true}), do: true
243 def invisible?(_), do: false
245 def avatar_url(user, options \\ []) do
247 %{"url" => [%{"href" => href} | _]} -> href
248 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
252 def banner_url(user, options \\ []) do
254 %{"url" => [%{"href" => href} | _]} -> href
255 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
259 def profile_url(%User{source_data: %{"url" => url}}), do: url
260 def profile_url(%User{ap_id: ap_id}), do: ap_id
261 def profile_url(_), do: nil
263 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
265 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
266 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
268 @spec ap_following(User.t()) :: Sring.t()
269 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
270 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
272 def follow_state(%User{} = user, %User{} = target) do
273 case Utils.fetch_latest_follow(user, target) do
274 %{data: %{"state" => state}} -> state
275 # Ideally this would be nil, but then Cachex does not commit the value
280 def get_cached_follow_state(user, target) do
281 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
282 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
285 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
286 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
287 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
290 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
291 def restrict_deactivated(query) do
292 from(u in query, where: u.deactivated != ^true)
295 defdelegate following_count(user), to: FollowingRelationship
297 defp truncate_fields_param(params) do
298 if Map.has_key?(params, :fields) do
299 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
305 defp truncate_if_exists(params, key, max_length) do
306 if Map.has_key?(params, key) and is_binary(params[key]) do
307 {value, _chopped} = String.split_at(params[key], max_length)
308 Map.put(params, key, value)
314 def remote_user_creation(params) do
315 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
316 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
320 |> truncate_if_exists(:name, name_limit)
321 |> truncate_if_exists(:bio, bio_limit)
322 |> truncate_fields_param()
342 :hide_followers_count,
352 |> validate_required([:name, :ap_id])
353 |> unique_constraint(:nickname)
354 |> validate_format(:nickname, @email_regex)
355 |> validate_length(:bio, max: bio_limit)
356 |> validate_length(:name, max: name_limit)
357 |> validate_fields(true)
359 case params[:source_data] do
360 %{"followers" => followers, "following" => following} ->
362 |> put_change(:follower_address, followers)
363 |> put_change(:following_address, following)
366 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
367 put_change(changeset, :follower_address, followers)
371 def update_changeset(struct, params \\ %{}) do
372 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
373 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
388 :hide_followers_count,
391 :allow_following_move,
394 :skip_thread_containment,
397 :pleroma_settings_store,
402 |> unique_constraint(:nickname)
403 |> validate_format(:nickname, local_nickname_regex())
404 |> validate_length(:bio, max: bio_limit)
405 |> validate_length(:name, min: 1, max: name_limit)
406 |> validate_fields(false)
409 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
410 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
411 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
413 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
415 params = if remote?, do: truncate_fields_param(params), else: params
437 :allow_following_move,
439 :hide_followers_count,
444 |> unique_constraint(:nickname)
445 |> validate_format(:nickname, local_nickname_regex())
446 |> validate_length(:bio, max: bio_limit)
447 |> validate_length(:name, max: name_limit)
448 |> validate_fields(remote?)
451 def password_update_changeset(struct, params) do
453 |> cast(params, [:password, :password_confirmation])
454 |> validate_required([:password, :password_confirmation])
455 |> validate_confirmation(:password)
456 |> put_password_hash()
457 |> put_change(:password_reset_pending, false)
460 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
461 def reset_password(%User{id: user_id} = user, data) do
464 |> Multi.update(:user, password_update_changeset(user, data))
465 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
466 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
468 case Repo.transaction(multi) do
469 {:ok, %{user: user} = _} -> set_cache(user)
470 {:error, _, changeset, _} -> {:error, changeset}
474 def update_password_reset_pending(user, value) do
477 |> put_change(:password_reset_pending, value)
478 |> update_and_set_cache()
481 def force_password_reset_async(user) do
482 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
485 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
486 def force_password_reset(user), do: update_password_reset_pending(user, true)
488 def register_changeset(struct, params \\ %{}, opts \\ []) do
489 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
490 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
493 if is_nil(opts[:need_confirmation]) do
494 Pleroma.Config.get([:instance, :account_activation_required])
496 opts[:need_confirmation]
500 |> confirmation_changeset(need_confirmation: need_confirmation?)
501 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
502 |> validate_required([:name, :nickname, :password, :password_confirmation])
503 |> validate_confirmation(:password)
504 |> unique_constraint(:email)
505 |> unique_constraint(:nickname)
506 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
507 |> validate_format(:nickname, local_nickname_regex())
508 |> validate_format(:email, @email_regex)
509 |> validate_length(:bio, max: bio_limit)
510 |> validate_length(:name, min: 1, max: name_limit)
511 |> maybe_validate_required_email(opts[:external])
514 |> unique_constraint(:ap_id)
515 |> put_following_and_follower_address()
518 def maybe_validate_required_email(changeset, true), do: changeset
519 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
521 defp put_ap_id(changeset) do
522 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
523 put_change(changeset, :ap_id, ap_id)
526 defp put_following_and_follower_address(changeset) do
527 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
530 |> put_change(:follower_address, followers)
533 defp autofollow_users(user) do
534 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
537 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
540 follow_all(user, autofollowed_users)
543 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
544 def register(%Ecto.Changeset{} = changeset) do
545 with {:ok, user} <- Repo.insert(changeset) do
546 post_register_action(user)
550 def post_register_action(%User{} = user) do
551 with {:ok, user} <- autofollow_users(user),
552 {:ok, user} <- set_cache(user),
553 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
554 {:ok, _} <- try_send_confirmation_email(user) do
559 def try_send_confirmation_email(%User{} = user) do
560 if user.confirmation_pending &&
561 Pleroma.Config.get([:instance, :account_activation_required]) do
563 |> Pleroma.Emails.UserEmail.account_confirmation_email()
564 |> Pleroma.Emails.Mailer.deliver_async()
572 def try_send_confirmation_email(users) do
573 Enum.each(users, &try_send_confirmation_email/1)
576 def needs_update?(%User{local: true}), do: false
578 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
580 def needs_update?(%User{local: false} = user) do
581 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
584 def needs_update?(_), do: true
586 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
587 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
588 follow(follower, followed, "pending")
591 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
592 follow(follower, followed)
595 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
596 if not ap_enabled?(followed) do
597 follow(follower, followed)
603 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
604 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
605 def follow_all(follower, followeds) do
607 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
608 |> Enum.each(&follow(follower, &1, "accept"))
613 defdelegate following(user), to: FollowingRelationship
615 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
616 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
619 followed.deactivated ->
620 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
622 deny_follow_blocked and blocks?(followed, follower) ->
623 {:error, "Could not follow user: #{followed.nickname} blocked you."}
626 FollowingRelationship.follow(follower, followed, state)
628 {:ok, _} = update_follower_count(followed)
631 |> update_following_count()
636 def unfollow(%User{} = follower, %User{} = followed) do
637 if following?(follower, followed) and follower.ap_id != followed.ap_id do
638 FollowingRelationship.unfollow(follower, followed)
640 {:ok, followed} = update_follower_count(followed)
644 |> update_following_count()
647 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
649 {:error, "Not subscribed!"}
653 defdelegate following?(follower, followed), to: FollowingRelationship
655 def locked?(%User{} = user) do
660 Repo.get_by(User, id: id)
663 def get_by_ap_id(ap_id) do
664 Repo.get_by(User, ap_id: ap_id)
667 def get_all_by_ap_id(ap_ids) do
668 from(u in __MODULE__,
669 where: u.ap_id in ^ap_ids
674 def get_all_by_ids(ids) do
675 from(u in __MODULE__, where: u.id in ^ids)
679 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
680 # of the ap_id and the domain and tries to get that user
681 def get_by_guessed_nickname(ap_id) do
682 domain = URI.parse(ap_id).host
683 name = List.last(String.split(ap_id, "/"))
684 nickname = "#{name}@#{domain}"
686 get_cached_by_nickname(nickname)
689 def set_cache({:ok, user}), do: set_cache(user)
690 def set_cache({:error, err}), do: {:error, err}
692 def set_cache(%User{} = user) do
693 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
694 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
698 def update_and_set_cache(struct, params) do
700 |> update_changeset(params)
701 |> update_and_set_cache()
704 def update_and_set_cache(changeset) do
705 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
710 def invalidate_cache(user) do
711 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
712 Cachex.del(:user_cache, "nickname:#{user.nickname}")
715 def get_cached_by_ap_id(ap_id) do
716 key = "ap_id:#{ap_id}"
717 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
720 def get_cached_by_id(id) do
724 Cachex.fetch!(:user_cache, key, fn _ ->
728 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
729 {:commit, user.ap_id}
735 get_cached_by_ap_id(ap_id)
738 def get_cached_by_nickname(nickname) do
739 key = "nickname:#{nickname}"
741 Cachex.fetch!(:user_cache, key, fn ->
742 case get_or_fetch_by_nickname(nickname) do
743 {:ok, user} -> {:commit, user}
744 {:error, _error} -> {:ignore, nil}
749 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
750 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
753 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
754 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
756 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
757 get_cached_by_nickname(nickname_or_id)
759 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
760 get_cached_by_nickname(nickname_or_id)
767 def get_by_nickname(nickname) do
768 Repo.get_by(User, nickname: nickname) ||
769 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
770 Repo.get_by(User, nickname: local_nickname(nickname))
774 def get_by_email(email), do: Repo.get_by(User, email: email)
776 def get_by_nickname_or_email(nickname_or_email) do
777 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
780 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
782 def get_or_fetch_by_nickname(nickname) do
783 with %User{} = user <- get_by_nickname(nickname) do
787 with [_nick, _domain] <- String.split(nickname, "@"),
788 {:ok, user} <- fetch_by_nickname(nickname) do
789 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
790 fetch_initial_posts(user)
795 _e -> {:error, "not found " <> nickname}
800 @doc "Fetch some posts when the user has just been federated with"
801 def fetch_initial_posts(user) do
802 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
805 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
806 def get_followers_query(%User{} = user, nil) do
807 User.Query.build(%{followers: user, deactivated: false})
810 def get_followers_query(user, page) do
812 |> get_followers_query(nil)
813 |> User.Query.paginate(page, 20)
816 @spec get_followers_query(User.t()) :: Ecto.Query.t()
817 def get_followers_query(user), do: get_followers_query(user, nil)
819 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
820 def get_followers(user, page \\ nil) do
822 |> get_followers_query(page)
826 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
827 def get_external_followers(user, page \\ nil) do
829 |> get_followers_query(page)
830 |> User.Query.build(%{external: true})
834 def get_followers_ids(user, page \\ nil) do
836 |> get_followers_query(page)
841 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
842 def get_friends_query(%User{} = user, nil) do
843 User.Query.build(%{friends: user, deactivated: false})
846 def get_friends_query(user, page) do
848 |> get_friends_query(nil)
849 |> User.Query.paginate(page, 20)
852 @spec get_friends_query(User.t()) :: Ecto.Query.t()
853 def get_friends_query(user), do: get_friends_query(user, nil)
855 def get_friends(user, page \\ nil) do
857 |> get_friends_query(page)
861 def get_friends_ids(user, page \\ nil) do
863 |> get_friends_query(page)
868 defdelegate get_follow_requests(user), to: FollowingRelationship
870 def increase_note_count(%User{} = user) do
872 |> where(id: ^user.id)
873 |> update([u], inc: [note_count: 1])
875 |> Repo.update_all([])
877 {1, [user]} -> set_cache(user)
882 def decrease_note_count(%User{} = user) do
884 |> where(id: ^user.id)
887 note_count: fragment("greatest(0, note_count - 1)")
891 |> Repo.update_all([])
893 {1, [user]} -> set_cache(user)
898 def update_note_count(%User{} = user, note_count \\ nil) do
903 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
909 |> cast(%{note_count: note_count}, [:note_count])
910 |> update_and_set_cache()
913 @spec maybe_fetch_follow_information(User.t()) :: User.t()
914 def maybe_fetch_follow_information(user) do
915 with {:ok, user} <- fetch_follow_information(user) do
919 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
925 def fetch_follow_information(user) do
926 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
928 |> follow_information_changeset(info)
929 |> update_and_set_cache()
933 defp follow_information_changeset(user, params) do
940 :hide_followers_count,
945 def update_follower_count(%User{} = user) do
946 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
947 follower_count_query =
948 User.Query.build(%{followers: user, deactivated: false})
949 |> select([u], %{count: count(u.id)})
952 |> where(id: ^user.id)
953 |> join(:inner, [u], s in subquery(follower_count_query))
955 set: [follower_count: s.count]
958 |> Repo.update_all([])
960 {1, [user]} -> set_cache(user)
964 {:ok, maybe_fetch_follow_information(user)}
968 @spec update_following_count(User.t()) :: User.t()
969 def update_following_count(%User{local: false} = user) do
970 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
971 maybe_fetch_follow_information(user)
977 def update_following_count(%User{local: true} = user) do
978 following_count = FollowingRelationship.following_count(user)
981 |> follow_information_changeset(%{following_count: following_count})
985 def set_unread_conversation_count(%User{local: true} = user) do
986 unread_query = Participation.unread_conversation_count_for_user(user)
989 |> join(:inner, [u], p in subquery(unread_query))
991 set: [unread_conversation_count: p.count]
993 |> where([u], u.id == ^user.id)
995 |> Repo.update_all([])
997 {1, [user]} -> set_cache(user)
1002 def set_unread_conversation_count(user), do: {:ok, user}
1004 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1006 Participation.unread_conversation_count_for_user(user)
1007 |> where([p], p.conversation_id == ^conversation.id)
1010 |> join(:inner, [u], p in subquery(unread_query))
1012 inc: [unread_conversation_count: 1]
1014 |> where([u], u.id == ^user.id)
1015 |> where([u, p], p.count == 0)
1017 |> Repo.update_all([])
1019 {1, [user]} -> set_cache(user)
1024 def increment_unread_conversation_count(_, user), do: {:ok, user}
1026 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1027 def get_users_from_set(ap_ids, local_only \\ true) do
1028 criteria = %{ap_id: ap_ids, deactivated: false}
1029 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1031 User.Query.build(criteria)
1035 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1036 def get_recipients_from_activity(%Activity{recipients: to}) do
1037 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1041 @spec mute(User.t(), User.t(), boolean()) ::
1042 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1043 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1044 add_to_mutes(muter, mutee, notifications?)
1047 def unmute(%User{} = muter, %User{} = mutee) do
1048 remove_from_mutes(muter, mutee)
1051 def subscribe(%User{} = subscriber, %User{} = target) do
1052 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1054 if blocks?(target, subscriber) and deny_follow_blocked do
1055 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1057 # Note: the relationship is inverse: subscriber acts as relationship target
1058 UserRelationship.create_inverse_subscription(target, subscriber)
1062 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1063 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1064 subscribe(subscriber, subscribee)
1068 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1069 # Note: the relationship is inverse: subscriber acts as relationship target
1070 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1073 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1074 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1075 unsubscribe(unsubscriber, user)
1079 def block(%User{} = blocker, %User{} = blocked) do
1080 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1082 if following?(blocker, blocked) do
1083 {:ok, blocker, _} = unfollow(blocker, blocked)
1089 # clear any requested follows as well
1091 case CommonAPI.reject_follow_request(blocked, blocker) do
1092 {:ok, %User{} = updated_blocked} -> updated_blocked
1096 unsubscribe(blocked, blocker)
1098 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1100 {:ok, blocker} = update_follower_count(blocker)
1101 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1102 add_to_block(blocker, blocked)
1105 # helper to handle the block given only an actor's AP id
1106 def block(%User{} = blocker, %{ap_id: ap_id}) do
1107 block(blocker, get_cached_by_ap_id(ap_id))
1110 def unblock(%User{} = blocker, %User{} = blocked) do
1111 remove_from_block(blocker, blocked)
1114 # helper to handle the block given only an actor's AP id
1115 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1116 unblock(blocker, get_cached_by_ap_id(ap_id))
1119 def mutes?(nil, _), do: false
1120 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1122 def mutes_user?(%User{} = user, %User{} = target) do
1123 UserRelationship.mute_exists?(user, target)
1126 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1127 def muted_notifications?(nil, _), do: false
1129 def muted_notifications?(%User{} = user, %User{} = target),
1130 do: UserRelationship.notification_mute_exists?(user, target)
1132 def blocks?(nil, _), do: false
1134 def blocks?(%User{} = user, %User{} = target) do
1135 blocks_user?(user, target) || blocks_domain?(user, target)
1138 def blocks_user?(%User{} = user, %User{} = target) do
1139 UserRelationship.block_exists?(user, target)
1142 def blocks_user?(_, _), do: false
1144 def blocks_domain?(%User{} = user, %User{} = target) do
1145 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1146 %{host: host} = URI.parse(target.ap_id)
1147 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1150 def blocks_domain?(_, _), do: false
1152 def subscribed_to?(%User{} = user, %User{} = target) do
1153 # Note: the relationship is inverse: subscriber acts as relationship target
1154 UserRelationship.inverse_subscription_exists?(target, user)
1157 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1158 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1159 subscribed_to?(user, target)
1164 Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
1165 E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1167 @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1168 def outgoing_relations_ap_ids(_, []), do: %{}
1170 def outgoing_relations_ap_ids(%User{} = user, relationship_types)
1171 when is_list(relationship_types) do
1174 |> assoc(:outgoing_relationships)
1175 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1176 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1177 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1178 |> group_by([user_rel, u], user_rel.relationship_type)
1180 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1185 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1189 def deactivate_async(user, status \\ true) do
1190 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1193 def deactivate(user, status \\ true)
1195 def deactivate(users, status) when is_list(users) do
1196 Repo.transaction(fn ->
1197 for user <- users, do: deactivate(user, status)
1201 def deactivate(%User{} = user, status) do
1202 with {:ok, user} <- set_activation_status(user, status) do
1205 |> Enum.filter(& &1.local)
1206 |> Enum.each(fn follower ->
1207 follower |> update_following_count() |> set_cache()
1210 # Only update local user counts, remote will be update during the next pull.
1213 |> Enum.filter(& &1.local)
1214 |> Enum.each(&update_follower_count/1)
1220 def update_notification_settings(%User{} = user, settings) do
1222 |> cast(%{notification_settings: settings}, [])
1223 |> cast_embed(:notification_settings)
1224 |> validate_required([:notification_settings])
1225 |> update_and_set_cache()
1228 def delete(users) when is_list(users) do
1229 for user <- users, do: delete(user)
1232 def delete(%User{} = user) do
1233 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1236 def perform(:force_password_reset, user), do: force_password_reset(user)
1238 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1239 def perform(:delete, %User{} = user) do
1240 {:ok, _user} = ActivityPub.delete(user)
1242 # Remove all relationships
1245 |> Enum.each(fn follower ->
1246 ActivityPub.unfollow(follower, user)
1247 unfollow(follower, user)
1252 |> Enum.each(fn followed ->
1253 ActivityPub.unfollow(user, followed)
1254 unfollow(user, followed)
1257 delete_user_activities(user)
1258 invalidate_cache(user)
1262 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1263 def perform(:fetch_initial_posts, %User{} = user) do
1264 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1266 # Insert all the posts in reverse order, so they're in the right order on the timeline
1267 user.source_data["outbox"]
1268 |> Utils.fetch_ordered_collection(pages)
1270 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1273 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1275 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1276 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1277 when is_list(blocked_identifiers) do
1279 blocked_identifiers,
1280 fn blocked_identifier ->
1281 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1282 {:ok, _user_block} <- block(blocker, blocked),
1283 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1287 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1294 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1295 def perform(:follow_import, %User{} = follower, followed_identifiers)
1296 when is_list(followed_identifiers) do
1298 followed_identifiers,
1299 fn followed_identifier ->
1300 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1301 {:ok, follower} <- maybe_direct_follow(follower, followed),
1302 {:ok, _} <- ActivityPub.follow(follower, followed) do
1306 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1313 @spec external_users_query() :: Ecto.Query.t()
1314 def external_users_query do
1322 @spec external_users(keyword()) :: [User.t()]
1323 def external_users(opts \\ []) do
1325 external_users_query()
1326 |> select([u], struct(u, [:id, :ap_id]))
1330 do: where(query, [u], u.id > ^opts[:max_id]),
1335 do: limit(query, ^opts[:limit]),
1341 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1342 BackgroundWorker.enqueue("blocks_import", %{
1343 "blocker_id" => blocker.id,
1344 "blocked_identifiers" => blocked_identifiers
1348 def follow_import(%User{} = follower, followed_identifiers)
1349 when is_list(followed_identifiers) do
1350 BackgroundWorker.enqueue("follow_import", %{
1351 "follower_id" => follower.id,
1352 "followed_identifiers" => followed_identifiers
1356 def delete_user_activities(%User{ap_id: ap_id}) do
1358 |> Activity.Queries.by_actor()
1359 |> RepoStreamer.chunk_stream(50)
1360 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1364 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1366 |> Object.normalize()
1367 |> ActivityPub.delete()
1370 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1371 object = Object.normalize(activity)
1374 |> get_cached_by_ap_id()
1375 |> ActivityPub.unlike(object)
1378 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1379 object = Object.normalize(activity)
1382 |> get_cached_by_ap_id()
1383 |> ActivityPub.unannounce(object)
1386 defp delete_activity(_activity), do: "Doing nothing"
1388 def html_filter_policy(%User{no_rich_text: true}) do
1389 Pleroma.HTML.Scrubber.TwitterText
1392 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1394 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1396 def get_or_fetch_by_ap_id(ap_id) do
1397 user = get_cached_by_ap_id(ap_id)
1399 if !is_nil(user) and !needs_update?(user) do
1402 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1403 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1405 resp = fetch_by_ap_id(ap_id)
1407 if should_fetch_initial do
1408 with {:ok, %User{} = user} <- resp do
1409 fetch_initial_posts(user)
1418 Creates an internal service actor by URI if missing.
1419 Optionally takes nickname for addressing.
1421 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1422 with user when is_nil(user) <- get_cached_by_ap_id(uri) do
1429 follower_address: uri <> "/followers"
1438 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1441 |> :public_key.pem_decode()
1443 |> :public_key.pem_entry_decode()
1448 def public_key(_), do: {:error, "not found key"}
1450 def get_public_key_for_ap_id(ap_id) do
1451 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1452 {:ok, public_key} <- public_key(user) do
1459 defp blank?(""), do: nil
1460 defp blank?(n), do: n
1462 def insert_or_update_user(data) do
1464 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1465 |> remote_user_creation()
1466 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1470 def ap_enabled?(%User{local: true}), do: true
1471 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1472 def ap_enabled?(_), do: false
1474 @doc "Gets or fetch a user by uri or nickname."
1475 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1476 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1477 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1479 # wait a period of time and return newest version of the User structs
1480 # this is because we have synchronous follow APIs and need to simulate them
1481 # with an async handshake
1482 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1483 with %User{} = a <- get_cached_by_id(a.id),
1484 %User{} = b <- get_cached_by_id(b.id) do
1491 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1492 with :ok <- :timer.sleep(timeout),
1493 %User{} = a <- get_cached_by_id(a.id),
1494 %User{} = b <- get_cached_by_id(b.id) do
1501 def parse_bio(bio) when is_binary(bio) and bio != "" do
1503 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1507 def parse_bio(_), do: ""
1509 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1510 # TODO: get profile URLs other than user.ap_id
1511 profile_urls = [user.ap_id]
1514 |> CommonUtils.format_input("text/plain",
1515 mentions_format: :full,
1516 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1521 def parse_bio(_, _), do: ""
1523 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1524 Repo.transaction(fn ->
1525 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1529 def tag(nickname, tags) when is_binary(nickname),
1530 do: tag(get_by_nickname(nickname), tags)
1532 def tag(%User{} = user, tags),
1533 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1535 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1536 Repo.transaction(fn ->
1537 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1541 def untag(nickname, tags) when is_binary(nickname),
1542 do: untag(get_by_nickname(nickname), tags)
1544 def untag(%User{} = user, tags),
1545 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1547 defp update_tags(%User{} = user, new_tags) do
1548 {:ok, updated_user} =
1550 |> change(%{tags: new_tags})
1551 |> update_and_set_cache()
1556 defp normalize_tags(tags) do
1559 |> Enum.map(&String.downcase/1)
1562 defp local_nickname_regex do
1563 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1564 @extended_local_nickname_regex
1566 @strict_local_nickname_regex
1570 def local_nickname(nickname_or_mention) do
1573 |> String.split("@")
1577 def full_nickname(nickname_or_mention),
1578 do: String.trim_leading(nickname_or_mention, "@")
1580 def error_user(ap_id) do
1584 nickname: "erroruser@example.com",
1585 inserted_at: NaiveDateTime.utc_now()
1589 @spec all_superusers() :: [User.t()]
1590 def all_superusers do
1591 User.Query.build(%{super_users: true, local: true, deactivated: false})
1595 def showing_reblogs?(%User{} = user, %User{} = target) do
1596 not UserRelationship.reblog_mute_exists?(user, target)
1600 The function returns a query to get users with no activity for given interval of days.
1601 Inactive users are those who didn't read any notification, or had any activity where
1602 the user is the activity's actor, during `inactivity_threshold` days.
1603 Deactivated users will not appear in this list.
1607 iex> Pleroma.User.list_inactive_users()
1610 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1611 def list_inactive_users_query(inactivity_threshold \\ 7) do
1612 negative_inactivity_threshold = -inactivity_threshold
1613 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1614 # Subqueries are not supported in `where` clauses, join gets too complicated.
1615 has_read_notifications =
1616 from(n in Pleroma.Notification,
1617 where: n.seen == true,
1619 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1622 |> Pleroma.Repo.all()
1624 from(u in Pleroma.User,
1625 left_join: a in Pleroma.Activity,
1626 on: u.ap_id == a.actor,
1627 where: not is_nil(u.nickname),
1628 where: u.deactivated != ^true,
1629 where: u.id not in ^has_read_notifications,
1632 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1633 is_nil(max(a.inserted_at))
1638 Enable or disable email notifications for user
1642 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1643 Pleroma.User{email_notifications: %{"digest" => true}}
1645 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1646 Pleroma.User{email_notifications: %{"digest" => false}}
1648 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1649 {:ok, t()} | {:error, Ecto.Changeset.t()}
1650 def switch_email_notifications(user, type, status) do
1651 User.update_email_notifications(user, %{type => status})
1655 Set `last_digest_emailed_at` value for the user to current time
1657 @spec touch_last_digest_emailed_at(t()) :: t()
1658 def touch_last_digest_emailed_at(user) do
1659 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1661 {:ok, updated_user} =
1663 |> change(%{last_digest_emailed_at: now})
1664 |> update_and_set_cache()
1669 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1670 def toggle_confirmation(%User{} = user) do
1672 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1673 |> update_and_set_cache()
1676 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1677 def toggle_confirmation(users) do
1678 Enum.map(users, &toggle_confirmation/1)
1681 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1685 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1686 # use instance-default
1687 config = Pleroma.Config.get([:assets, :mascots])
1688 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1689 mascot = Keyword.get(config, default_mascot)
1692 "id" => "default-mascot",
1693 "url" => mascot[:url],
1694 "preview_url" => mascot[:url],
1696 "mime_type" => mascot[:mime_type]
1701 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1703 def ensure_keys_present(%User{} = user) do
1704 with {:ok, pem} <- Keys.generate_rsa_pem() do
1706 |> cast(%{keys: pem}, [:keys])
1707 |> validate_required([:keys])
1708 |> update_and_set_cache()
1712 def get_ap_ids_by_nicknames(nicknames) do
1714 where: u.nickname in ^nicknames,
1720 defdelegate search(query, opts \\ []), to: User.Search
1722 defp put_password_hash(
1723 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1725 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1728 defp put_password_hash(changeset), do: changeset
1730 def is_internal_user?(%User{nickname: nil}), do: true
1731 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1732 def is_internal_user?(_), do: false
1734 # A hack because user delete activities have a fake id for whatever reason
1735 # TODO: Get rid of this
1736 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1738 def get_delivered_users_by_object_id(object_id) do
1740 inner_join: delivery in assoc(u, :deliveries),
1741 where: delivery.object_id == ^object_id
1746 def change_email(user, email) do
1748 |> cast(%{email: email}, [:email])
1749 |> validate_required([:email])
1750 |> unique_constraint(:email)
1751 |> validate_format(:email, @email_regex)
1752 |> update_and_set_cache()
1755 # Internal function; public one is `deactivate/2`
1756 defp set_activation_status(user, deactivated) do
1758 |> cast(%{deactivated: deactivated}, [:deactivated])
1759 |> update_and_set_cache()
1762 def update_banner(user, banner) do
1764 |> cast(%{banner: banner}, [:banner])
1765 |> update_and_set_cache()
1768 def update_background(user, background) do
1770 |> cast(%{background: background}, [:background])
1771 |> update_and_set_cache()
1774 def update_source_data(user, source_data) do
1776 |> cast(%{source_data: source_data}, [:source_data])
1777 |> update_and_set_cache()
1780 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1783 moderator: is_moderator
1787 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1788 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1789 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1790 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1793 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1794 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1798 def fields(%{fields: nil}), do: []
1800 def fields(%{fields: fields}), do: fields
1802 def validate_fields(changeset, remote? \\ false) do
1803 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1804 limit = Pleroma.Config.get([:instance, limit_name], 0)
1807 |> validate_length(:fields, max: limit)
1808 |> validate_change(:fields, fn :fields, fields ->
1809 if Enum.all?(fields, &valid_field?/1) do
1817 defp valid_field?(%{"name" => name, "value" => value}) do
1818 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1819 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1821 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1822 String.length(value) <= value_limit
1825 defp valid_field?(_), do: false
1827 defp truncate_field(%{"name" => name, "value" => value}) do
1829 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1832 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1834 %{"name" => name, "value" => value}
1837 def admin_api_update(user, params) do
1844 |> update_and_set_cache()
1847 def mascot_update(user, url) do
1849 |> cast(%{mascot: url}, [:mascot])
1850 |> validate_required([:mascot])
1851 |> update_and_set_cache()
1854 def mastodon_settings_update(user, settings) do
1856 |> cast(%{settings: settings}, [:settings])
1857 |> validate_required([:settings])
1858 |> update_and_set_cache()
1861 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1862 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1864 if need_confirmation? do
1866 confirmation_pending: true,
1867 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1871 confirmation_pending: false,
1872 confirmation_token: nil
1876 cast(user, params, [:confirmation_pending, :confirmation_token])
1879 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1880 if id not in user.pinned_activities do
1881 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1882 params = %{pinned_activities: user.pinned_activities ++ [id]}
1885 |> cast(params, [:pinned_activities])
1886 |> validate_length(:pinned_activities,
1887 max: max_pinned_statuses,
1888 message: "You have already pinned the maximum number of statuses"
1893 |> update_and_set_cache()
1896 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1897 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1900 |> cast(params, [:pinned_activities])
1901 |> update_and_set_cache()
1904 def update_email_notifications(user, settings) do
1905 email_notifications =
1906 user.email_notifications
1907 |> Map.merge(settings)
1908 |> Map.take(["digest"])
1910 params = %{email_notifications: email_notifications}
1911 fields = [:email_notifications]
1914 |> cast(params, fields)
1915 |> validate_required(fields)
1916 |> update_and_set_cache()
1919 defp set_domain_blocks(user, domain_blocks) do
1920 params = %{domain_blocks: domain_blocks}
1923 |> cast(params, [:domain_blocks])
1924 |> validate_required([:domain_blocks])
1925 |> update_and_set_cache()
1928 def block_domain(user, domain_blocked) do
1929 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1932 def unblock_domain(user, domain_blocked) do
1933 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1936 @spec add_to_block(User.t(), User.t()) ::
1937 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
1938 defp add_to_block(%User{} = user, %User{} = blocked) do
1939 UserRelationship.create_block(user, blocked)
1942 @spec add_to_block(User.t(), User.t()) ::
1943 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
1944 defp remove_from_block(%User{} = user, %User{} = blocked) do
1945 UserRelationship.delete_block(user, blocked)
1948 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
1949 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
1950 {:ok, user_notification_mute} <-
1951 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
1953 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
1957 defp remove_from_mutes(user, %User{} = muted_user) do
1958 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
1959 {:ok, user_notification_mute} <-
1960 UserRelationship.delete_notification_mute(user, muted_user) do
1961 {:ok, [user_mute, user_notification_mute]}
1965 def set_invisible(user, invisible) do
1966 params = %{invisible: invisible}
1969 |> cast(params, [:invisible])
1970 |> validate_required([:invisible])
1971 |> update_and_set_cache()