1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.User do
10 import Ecto, only: [assoc: 2]
14 alias Pleroma.Activity
16 alias Pleroma.Conversation.Participation
17 alias Pleroma.Delivery
18 alias Pleroma.FollowingRelationship
21 alias Pleroma.Notification
23 alias Pleroma.Registration
25 alias Pleroma.RepoStreamer
27 alias Pleroma.UserRelationship
29 alias Pleroma.Web.ActivityPub.ActivityPub
30 alias Pleroma.Web.ActivityPub.Utils
31 alias Pleroma.Web.CommonAPI
32 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
33 alias Pleroma.Web.OAuth
34 alias Pleroma.Web.RelMe
35 alias Pleroma.Workers.BackgroundWorker
39 @type t :: %__MODULE__{}
40 @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
41 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
43 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
44 @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])?)*$/
46 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
47 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
49 # AP ID user relationships (blocks, mutes etc.)
50 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
51 @user_relationships_config [
53 blocker_blocks: :blocked_users,
54 blockee_blocks: :blocker_users
57 muter_mutes: :muted_users,
58 mutee_mutes: :muter_users
61 reblog_muter_mutes: :reblog_muted_users,
62 reblog_mutee_mutes: :reblog_muter_users
65 notification_muter_mutes: :notification_muted_users,
66 notification_mutee_mutes: :notification_muter_users
68 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
69 inverse_subscription: [
70 subscribee_subscriptions: :subscriber_users,
71 subscriber_subscriptions: :subscribee_users
77 field(:email, :string)
79 field(:nickname, :string)
80 field(:password_hash, :string)
81 field(:password, :string, virtual: true)
82 field(:password_confirmation, :string, virtual: true)
84 field(:ap_id, :string)
86 field(:local, :boolean, default: true)
87 field(:follower_address, :string)
88 field(:following_address, :string)
89 field(:search_rank, :float, virtual: true)
90 field(:search_type, :integer, virtual: true)
91 field(:tags, {:array, :string}, default: [])
92 field(:last_refreshed_at, :naive_datetime_usec)
93 field(:last_digest_emailed_at, :naive_datetime)
94 field(:banner, :map, default: %{})
95 field(:background, :map, default: %{})
96 field(:source_data, :map, default: %{})
97 field(:note_count, :integer, default: 0)
98 field(:follower_count, :integer, default: 0)
99 field(:following_count, :integer, default: 0)
100 field(:locked, :boolean, default: false)
101 field(:confirmation_pending, :boolean, default: false)
102 field(:password_reset_pending, :boolean, default: false)
103 field(:confirmation_token, :string, default: nil)
104 field(:default_scope, :string, default: "public")
105 field(:domain_blocks, {:array, :string}, default: [])
106 field(:deactivated, :boolean, default: false)
107 field(:no_rich_text, :boolean, default: false)
108 field(:ap_enabled, :boolean, default: false)
109 field(:is_moderator, :boolean, default: false)
110 field(:is_admin, :boolean, default: false)
111 field(:show_role, :boolean, default: true)
112 field(:settings, :map, default: nil)
113 field(:magic_key, :string, default: nil)
114 field(:uri, :string, default: nil)
115 field(:hide_followers_count, :boolean, default: false)
116 field(:hide_follows_count, :boolean, default: false)
117 field(:hide_followers, :boolean, default: false)
118 field(:hide_follows, :boolean, default: false)
119 field(:hide_favorites, :boolean, default: true)
120 field(:unread_conversation_count, :integer, default: 0)
121 field(:pinned_activities, {:array, :string}, default: [])
122 field(:email_notifications, :map, default: %{"digest" => false})
123 field(:mascot, :map, default: nil)
124 field(:emoji, {:array, :map}, default: [])
125 field(:pleroma_settings_store, :map, default: %{})
126 field(:fields, {:array, :map}, default: [])
127 field(:raw_fields, {:array, :map}, default: [])
128 field(:discoverable, :boolean, default: false)
129 field(:invisible, :boolean, default: false)
130 field(:allow_following_move, :boolean, default: true)
131 field(:skip_thread_containment, :boolean, default: false)
132 field(:actor_type, :string, default: "Person")
133 field(:also_known_as, {:array, :string}, default: [])
136 :notification_settings,
137 Pleroma.User.NotificationSetting,
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` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
154 # :notification_muter_mutes, :subscribee_subscriptions
155 has_many(outgoing_relation, UserRelationship,
156 foreign_key: :source_id,
157 where: [relationship_type: relationship_type]
160 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
161 # :notification_mutee_mutes, :subscriber_subscriptions
162 has_many(incoming_relation, UserRelationship,
163 foreign_key: :target_id,
164 where: [relationship_type: relationship_type]
167 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
168 # :notification_muted_users, :subscriber_users
169 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
171 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
172 # :notification_muter_users, :subscribee_users
173 has_many(incoming_relation_source, through: [incoming_relation, :source])
176 # `:blocks` is deprecated (replaced with `blocked_users` relation)
177 field(:blocks, {:array, :string}, default: [])
178 # `:mutes` is deprecated (replaced with `muted_users` relation)
179 field(:mutes, {:array, :string}, default: [])
180 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
181 field(:muted_reblogs, {:array, :string}, default: [])
182 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
183 field(:muted_notifications, {:array, :string}, default: [])
184 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
185 field(:subscribers, {:array, :string}, default: [])
190 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
191 @user_relationships_config do
192 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
193 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
194 # `def subscriber_users/2`
195 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
196 target_users_query = assoc(user, unquote(outgoing_relation_target))
198 if restrict_deactivated? do
199 restrict_deactivated(target_users_query)
205 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
206 # `def notification_muted_users/2`, `def subscriber_users/2`
207 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
209 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
211 restrict_deactivated?
216 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
217 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
218 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
220 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
222 restrict_deactivated?
224 |> select([u], u.ap_id)
229 @doc "Returns status account"
230 @spec account_status(User.t()) :: account_status()
231 def account_status(%User{deactivated: true}), do: :deactivated
232 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
234 def account_status(%User{confirmation_pending: true}) do
235 case Config.get([:instance, :account_activation_required]) do
236 true -> :confirmation_pending
241 def account_status(%User{}), do: :active
243 @spec visible_for?(User.t(), User.t() | nil) :: boolean()
244 def visible_for?(user, for_user \\ nil)
246 def visible_for?(%User{invisible: true}, _), do: false
248 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
250 def visible_for?(%User{} = user, for_user) do
251 account_status(user) == :active || superuser?(for_user)
254 def visible_for?(_, _), do: false
256 @spec superuser?(User.t()) :: boolean()
257 def superuser?(%User{local: true, is_admin: true}), do: true
258 def superuser?(%User{local: true, is_moderator: true}), do: true
259 def superuser?(_), do: false
261 @spec invisible?(User.t()) :: boolean()
262 def invisible?(%User{invisible: true}), do: true
263 def invisible?(_), do: false
265 def avatar_url(user, options \\ []) do
267 %{"url" => [%{"href" => href} | _]} -> href
268 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
272 def banner_url(user, options \\ []) do
274 %{"url" => [%{"href" => href} | _]} -> href
275 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
279 def profile_url(%User{source_data: %{"url" => url}}), do: url
280 def profile_url(%User{ap_id: ap_id}), do: ap_id
281 def profile_url(_), do: nil
283 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
285 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
286 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
288 @spec ap_following(User.t()) :: Sring.t()
289 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
290 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
292 def follow_state(%User{} = user, %User{} = target) do
293 case Utils.fetch_latest_follow(user, target) do
294 %{data: %{"state" => state}} -> state
295 # Ideally this would be nil, but then Cachex does not commit the value
300 def get_cached_follow_state(user, target) do
301 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
302 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
305 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
306 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
307 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
310 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
311 def restrict_deactivated(query) do
312 from(u in query, where: u.deactivated != ^true)
315 defdelegate following_count(user), to: FollowingRelationship
317 defp truncate_fields_param(params) do
318 if Map.has_key?(params, :fields) do
319 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
325 defp truncate_if_exists(params, key, max_length) do
326 if Map.has_key?(params, key) and is_binary(params[key]) do
327 {value, _chopped} = String.split_at(params[key], max_length)
328 Map.put(params, key, value)
334 def remote_user_creation(params) do
335 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
336 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
340 |> truncate_if_exists(:name, name_limit)
341 |> truncate_if_exists(:bio, bio_limit)
342 |> truncate_fields_param()
362 :hide_followers_count,
373 |> validate_required([:name, :ap_id])
374 |> unique_constraint(:nickname)
375 |> validate_format(:nickname, @email_regex)
376 |> validate_length(:bio, max: bio_limit)
377 |> validate_length(:name, max: name_limit)
378 |> validate_fields(true)
380 case params[:source_data] do
381 %{"followers" => followers, "following" => following} ->
383 |> put_change(:follower_address, followers)
384 |> put_change(:following_address, following)
387 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
388 put_change(changeset, :follower_address, followers)
392 def update_changeset(struct, params \\ %{}) do
393 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
394 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
409 :hide_followers_count,
412 :allow_following_move,
415 :skip_thread_containment,
418 :pleroma_settings_store,
424 |> unique_constraint(:nickname)
425 |> validate_format(:nickname, local_nickname_regex())
426 |> validate_length(:bio, max: bio_limit)
427 |> validate_length(:name, min: 1, max: name_limit)
428 |> validate_fields(false)
431 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
432 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
433 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
435 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
437 params = if remote?, do: truncate_fields_param(params), else: params
459 :allow_following_move,
461 :hide_followers_count,
467 |> unique_constraint(:nickname)
468 |> validate_format(:nickname, local_nickname_regex())
469 |> validate_length(:bio, max: bio_limit)
470 |> validate_length(:name, max: name_limit)
471 |> validate_fields(remote?)
474 def password_update_changeset(struct, params) do
476 |> cast(params, [:password, :password_confirmation])
477 |> validate_required([:password, :password_confirmation])
478 |> validate_confirmation(:password)
479 |> put_password_hash()
480 |> put_change(:password_reset_pending, false)
483 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
484 def reset_password(%User{id: user_id} = user, data) do
487 |> Multi.update(:user, password_update_changeset(user, data))
488 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
489 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
491 case Repo.transaction(multi) do
492 {:ok, %{user: user} = _} -> set_cache(user)
493 {:error, _, changeset, _} -> {:error, changeset}
497 def update_password_reset_pending(user, value) do
500 |> put_change(:password_reset_pending, value)
501 |> update_and_set_cache()
504 def force_password_reset_async(user) do
505 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
508 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
509 def force_password_reset(user), do: update_password_reset_pending(user, true)
511 def register_changeset(struct, params \\ %{}, opts \\ []) do
512 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
513 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
516 if is_nil(opts[:need_confirmation]) do
517 Pleroma.Config.get([:instance, :account_activation_required])
519 opts[:need_confirmation]
523 |> confirmation_changeset(need_confirmation: need_confirmation?)
524 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
525 |> validate_required([:name, :nickname, :password, :password_confirmation])
526 |> validate_confirmation(:password)
527 |> unique_constraint(:email)
528 |> unique_constraint(:nickname)
529 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
530 |> validate_format(:nickname, local_nickname_regex())
531 |> validate_format(:email, @email_regex)
532 |> validate_length(:bio, max: bio_limit)
533 |> validate_length(:name, min: 1, max: name_limit)
534 |> maybe_validate_required_email(opts[:external])
537 |> unique_constraint(:ap_id)
538 |> put_following_and_follower_address()
541 def maybe_validate_required_email(changeset, true), do: changeset
543 def maybe_validate_required_email(changeset, _) do
544 if Pleroma.Config.get([:instance, :account_activation_required]) do
545 validate_required(changeset, [:email])
551 defp put_ap_id(changeset) do
552 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
553 put_change(changeset, :ap_id, ap_id)
556 defp put_following_and_follower_address(changeset) do
557 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
560 |> put_change(:follower_address, followers)
563 defp autofollow_users(user) do
564 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
567 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
570 follow_all(user, autofollowed_users)
573 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
574 def register(%Ecto.Changeset{} = changeset) do
575 with {:ok, user} <- Repo.insert(changeset) do
576 post_register_action(user)
580 def post_register_action(%User{} = user) do
581 with {:ok, user} <- autofollow_users(user),
582 {:ok, user} <- set_cache(user),
583 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
584 {:ok, _} <- try_send_confirmation_email(user) do
589 def try_send_confirmation_email(%User{} = user) do
590 if user.confirmation_pending &&
591 Pleroma.Config.get([:instance, :account_activation_required]) do
593 |> Pleroma.Emails.UserEmail.account_confirmation_email()
594 |> Pleroma.Emails.Mailer.deliver_async()
602 def try_send_confirmation_email(users) do
603 Enum.each(users, &try_send_confirmation_email/1)
606 def needs_update?(%User{local: true}), do: false
608 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
610 def needs_update?(%User{local: false} = user) do
611 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
614 def needs_update?(_), do: true
616 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
617 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
618 follow(follower, followed, "pending")
621 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
622 follow(follower, followed)
625 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
626 if not ap_enabled?(followed) do
627 follow(follower, followed)
633 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
634 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
635 def follow_all(follower, followeds) do
637 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
638 |> Enum.each(&follow(follower, &1, "accept"))
643 defdelegate following(user), to: FollowingRelationship
645 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
646 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
649 followed.deactivated ->
650 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
652 deny_follow_blocked and blocks?(followed, follower) ->
653 {:error, "Could not follow user: #{followed.nickname} blocked you."}
656 FollowingRelationship.follow(follower, followed, state)
658 {:ok, _} = update_follower_count(followed)
661 |> update_following_count()
666 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
667 {:error, "Not subscribed!"}
670 def unfollow(%User{} = follower, %User{} = followed) do
671 case get_follow_state(follower, followed) do
672 state when state in ["accept", "pending"] ->
673 FollowingRelationship.unfollow(follower, followed)
674 {:ok, followed} = update_follower_count(followed)
678 |> update_following_count()
681 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
684 {:error, "Not subscribed!"}
688 defdelegate following?(follower, followed), to: FollowingRelationship
690 def get_follow_state(%User{} = follower, %User{} = following) do
691 following_relationship = FollowingRelationship.get(follower, following)
693 case {following_relationship, following.local} do
695 case Utils.fetch_latest_follow(follower, following) do
696 %{data: %{"state" => state}} when state in ["pending", "accept"] -> state
700 {%{state: state}, _} ->
708 def locked?(%User{} = user) do
713 Repo.get_by(User, id: id)
716 def get_by_ap_id(ap_id) do
717 Repo.get_by(User, ap_id: ap_id)
720 def get_all_by_ap_id(ap_ids) do
721 from(u in __MODULE__,
722 where: u.ap_id in ^ap_ids
727 def get_all_by_ids(ids) do
728 from(u in __MODULE__, where: u.id in ^ids)
732 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
733 # of the ap_id and the domain and tries to get that user
734 def get_by_guessed_nickname(ap_id) do
735 domain = URI.parse(ap_id).host
736 name = List.last(String.split(ap_id, "/"))
737 nickname = "#{name}@#{domain}"
739 get_cached_by_nickname(nickname)
742 def set_cache({:ok, user}), do: set_cache(user)
743 def set_cache({:error, err}), do: {:error, err}
745 def set_cache(%User{} = user) do
746 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
747 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
751 def update_and_set_cache(struct, params) do
753 |> update_changeset(params)
754 |> update_and_set_cache()
757 def update_and_set_cache(changeset) do
758 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
763 def invalidate_cache(user) do
764 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
765 Cachex.del(:user_cache, "nickname:#{user.nickname}")
768 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
769 def get_cached_by_ap_id(ap_id) do
770 key = "ap_id:#{ap_id}"
772 with {:ok, nil} <- Cachex.get(:user_cache, key),
773 user when not is_nil(user) <- get_by_ap_id(ap_id),
774 {:ok, true} <- Cachex.put(:user_cache, key, user) do
782 def get_cached_by_id(id) do
786 Cachex.fetch!(:user_cache, key, fn _ ->
790 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
791 {:commit, user.ap_id}
797 get_cached_by_ap_id(ap_id)
800 def get_cached_by_nickname(nickname) do
801 key = "nickname:#{nickname}"
803 Cachex.fetch!(:user_cache, key, fn ->
804 case get_or_fetch_by_nickname(nickname) do
805 {:ok, user} -> {:commit, user}
806 {:error, _error} -> {:ignore, nil}
811 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
812 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
815 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
816 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
818 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
819 get_cached_by_nickname(nickname_or_id)
821 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
822 get_cached_by_nickname(nickname_or_id)
829 def get_by_nickname(nickname) do
830 Repo.get_by(User, nickname: nickname) ||
831 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
832 Repo.get_by(User, nickname: local_nickname(nickname))
836 def get_by_email(email), do: Repo.get_by(User, email: email)
838 def get_by_nickname_or_email(nickname_or_email) do
839 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
842 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
844 def get_or_fetch_by_nickname(nickname) do
845 with %User{} = user <- get_by_nickname(nickname) do
849 with [_nick, _domain] <- String.split(nickname, "@"),
850 {:ok, user} <- fetch_by_nickname(nickname) do
853 _e -> {:error, "not found " <> nickname}
858 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
859 def get_followers_query(%User{} = user, nil) do
860 User.Query.build(%{followers: user, deactivated: false})
863 def get_followers_query(user, page) do
865 |> get_followers_query(nil)
866 |> User.Query.paginate(page, 20)
869 @spec get_followers_query(User.t()) :: Ecto.Query.t()
870 def get_followers_query(user), do: get_followers_query(user, nil)
872 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
873 def get_followers(user, page \\ nil) do
875 |> get_followers_query(page)
879 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
880 def get_external_followers(user, page \\ nil) do
882 |> get_followers_query(page)
883 |> User.Query.build(%{external: true})
887 def get_followers_ids(user, page \\ nil) do
889 |> get_followers_query(page)
894 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
895 def get_friends_query(%User{} = user, nil) do
896 User.Query.build(%{friends: user, deactivated: false})
899 def get_friends_query(user, page) do
901 |> get_friends_query(nil)
902 |> User.Query.paginate(page, 20)
905 @spec get_friends_query(User.t()) :: Ecto.Query.t()
906 def get_friends_query(user), do: get_friends_query(user, nil)
908 def get_friends(user, page \\ nil) do
910 |> get_friends_query(page)
914 def get_friends_ap_ids(user) do
916 |> get_friends_query(nil)
917 |> select([u], u.ap_id)
921 def get_friends_ids(user, page \\ nil) do
923 |> get_friends_query(page)
928 defdelegate get_follow_requests(user), to: FollowingRelationship
930 def increase_note_count(%User{} = user) do
932 |> where(id: ^user.id)
933 |> update([u], inc: [note_count: 1])
935 |> Repo.update_all([])
937 {1, [user]} -> set_cache(user)
942 def decrease_note_count(%User{} = user) do
944 |> where(id: ^user.id)
947 note_count: fragment("greatest(0, note_count - 1)")
951 |> Repo.update_all([])
953 {1, [user]} -> set_cache(user)
958 def update_note_count(%User{} = user, note_count \\ nil) do
963 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
969 |> cast(%{note_count: note_count}, [:note_count])
970 |> update_and_set_cache()
973 @spec maybe_fetch_follow_information(User.t()) :: User.t()
974 def maybe_fetch_follow_information(user) do
975 with {:ok, user} <- fetch_follow_information(user) do
979 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
985 def fetch_follow_information(user) do
986 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
988 |> follow_information_changeset(info)
989 |> update_and_set_cache()
993 defp follow_information_changeset(user, params) do
1000 :hide_followers_count,
1005 def update_follower_count(%User{} = user) do
1006 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
1007 follower_count_query =
1008 User.Query.build(%{followers: user, deactivated: false})
1009 |> select([u], %{count: count(u.id)})
1012 |> where(id: ^user.id)
1013 |> join(:inner, [u], s in subquery(follower_count_query))
1015 set: [follower_count: s.count]
1018 |> Repo.update_all([])
1020 {1, [user]} -> set_cache(user)
1024 {:ok, maybe_fetch_follow_information(user)}
1028 @spec update_following_count(User.t()) :: User.t()
1029 def update_following_count(%User{local: false} = user) do
1030 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1031 maybe_fetch_follow_information(user)
1037 def update_following_count(%User{local: true} = user) do
1038 following_count = FollowingRelationship.following_count(user)
1041 |> follow_information_changeset(%{following_count: following_count})
1045 def set_unread_conversation_count(%User{local: true} = user) do
1046 unread_query = Participation.unread_conversation_count_for_user(user)
1049 |> join(:inner, [u], p in subquery(unread_query))
1051 set: [unread_conversation_count: p.count]
1053 |> where([u], u.id == ^user.id)
1055 |> Repo.update_all([])
1057 {1, [user]} -> set_cache(user)
1062 def set_unread_conversation_count(user), do: {:ok, user}
1064 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1066 Participation.unread_conversation_count_for_user(user)
1067 |> where([p], p.conversation_id == ^conversation.id)
1070 |> join(:inner, [u], p in subquery(unread_query))
1072 inc: [unread_conversation_count: 1]
1074 |> where([u], u.id == ^user.id)
1075 |> where([u, p], p.count == 0)
1077 |> Repo.update_all([])
1079 {1, [user]} -> set_cache(user)
1084 def increment_unread_conversation_count(_, user), do: {:ok, user}
1086 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1087 def get_users_from_set(ap_ids, local_only \\ true) do
1088 criteria = %{ap_id: ap_ids, deactivated: false}
1089 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1091 User.Query.build(criteria)
1095 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1096 def get_recipients_from_activity(%Activity{recipients: to}) do
1097 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1101 @spec mute(User.t(), User.t(), boolean()) ::
1102 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1103 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1104 add_to_mutes(muter, mutee, notifications?)
1107 def unmute(%User{} = muter, %User{} = mutee) do
1108 remove_from_mutes(muter, mutee)
1111 def subscribe(%User{} = subscriber, %User{} = target) do
1112 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1114 if blocks?(target, subscriber) and deny_follow_blocked do
1115 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1117 # Note: the relationship is inverse: subscriber acts as relationship target
1118 UserRelationship.create_inverse_subscription(target, subscriber)
1122 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1123 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1124 subscribe(subscriber, subscribee)
1128 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1129 # Note: the relationship is inverse: subscriber acts as relationship target
1130 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1133 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1134 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1135 unsubscribe(unsubscriber, user)
1139 def block(%User{} = blocker, %User{} = blocked) do
1140 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1142 if following?(blocker, blocked) do
1143 {:ok, blocker, _} = unfollow(blocker, blocked)
1149 # clear any requested follows as well
1151 case CommonAPI.reject_follow_request(blocked, blocker) do
1152 {:ok, %User{} = updated_blocked} -> updated_blocked
1156 unsubscribe(blocked, blocker)
1158 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1160 {:ok, blocker} = update_follower_count(blocker)
1161 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1162 add_to_block(blocker, blocked)
1165 # helper to handle the block given only an actor's AP id
1166 def block(%User{} = blocker, %{ap_id: ap_id}) do
1167 block(blocker, get_cached_by_ap_id(ap_id))
1170 def unblock(%User{} = blocker, %User{} = blocked) do
1171 remove_from_block(blocker, blocked)
1174 # helper to handle the block given only an actor's AP id
1175 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1176 unblock(blocker, get_cached_by_ap_id(ap_id))
1179 def mutes?(nil, _), do: false
1180 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1182 def mutes_user?(%User{} = user, %User{} = target) do
1183 UserRelationship.mute_exists?(user, target)
1186 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1187 def muted_notifications?(nil, _), do: false
1189 def muted_notifications?(%User{} = user, %User{} = target),
1190 do: UserRelationship.notification_mute_exists?(user, target)
1192 def blocks?(nil, _), do: false
1194 def blocks?(%User{} = user, %User{} = target) do
1195 blocks_user?(user, target) ||
1196 (!User.following?(user, target) && blocks_domain?(user, target))
1199 def blocks_user?(%User{} = user, %User{} = target) do
1200 UserRelationship.block_exists?(user, target)
1203 def blocks_user?(_, _), do: false
1205 def blocks_domain?(%User{} = user, %User{} = target) do
1206 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1207 %{host: host} = URI.parse(target.ap_id)
1208 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1211 def blocks_domain?(_, _), do: false
1213 def subscribed_to?(%User{} = user, %User{} = target) do
1214 # Note: the relationship is inverse: subscriber acts as relationship target
1215 UserRelationship.inverse_subscription_exists?(target, user)
1218 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1219 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1220 subscribed_to?(user, target)
1225 Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
1226 E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1228 @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1229 def outgoing_relations_ap_ids(_user, []), do: %{}
1231 def outgoing_relations_ap_ids(nil, _relationship_types), do: %{}
1233 def outgoing_relations_ap_ids(%User{} = user, relationship_types)
1234 when is_list(relationship_types) do
1237 |> assoc(:outgoing_relationships)
1238 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1239 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1240 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1241 |> group_by([user_rel, u], user_rel.relationship_type)
1243 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1248 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1252 def incoming_relations_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1254 def incoming_relations_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1256 def incoming_relations_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1258 def incoming_relations_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1259 when is_list(relationship_types) do
1261 |> assoc(:incoming_relationships)
1262 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1263 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1264 |> maybe_filter_on_ap_id(ap_ids)
1265 |> select([user_rel, u], u.ap_id)
1270 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1271 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1274 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1276 def deactivate_async(user, status \\ true) do
1277 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1280 def deactivate(user, status \\ true)
1282 def deactivate(users, status) when is_list(users) do
1283 Repo.transaction(fn ->
1284 for user <- users, do: deactivate(user, status)
1288 def deactivate(%User{} = user, status) do
1289 with {:ok, user} <- set_activation_status(user, status) do
1292 |> Enum.filter(& &1.local)
1293 |> Enum.each(fn follower ->
1294 follower |> update_following_count() |> set_cache()
1297 # Only update local user counts, remote will be update during the next pull.
1300 |> Enum.filter(& &1.local)
1301 |> Enum.each(&update_follower_count/1)
1307 def update_notification_settings(%User{} = user, settings) do
1309 |> cast(%{notification_settings: settings}, [])
1310 |> cast_embed(:notification_settings)
1311 |> validate_required([:notification_settings])
1312 |> update_and_set_cache()
1315 def delete(users) when is_list(users) do
1316 for user <- users, do: delete(user)
1319 def delete(%User{} = user) do
1320 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1323 def perform(:force_password_reset, user), do: force_password_reset(user)
1325 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1326 def perform(:delete, %User{} = user) do
1327 {:ok, _user} = ActivityPub.delete(user)
1329 # Remove all relationships
1332 |> Enum.each(fn follower ->
1333 ActivityPub.unfollow(follower, user)
1334 unfollow(follower, user)
1339 |> Enum.each(fn followed ->
1340 ActivityPub.unfollow(user, followed)
1341 unfollow(user, followed)
1344 delete_user_activities(user)
1345 invalidate_cache(user)
1349 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1351 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1352 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1353 when is_list(blocked_identifiers) do
1355 blocked_identifiers,
1356 fn blocked_identifier ->
1357 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1358 {:ok, _user_block} <- block(blocker, blocked),
1359 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1363 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1370 def perform(:follow_import, %User{} = follower, followed_identifiers)
1371 when is_list(followed_identifiers) do
1373 followed_identifiers,
1374 fn followed_identifier ->
1375 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1376 {:ok, follower} <- maybe_direct_follow(follower, followed),
1377 {:ok, _} <- ActivityPub.follow(follower, followed) do
1381 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1388 @spec external_users_query() :: Ecto.Query.t()
1389 def external_users_query do
1397 @spec external_users(keyword()) :: [User.t()]
1398 def external_users(opts \\ []) do
1400 external_users_query()
1401 |> select([u], struct(u, [:id, :ap_id]))
1405 do: where(query, [u], u.id > ^opts[:max_id]),
1410 do: limit(query, ^opts[:limit]),
1416 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1417 BackgroundWorker.enqueue("blocks_import", %{
1418 "blocker_id" => blocker.id,
1419 "blocked_identifiers" => blocked_identifiers
1423 def follow_import(%User{} = follower, followed_identifiers)
1424 when is_list(followed_identifiers) do
1425 BackgroundWorker.enqueue("follow_import", %{
1426 "follower_id" => follower.id,
1427 "followed_identifiers" => followed_identifiers
1431 def delete_user_activities(%User{ap_id: ap_id}) do
1433 |> Activity.Queries.by_actor()
1434 |> RepoStreamer.chunk_stream(50)
1435 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1439 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1441 |> Object.normalize()
1442 |> ActivityPub.delete()
1445 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1446 object = Object.normalize(activity)
1449 |> get_cached_by_ap_id()
1450 |> ActivityPub.unlike(object)
1453 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1454 object = Object.normalize(activity)
1457 |> get_cached_by_ap_id()
1458 |> ActivityPub.unannounce(object)
1461 defp delete_activity(_activity), do: "Doing nothing"
1463 def html_filter_policy(%User{no_rich_text: true}) do
1464 Pleroma.HTML.Scrubber.TwitterText
1467 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1469 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1471 def get_or_fetch_by_ap_id(ap_id) do
1472 user = get_cached_by_ap_id(ap_id)
1474 if !is_nil(user) and !needs_update?(user) do
1477 fetch_by_ap_id(ap_id)
1482 Creates an internal service actor by URI if missing.
1483 Optionally takes nickname for addressing.
1485 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1486 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1488 case get_cached_by_ap_id(uri) do
1490 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1491 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1495 %User{invisible: false} = user ->
1505 @spec set_invisible(User.t()) :: {:ok, User.t()}
1506 defp set_invisible(user) do
1508 |> change(%{invisible: true})
1509 |> update_and_set_cache()
1512 @spec create_service_actor(String.t(), String.t()) ::
1513 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1514 defp create_service_actor(uri, nickname) do
1520 follower_address: uri <> "/followers"
1523 |> unique_constraint(:nickname)
1529 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1532 |> :public_key.pem_decode()
1534 |> :public_key.pem_entry_decode()
1539 def public_key(_), do: {:error, "not found key"}
1541 def get_public_key_for_ap_id(ap_id) do
1542 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1543 {:ok, public_key} <- public_key(user) do
1550 defp blank?(""), do: nil
1551 defp blank?(n), do: n
1553 def insert_or_update_user(data) do
1555 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1556 |> remote_user_creation()
1557 |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
1561 def ap_enabled?(%User{local: true}), do: true
1562 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1563 def ap_enabled?(_), do: false
1565 @doc "Gets or fetch a user by uri or nickname."
1566 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1567 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1568 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1570 # wait a period of time and return newest version of the User structs
1571 # this is because we have synchronous follow APIs and need to simulate them
1572 # with an async handshake
1573 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1574 with %User{} = a <- get_cached_by_id(a.id),
1575 %User{} = b <- get_cached_by_id(b.id) do
1582 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1583 with :ok <- :timer.sleep(timeout),
1584 %User{} = a <- get_cached_by_id(a.id),
1585 %User{} = b <- get_cached_by_id(b.id) do
1592 def parse_bio(bio) when is_binary(bio) and bio != "" do
1594 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1598 def parse_bio(_), do: ""
1600 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1601 # TODO: get profile URLs other than user.ap_id
1602 profile_urls = [user.ap_id]
1605 |> CommonUtils.format_input("text/plain",
1606 mentions_format: :full,
1607 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1612 def parse_bio(_, _), do: ""
1614 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1615 Repo.transaction(fn ->
1616 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1620 def tag(nickname, tags) when is_binary(nickname),
1621 do: tag(get_by_nickname(nickname), tags)
1623 def tag(%User{} = user, tags),
1624 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1626 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1627 Repo.transaction(fn ->
1628 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1632 def untag(nickname, tags) when is_binary(nickname),
1633 do: untag(get_by_nickname(nickname), tags)
1635 def untag(%User{} = user, tags),
1636 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1638 defp update_tags(%User{} = user, new_tags) do
1639 {:ok, updated_user} =
1641 |> change(%{tags: new_tags})
1642 |> update_and_set_cache()
1647 defp normalize_tags(tags) do
1650 |> Enum.map(&String.downcase/1)
1653 defp local_nickname_regex do
1654 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1655 @extended_local_nickname_regex
1657 @strict_local_nickname_regex
1661 def local_nickname(nickname_or_mention) do
1664 |> String.split("@")
1668 def full_nickname(nickname_or_mention),
1669 do: String.trim_leading(nickname_or_mention, "@")
1671 def error_user(ap_id) do
1675 nickname: "erroruser@example.com",
1676 inserted_at: NaiveDateTime.utc_now()
1680 @spec all_superusers() :: [User.t()]
1681 def all_superusers do
1682 User.Query.build(%{super_users: true, local: true, deactivated: false})
1686 def showing_reblogs?(%User{} = user, %User{} = target) do
1687 not UserRelationship.reblog_mute_exists?(user, target)
1691 The function returns a query to get users with no activity for given interval of days.
1692 Inactive users are those who didn't read any notification, or had any activity where
1693 the user is the activity's actor, during `inactivity_threshold` days.
1694 Deactivated users will not appear in this list.
1698 iex> Pleroma.User.list_inactive_users()
1701 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1702 def list_inactive_users_query(inactivity_threshold \\ 7) do
1703 negative_inactivity_threshold = -inactivity_threshold
1704 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1705 # Subqueries are not supported in `where` clauses, join gets too complicated.
1706 has_read_notifications =
1707 from(n in Pleroma.Notification,
1708 where: n.seen == true,
1710 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1713 |> Pleroma.Repo.all()
1715 from(u in Pleroma.User,
1716 left_join: a in Pleroma.Activity,
1717 on: u.ap_id == a.actor,
1718 where: not is_nil(u.nickname),
1719 where: u.deactivated != ^true,
1720 where: u.id not in ^has_read_notifications,
1723 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1724 is_nil(max(a.inserted_at))
1729 Enable or disable email notifications for user
1733 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1734 Pleroma.User{email_notifications: %{"digest" => true}}
1736 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1737 Pleroma.User{email_notifications: %{"digest" => false}}
1739 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1740 {:ok, t()} | {:error, Ecto.Changeset.t()}
1741 def switch_email_notifications(user, type, status) do
1742 User.update_email_notifications(user, %{type => status})
1746 Set `last_digest_emailed_at` value for the user to current time
1748 @spec touch_last_digest_emailed_at(t()) :: t()
1749 def touch_last_digest_emailed_at(user) do
1750 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1752 {:ok, updated_user} =
1754 |> change(%{last_digest_emailed_at: now})
1755 |> update_and_set_cache()
1760 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1761 def toggle_confirmation(%User{} = user) do
1763 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1764 |> update_and_set_cache()
1767 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1768 def toggle_confirmation(users) do
1769 Enum.map(users, &toggle_confirmation/1)
1772 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1776 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1777 # use instance-default
1778 config = Pleroma.Config.get([:assets, :mascots])
1779 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1780 mascot = Keyword.get(config, default_mascot)
1783 "id" => "default-mascot",
1784 "url" => mascot[:url],
1785 "preview_url" => mascot[:url],
1787 "mime_type" => mascot[:mime_type]
1792 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1794 def ensure_keys_present(%User{} = user) do
1795 with {:ok, pem} <- Keys.generate_rsa_pem() do
1797 |> cast(%{keys: pem}, [:keys])
1798 |> validate_required([:keys])
1799 |> update_and_set_cache()
1803 def get_ap_ids_by_nicknames(nicknames) do
1805 where: u.nickname in ^nicknames,
1811 defdelegate search(query, opts \\ []), to: User.Search
1813 defp put_password_hash(
1814 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1816 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1819 defp put_password_hash(changeset), do: changeset
1821 def is_internal_user?(%User{nickname: nil}), do: true
1822 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1823 def is_internal_user?(_), do: false
1825 # A hack because user delete activities have a fake id for whatever reason
1826 # TODO: Get rid of this
1827 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1829 def get_delivered_users_by_object_id(object_id) do
1831 inner_join: delivery in assoc(u, :deliveries),
1832 where: delivery.object_id == ^object_id
1837 def change_email(user, email) do
1839 |> cast(%{email: email}, [:email])
1840 |> validate_required([:email])
1841 |> unique_constraint(:email)
1842 |> validate_format(:email, @email_regex)
1843 |> update_and_set_cache()
1846 # Internal function; public one is `deactivate/2`
1847 defp set_activation_status(user, deactivated) do
1849 |> cast(%{deactivated: deactivated}, [:deactivated])
1850 |> update_and_set_cache()
1853 def update_banner(user, banner) do
1855 |> cast(%{banner: banner}, [:banner])
1856 |> update_and_set_cache()
1859 def update_background(user, background) do
1861 |> cast(%{background: background}, [:background])
1862 |> update_and_set_cache()
1865 def update_source_data(user, source_data) do
1867 |> cast(%{source_data: source_data}, [:source_data])
1868 |> update_and_set_cache()
1871 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1874 moderator: is_moderator
1878 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1879 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1880 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1881 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1884 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1885 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1889 def fields(%{fields: nil}), do: []
1891 def fields(%{fields: fields}), do: fields
1893 def validate_fields(changeset, remote? \\ false) do
1894 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1895 limit = Pleroma.Config.get([:instance, limit_name], 0)
1898 |> validate_length(:fields, max: limit)
1899 |> validate_change(:fields, fn :fields, fields ->
1900 if Enum.all?(fields, &valid_field?/1) do
1908 defp valid_field?(%{"name" => name, "value" => value}) do
1909 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1910 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1912 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1913 String.length(value) <= value_limit
1916 defp valid_field?(_), do: false
1918 defp truncate_field(%{"name" => name, "value" => value}) do
1920 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1923 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1925 %{"name" => name, "value" => value}
1928 def admin_api_update(user, params) do
1935 |> update_and_set_cache()
1938 @doc "Signs user out of all applications"
1939 def global_sign_out(user) do
1940 OAuth.Authorization.delete_user_authorizations(user)
1941 OAuth.Token.delete_user_tokens(user)
1944 def mascot_update(user, url) do
1946 |> cast(%{mascot: url}, [:mascot])
1947 |> validate_required([:mascot])
1948 |> update_and_set_cache()
1951 def mastodon_settings_update(user, settings) do
1953 |> cast(%{settings: settings}, [:settings])
1954 |> validate_required([:settings])
1955 |> update_and_set_cache()
1958 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1959 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1961 if need_confirmation? do
1963 confirmation_pending: true,
1964 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1968 confirmation_pending: false,
1969 confirmation_token: nil
1973 cast(user, params, [:confirmation_pending, :confirmation_token])
1976 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1977 if id not in user.pinned_activities do
1978 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1979 params = %{pinned_activities: user.pinned_activities ++ [id]}
1982 |> cast(params, [:pinned_activities])
1983 |> validate_length(:pinned_activities,
1984 max: max_pinned_statuses,
1985 message: "You have already pinned the maximum number of statuses"
1990 |> update_and_set_cache()
1993 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1994 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1997 |> cast(params, [:pinned_activities])
1998 |> update_and_set_cache()
2001 def update_email_notifications(user, settings) do
2002 email_notifications =
2003 user.email_notifications
2004 |> Map.merge(settings)
2005 |> Map.take(["digest"])
2007 params = %{email_notifications: email_notifications}
2008 fields = [:email_notifications]
2011 |> cast(params, fields)
2012 |> validate_required(fields)
2013 |> update_and_set_cache()
2016 defp set_domain_blocks(user, domain_blocks) do
2017 params = %{domain_blocks: domain_blocks}
2020 |> cast(params, [:domain_blocks])
2021 |> validate_required([:domain_blocks])
2022 |> update_and_set_cache()
2025 def block_domain(user, domain_blocked) do
2026 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2029 def unblock_domain(user, domain_blocked) do
2030 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2033 @spec add_to_block(User.t(), User.t()) ::
2034 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2035 defp add_to_block(%User{} = user, %User{} = blocked) do
2036 UserRelationship.create_block(user, blocked)
2039 @spec add_to_block(User.t(), User.t()) ::
2040 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2041 defp remove_from_block(%User{} = user, %User{} = blocked) do
2042 UserRelationship.delete_block(user, blocked)
2045 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2046 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2047 {:ok, user_notification_mute} <-
2048 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2050 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2054 defp remove_from_mutes(user, %User{} = muted_user) do
2055 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2056 {:ok, user_notification_mute} <-
2057 UserRelationship.delete_notification_mute(user, muted_user) do
2058 {:ok, [user_mute, user_notification_mute]}
2062 def set_invisible(user, invisible) do
2063 params = %{invisible: invisible}
2066 |> cast(params, [:invisible])
2067 |> validate_required([:invisible])
2068 |> update_and_set_cache()
2071 def sanitize_html(%User{} = user) do
2072 sanitize_html(user, nil)
2075 # User data that mastodon isn't filtering (treated as plaintext):
2078 def sanitize_html(%User{} = user, filter) do
2082 |> Enum.map(fn %{"name" => name, "value" => value} ->
2085 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2090 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2091 |> Map.put(:fields, fields)