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)
230 Dumps Flake Id to SQL-compatible format (16-byte UUID).
231 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
233 def binary_id(source_id) when is_binary(source_id) do
234 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
241 def binary_id(source_ids) when is_list(source_ids) do
242 Enum.map(source_ids, &binary_id/1)
245 def binary_id(%User{} = user), do: binary_id(user.id)
247 @doc "Returns status account"
248 @spec account_status(User.t()) :: account_status()
249 def account_status(%User{deactivated: true}), do: :deactivated
250 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
252 def account_status(%User{confirmation_pending: true}) do
253 case Config.get([:instance, :account_activation_required]) do
254 true -> :confirmation_pending
259 def account_status(%User{}), do: :active
261 @spec visible_for?(User.t(), User.t() | nil) :: boolean()
262 def visible_for?(user, for_user \\ nil)
264 def visible_for?(%User{invisible: true}, _), do: false
266 def visible_for?(%User{id: user_id}, %User{id: user_id}), do: true
268 def visible_for?(%User{local: local} = user, nil) do
274 if Config.get([:restrict_unauthenticated, :profiles, cfg_key]),
276 else: account_status(user) == :active
279 def visible_for?(%User{} = user, for_user) do
280 account_status(user) == :active || superuser?(for_user)
283 def visible_for?(_, _), do: false
285 @spec superuser?(User.t()) :: boolean()
286 def superuser?(%User{local: true, is_admin: true}), do: true
287 def superuser?(%User{local: true, is_moderator: true}), do: true
288 def superuser?(_), do: false
290 @spec invisible?(User.t()) :: boolean()
291 def invisible?(%User{invisible: true}), do: true
292 def invisible?(_), do: false
294 def avatar_url(user, options \\ []) do
296 %{"url" => [%{"href" => href} | _]} -> href
297 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
301 def banner_url(user, options \\ []) do
303 %{"url" => [%{"href" => href} | _]} -> href
304 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
308 def profile_url(%User{source_data: %{"url" => url}}), do: url
309 def profile_url(%User{ap_id: ap_id}), do: ap_id
310 def profile_url(_), do: nil
312 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
314 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
315 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
317 @spec ap_following(User.t()) :: Sring.t()
318 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
319 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
321 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
322 def restrict_deactivated(query) do
323 from(u in query, where: u.deactivated != ^true)
326 defdelegate following_count(user), to: FollowingRelationship
328 defp truncate_fields_param(params) do
329 if Map.has_key?(params, :fields) do
330 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
336 defp truncate_if_exists(params, key, max_length) do
337 if Map.has_key?(params, key) and is_binary(params[key]) do
338 {value, _chopped} = String.split_at(params[key], max_length)
339 Map.put(params, key, value)
345 def remote_user_creation(params) do
346 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
347 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
351 |> truncate_if_exists(:name, name_limit)
352 |> truncate_if_exists(:bio, bio_limit)
353 |> truncate_fields_param()
373 :hide_followers_count,
384 |> validate_required([:name, :ap_id])
385 |> unique_constraint(:nickname)
386 |> validate_format(:nickname, @email_regex)
387 |> validate_length(:bio, max: bio_limit)
388 |> validate_length(:name, max: name_limit)
389 |> validate_fields(true)
391 case params[:source_data] do
392 %{"followers" => followers, "following" => following} ->
394 |> put_change(:follower_address, followers)
395 |> put_change(:following_address, following)
398 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
399 put_change(changeset, :follower_address, followers)
403 def update_changeset(struct, params \\ %{}) do
404 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
405 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
420 :hide_followers_count,
423 :allow_following_move,
426 :skip_thread_containment,
429 :pleroma_settings_store,
435 |> unique_constraint(:nickname)
436 |> validate_format(:nickname, local_nickname_regex())
437 |> validate_length(:bio, max: bio_limit)
438 |> validate_length(:name, min: 1, max: name_limit)
440 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
441 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
442 |> put_change_if_present(:banner, &put_upload(&1, :banner))
443 |> put_change_if_present(:background, &put_upload(&1, :background))
444 |> put_change_if_present(
445 :pleroma_settings_store,
446 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
448 |> validate_fields(false)
451 defp put_fields(changeset) do
452 if raw_fields = get_change(changeset, :raw_fields) do
455 |> Enum.filter(fn %{"name" => n} -> n != "" end)
459 |> Enum.map(fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
462 |> put_change(:raw_fields, raw_fields)
463 |> put_change(:fields, fields)
469 defp put_change_if_present(changeset, map_field, value_function) do
470 if value = get_change(changeset, map_field) do
471 with {:ok, new_value} <- value_function.(value) do
472 put_change(changeset, map_field, new_value)
481 defp put_upload(value, type) do
482 with %Plug.Upload{} <- value,
483 {:ok, object} <- ActivityPub.upload(value, type: type) do
488 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
489 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
490 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
492 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
494 params = if remote?, do: truncate_fields_param(params), else: params
516 :allow_following_move,
518 :hide_followers_count,
524 |> unique_constraint(:nickname)
525 |> validate_format(:nickname, local_nickname_regex())
526 |> validate_length(:bio, max: bio_limit)
527 |> validate_length(:name, max: name_limit)
528 |> validate_fields(remote?)
531 def update_as_admin_changeset(struct, params) do
533 |> update_changeset(params)
534 |> cast(params, [:email])
535 |> delete_change(:also_known_as)
536 |> unique_constraint(:email)
537 |> validate_format(:email, @email_regex)
540 @spec update_as_admin(%User{}, map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
541 def update_as_admin(user, params) do
542 params = Map.put(params, "password_confirmation", params["password"])
543 changeset = update_as_admin_changeset(user, params)
545 if params["password"] do
546 reset_password(user, changeset, params)
548 User.update_and_set_cache(changeset)
552 def password_update_changeset(struct, params) do
554 |> cast(params, [:password, :password_confirmation])
555 |> validate_required([:password, :password_confirmation])
556 |> validate_confirmation(:password)
557 |> put_password_hash()
558 |> put_change(:password_reset_pending, false)
561 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
562 def reset_password(%User{} = user, params) do
563 reset_password(user, user, params)
566 def reset_password(%User{id: user_id} = user, struct, params) do
569 |> Multi.update(:user, password_update_changeset(struct, params))
570 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
571 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
573 case Repo.transaction(multi) do
574 {:ok, %{user: user} = _} -> set_cache(user)
575 {:error, _, changeset, _} -> {:error, changeset}
579 def update_password_reset_pending(user, value) do
582 |> put_change(:password_reset_pending, value)
583 |> update_and_set_cache()
586 def force_password_reset_async(user) do
587 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
590 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
591 def force_password_reset(user), do: update_password_reset_pending(user, true)
593 def register_changeset(struct, params \\ %{}, opts \\ []) do
594 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
595 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
598 if is_nil(opts[:need_confirmation]) do
599 Pleroma.Config.get([:instance, :account_activation_required])
601 opts[:need_confirmation]
605 |> confirmation_changeset(need_confirmation: need_confirmation?)
606 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
607 |> validate_required([:name, :nickname, :password, :password_confirmation])
608 |> validate_confirmation(:password)
609 |> unique_constraint(:email)
610 |> unique_constraint(:nickname)
611 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
612 |> validate_format(:nickname, local_nickname_regex())
613 |> validate_format(:email, @email_regex)
614 |> validate_length(:bio, max: bio_limit)
615 |> validate_length(:name, min: 1, max: name_limit)
616 |> maybe_validate_required_email(opts[:external])
619 |> unique_constraint(:ap_id)
620 |> put_following_and_follower_address()
623 def maybe_validate_required_email(changeset, true), do: changeset
625 def maybe_validate_required_email(changeset, _) do
626 if Pleroma.Config.get([:instance, :account_activation_required]) do
627 validate_required(changeset, [:email])
633 defp put_ap_id(changeset) do
634 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
635 put_change(changeset, :ap_id, ap_id)
638 defp put_following_and_follower_address(changeset) do
639 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
642 |> put_change(:follower_address, followers)
645 defp autofollow_users(user) do
646 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
649 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
652 follow_all(user, autofollowed_users)
655 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
656 def register(%Ecto.Changeset{} = changeset) do
657 with {:ok, user} <- Repo.insert(changeset) do
658 post_register_action(user)
662 def post_register_action(%User{} = user) do
663 with {:ok, user} <- autofollow_users(user),
664 {:ok, user} <- set_cache(user),
665 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
666 {:ok, _} <- try_send_confirmation_email(user) do
671 def try_send_confirmation_email(%User{} = user) do
672 if user.confirmation_pending &&
673 Pleroma.Config.get([:instance, :account_activation_required]) do
675 |> Pleroma.Emails.UserEmail.account_confirmation_email()
676 |> Pleroma.Emails.Mailer.deliver_async()
684 def try_send_confirmation_email(users) do
685 Enum.each(users, &try_send_confirmation_email/1)
688 def needs_update?(%User{local: true}), do: false
690 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
692 def needs_update?(%User{local: false} = user) do
693 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
696 def needs_update?(_), do: true
698 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
699 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
700 follow(follower, followed, :follow_pending)
703 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
704 follow(follower, followed)
707 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
708 if not ap_enabled?(followed) do
709 follow(follower, followed)
715 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
716 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
717 def follow_all(follower, followeds) do
719 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
720 |> Enum.each(&follow(follower, &1, :follow_accept))
725 defdelegate following(user), to: FollowingRelationship
727 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
728 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
731 followed.deactivated ->
732 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
734 deny_follow_blocked and blocks?(followed, follower) ->
735 {:error, "Could not follow user: #{followed.nickname} blocked you."}
738 FollowingRelationship.follow(follower, followed, state)
740 {:ok, _} = update_follower_count(followed)
743 |> update_following_count()
748 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
749 {:error, "Not subscribed!"}
752 def unfollow(%User{} = follower, %User{} = followed) do
753 case get_follow_state(follower, followed) do
754 state when state in [:follow_pending, :follow_accept] ->
755 FollowingRelationship.unfollow(follower, followed)
756 {:ok, followed} = update_follower_count(followed)
760 |> update_following_count()
763 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
766 {:error, "Not subscribed!"}
770 defdelegate following?(follower, followed), to: FollowingRelationship
772 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
773 def get_follow_state(%User{} = follower, %User{} = following) do
774 following_relationship = FollowingRelationship.get(follower, following)
775 get_follow_state(follower, following, following_relationship)
778 def get_follow_state(
781 following_relationship
783 case {following_relationship, following.local} do
785 case Utils.fetch_latest_follow(follower, following) do
786 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
787 FollowingRelationship.state_to_enum(state)
793 {%{state: state}, _} ->
801 def locked?(%User{} = user) do
806 Repo.get_by(User, id: id)
809 def get_by_ap_id(ap_id) do
810 Repo.get_by(User, ap_id: ap_id)
813 def get_all_by_ap_id(ap_ids) do
814 from(u in __MODULE__,
815 where: u.ap_id in ^ap_ids
820 def get_all_by_ids(ids) do
821 from(u in __MODULE__, where: u.id in ^ids)
825 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
826 # of the ap_id and the domain and tries to get that user
827 def get_by_guessed_nickname(ap_id) do
828 domain = URI.parse(ap_id).host
829 name = List.last(String.split(ap_id, "/"))
830 nickname = "#{name}@#{domain}"
832 get_cached_by_nickname(nickname)
835 def set_cache({:ok, user}), do: set_cache(user)
836 def set_cache({:error, err}), do: {:error, err}
838 def set_cache(%User{} = user) do
839 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
840 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
844 def update_and_set_cache(struct, params) do
846 |> update_changeset(params)
847 |> update_and_set_cache()
850 def update_and_set_cache(changeset) do
851 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
856 def invalidate_cache(user) do
857 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
858 Cachex.del(:user_cache, "nickname:#{user.nickname}")
861 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
862 def get_cached_by_ap_id(ap_id) do
863 key = "ap_id:#{ap_id}"
865 with {:ok, nil} <- Cachex.get(:user_cache, key),
866 user when not is_nil(user) <- get_by_ap_id(ap_id),
867 {:ok, true} <- Cachex.put(:user_cache, key, user) do
875 def get_cached_by_id(id) do
879 Cachex.fetch!(:user_cache, key, fn _ ->
883 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
884 {:commit, user.ap_id}
890 get_cached_by_ap_id(ap_id)
893 def get_cached_by_nickname(nickname) do
894 key = "nickname:#{nickname}"
896 Cachex.fetch!(:user_cache, key, fn ->
897 case get_or_fetch_by_nickname(nickname) do
898 {:ok, user} -> {:commit, user}
899 {:error, _error} -> {:ignore, nil}
904 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
905 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
908 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
909 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
911 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
912 get_cached_by_nickname(nickname_or_id)
914 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
915 get_cached_by_nickname(nickname_or_id)
922 def get_by_nickname(nickname) do
923 Repo.get_by(User, nickname: nickname) ||
924 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
925 Repo.get_by(User, nickname: local_nickname(nickname))
929 def get_by_email(email), do: Repo.get_by(User, email: email)
931 def get_by_nickname_or_email(nickname_or_email) do
932 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
935 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
937 def get_or_fetch_by_nickname(nickname) do
938 with %User{} = user <- get_by_nickname(nickname) do
942 with [_nick, _domain] <- String.split(nickname, "@"),
943 {:ok, user} <- fetch_by_nickname(nickname) do
946 _e -> {:error, "not found " <> nickname}
951 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
952 def get_followers_query(%User{} = user, nil) do
953 User.Query.build(%{followers: user, deactivated: false})
956 def get_followers_query(user, page) do
958 |> get_followers_query(nil)
959 |> User.Query.paginate(page, 20)
962 @spec get_followers_query(User.t()) :: Ecto.Query.t()
963 def get_followers_query(user), do: get_followers_query(user, nil)
965 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
966 def get_followers(user, page \\ nil) do
968 |> get_followers_query(page)
972 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
973 def get_external_followers(user, page \\ nil) do
975 |> get_followers_query(page)
976 |> User.Query.build(%{external: true})
980 def get_followers_ids(user, page \\ nil) do
982 |> get_followers_query(page)
987 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
988 def get_friends_query(%User{} = user, nil) do
989 User.Query.build(%{friends: user, deactivated: false})
992 def get_friends_query(user, page) do
994 |> get_friends_query(nil)
995 |> User.Query.paginate(page, 20)
998 @spec get_friends_query(User.t()) :: Ecto.Query.t()
999 def get_friends_query(user), do: get_friends_query(user, nil)
1001 def get_friends(user, page \\ nil) do
1003 |> get_friends_query(page)
1007 def get_friends_ap_ids(user) do
1009 |> get_friends_query(nil)
1010 |> select([u], u.ap_id)
1014 def get_friends_ids(user, page \\ nil) do
1016 |> get_friends_query(page)
1017 |> select([u], u.id)
1021 defdelegate get_follow_requests(user), to: FollowingRelationship
1023 def increase_note_count(%User{} = user) do
1025 |> where(id: ^user.id)
1026 |> update([u], inc: [note_count: 1])
1028 |> Repo.update_all([])
1030 {1, [user]} -> set_cache(user)
1035 def decrease_note_count(%User{} = user) do
1037 |> where(id: ^user.id)
1040 note_count: fragment("greatest(0, note_count - 1)")
1044 |> Repo.update_all([])
1046 {1, [user]} -> set_cache(user)
1051 def update_note_count(%User{} = user, note_count \\ nil) do
1056 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1062 |> cast(%{note_count: note_count}, [:note_count])
1063 |> update_and_set_cache()
1066 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1067 def maybe_fetch_follow_information(user) do
1068 with {:ok, user} <- fetch_follow_information(user) do
1072 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1078 def fetch_follow_information(user) do
1079 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1081 |> follow_information_changeset(info)
1082 |> update_and_set_cache()
1086 defp follow_information_changeset(user, params) do
1093 :hide_followers_count,
1098 def update_follower_count(%User{} = user) do
1099 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
1100 follower_count_query =
1101 User.Query.build(%{followers: user, deactivated: false})
1102 |> select([u], %{count: count(u.id)})
1105 |> where(id: ^user.id)
1106 |> join(:inner, [u], s in subquery(follower_count_query))
1108 set: [follower_count: s.count]
1111 |> Repo.update_all([])
1113 {1, [user]} -> set_cache(user)
1117 {:ok, maybe_fetch_follow_information(user)}
1121 @spec update_following_count(User.t()) :: User.t()
1122 def update_following_count(%User{local: false} = user) do
1123 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1124 maybe_fetch_follow_information(user)
1130 def update_following_count(%User{local: true} = user) do
1131 following_count = FollowingRelationship.following_count(user)
1134 |> follow_information_changeset(%{following_count: following_count})
1138 def set_unread_conversation_count(%User{local: true} = user) do
1139 unread_query = Participation.unread_conversation_count_for_user(user)
1142 |> join(:inner, [u], p in subquery(unread_query))
1144 set: [unread_conversation_count: p.count]
1146 |> where([u], u.id == ^user.id)
1148 |> Repo.update_all([])
1150 {1, [user]} -> set_cache(user)
1155 def set_unread_conversation_count(user), do: {:ok, user}
1157 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1159 Participation.unread_conversation_count_for_user(user)
1160 |> where([p], p.conversation_id == ^conversation.id)
1163 |> join(:inner, [u], p in subquery(unread_query))
1165 inc: [unread_conversation_count: 1]
1167 |> where([u], u.id == ^user.id)
1168 |> where([u, p], p.count == 0)
1170 |> Repo.update_all([])
1172 {1, [user]} -> set_cache(user)
1177 def increment_unread_conversation_count(_, user), do: {:ok, user}
1179 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1180 def get_users_from_set(ap_ids, local_only \\ true) do
1181 criteria = %{ap_id: ap_ids, deactivated: false}
1182 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1184 User.Query.build(criteria)
1188 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1189 def get_recipients_from_activity(%Activity{recipients: to}) do
1190 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1194 @spec mute(User.t(), User.t(), boolean()) ::
1195 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1196 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1197 add_to_mutes(muter, mutee, notifications?)
1200 def unmute(%User{} = muter, %User{} = mutee) do
1201 remove_from_mutes(muter, mutee)
1204 def subscribe(%User{} = subscriber, %User{} = target) do
1205 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1207 if blocks?(target, subscriber) and deny_follow_blocked do
1208 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1210 # Note: the relationship is inverse: subscriber acts as relationship target
1211 UserRelationship.create_inverse_subscription(target, subscriber)
1215 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1216 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1217 subscribe(subscriber, subscribee)
1221 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1222 # Note: the relationship is inverse: subscriber acts as relationship target
1223 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1226 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1227 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1228 unsubscribe(unsubscriber, user)
1232 def block(%User{} = blocker, %User{} = blocked) do
1233 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1235 if following?(blocker, blocked) do
1236 {:ok, blocker, _} = unfollow(blocker, blocked)
1242 # clear any requested follows as well
1244 case CommonAPI.reject_follow_request(blocked, blocker) do
1245 {:ok, %User{} = updated_blocked} -> updated_blocked
1249 unsubscribe(blocked, blocker)
1251 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1253 {:ok, blocker} = update_follower_count(blocker)
1254 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1255 add_to_block(blocker, blocked)
1258 # helper to handle the block given only an actor's AP id
1259 def block(%User{} = blocker, %{ap_id: ap_id}) do
1260 block(blocker, get_cached_by_ap_id(ap_id))
1263 def unblock(%User{} = blocker, %User{} = blocked) do
1264 remove_from_block(blocker, blocked)
1267 # helper to handle the block given only an actor's AP id
1268 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1269 unblock(blocker, get_cached_by_ap_id(ap_id))
1272 def mutes?(nil, _), do: false
1273 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1275 def mutes_user?(%User{} = user, %User{} = target) do
1276 UserRelationship.mute_exists?(user, target)
1279 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1280 def muted_notifications?(nil, _), do: false
1282 def muted_notifications?(%User{} = user, %User{} = target),
1283 do: UserRelationship.notification_mute_exists?(user, target)
1285 def blocks?(nil, _), do: false
1287 def blocks?(%User{} = user, %User{} = target) do
1288 blocks_user?(user, target) ||
1289 (blocks_domain?(user, target) and not User.following?(user, target))
1292 def blocks_user?(%User{} = user, %User{} = target) do
1293 UserRelationship.block_exists?(user, target)
1296 def blocks_user?(_, _), do: false
1298 def blocks_domain?(%User{} = user, %User{} = target) do
1299 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1300 %{host: host} = URI.parse(target.ap_id)
1301 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1304 def blocks_domain?(_, _), do: false
1306 def subscribed_to?(%User{} = user, %User{} = target) do
1307 # Note: the relationship is inverse: subscriber acts as relationship target
1308 UserRelationship.inverse_subscription_exists?(target, user)
1311 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1312 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1313 subscribed_to?(user, target)
1318 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1319 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1321 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1322 def outgoing_relationships_ap_ids(_user, []), do: %{}
1324 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1326 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1327 when is_list(relationship_types) do
1330 |> assoc(:outgoing_relationships)
1331 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1332 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1333 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1334 |> group_by([user_rel, u], user_rel.relationship_type)
1336 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1341 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1345 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1347 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1349 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1351 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1352 when is_list(relationship_types) do
1354 |> assoc(:incoming_relationships)
1355 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1356 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1357 |> maybe_filter_on_ap_id(ap_ids)
1358 |> select([user_rel, u], u.ap_id)
1363 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1364 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1367 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1369 def deactivate_async(user, status \\ true) do
1370 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1373 def deactivate(user, status \\ true)
1375 def deactivate(users, status) when is_list(users) do
1376 Repo.transaction(fn ->
1377 for user <- users, do: deactivate(user, status)
1381 def deactivate(%User{} = user, status) do
1382 with {:ok, user} <- set_activation_status(user, status) do
1385 |> Enum.filter(& &1.local)
1386 |> Enum.each(fn follower ->
1387 follower |> update_following_count() |> set_cache()
1390 # Only update local user counts, remote will be update during the next pull.
1393 |> Enum.filter(& &1.local)
1394 |> Enum.each(&update_follower_count/1)
1400 def update_notification_settings(%User{} = user, settings) do
1402 |> cast(%{notification_settings: settings}, [])
1403 |> cast_embed(:notification_settings)
1404 |> validate_required([:notification_settings])
1405 |> update_and_set_cache()
1408 def delete(users) when is_list(users) do
1409 for user <- users, do: delete(user)
1412 def delete(%User{} = user) do
1413 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1416 def perform(:force_password_reset, user), do: force_password_reset(user)
1418 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1419 def perform(:delete, %User{} = user) do
1420 {:ok, _user} = ActivityPub.delete(user)
1422 # Remove all relationships
1425 |> Enum.each(fn follower ->
1426 ActivityPub.unfollow(follower, user)
1427 unfollow(follower, user)
1432 |> Enum.each(fn followed ->
1433 ActivityPub.unfollow(user, followed)
1434 unfollow(user, followed)
1437 delete_user_activities(user)
1438 invalidate_cache(user)
1442 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1444 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1445 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1446 when is_list(blocked_identifiers) do
1448 blocked_identifiers,
1449 fn blocked_identifier ->
1450 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1451 {:ok, _user_block} <- block(blocker, blocked),
1452 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1456 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1463 def perform(:follow_import, %User{} = follower, followed_identifiers)
1464 when is_list(followed_identifiers) do
1466 followed_identifiers,
1467 fn followed_identifier ->
1468 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1469 {:ok, follower} <- maybe_direct_follow(follower, followed),
1470 {:ok, _} <- ActivityPub.follow(follower, followed) do
1474 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1481 @spec external_users_query() :: Ecto.Query.t()
1482 def external_users_query do
1490 @spec external_users(keyword()) :: [User.t()]
1491 def external_users(opts \\ []) do
1493 external_users_query()
1494 |> select([u], struct(u, [:id, :ap_id]))
1498 do: where(query, [u], u.id > ^opts[:max_id]),
1503 do: limit(query, ^opts[:limit]),
1509 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1510 BackgroundWorker.enqueue("blocks_import", %{
1511 "blocker_id" => blocker.id,
1512 "blocked_identifiers" => blocked_identifiers
1516 def follow_import(%User{} = follower, followed_identifiers)
1517 when is_list(followed_identifiers) do
1518 BackgroundWorker.enqueue("follow_import", %{
1519 "follower_id" => follower.id,
1520 "followed_identifiers" => followed_identifiers
1524 def delete_user_activities(%User{ap_id: ap_id}) do
1526 |> Activity.Queries.by_actor()
1527 |> RepoStreamer.chunk_stream(50)
1528 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1532 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1534 |> Object.normalize()
1535 |> ActivityPub.delete()
1538 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1539 object = Object.normalize(activity)
1542 |> get_cached_by_ap_id()
1543 |> ActivityPub.unlike(object)
1546 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1547 object = Object.normalize(activity)
1550 |> get_cached_by_ap_id()
1551 |> ActivityPub.unannounce(object)
1554 defp delete_activity(_activity), do: "Doing nothing"
1556 def html_filter_policy(%User{no_rich_text: true}) do
1557 Pleroma.HTML.Scrubber.TwitterText
1560 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1562 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1564 def get_or_fetch_by_ap_id(ap_id) do
1565 user = get_cached_by_ap_id(ap_id)
1567 if !is_nil(user) and !needs_update?(user) do
1570 fetch_by_ap_id(ap_id)
1575 Creates an internal service actor by URI if missing.
1576 Optionally takes nickname for addressing.
1578 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1579 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1581 case get_cached_by_ap_id(uri) do
1583 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1584 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1588 %User{invisible: false} = user ->
1598 @spec set_invisible(User.t()) :: {:ok, User.t()}
1599 defp set_invisible(user) do
1601 |> change(%{invisible: true})
1602 |> update_and_set_cache()
1605 @spec create_service_actor(String.t(), String.t()) ::
1606 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1607 defp create_service_actor(uri, nickname) do
1613 follower_address: uri <> "/followers"
1616 |> unique_constraint(:nickname)
1622 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1625 |> :public_key.pem_decode()
1627 |> :public_key.pem_entry_decode()
1632 def public_key(_), do: {:error, "not found key"}
1634 def get_public_key_for_ap_id(ap_id) do
1635 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1636 {:ok, public_key} <- public_key(user) do
1643 defp blank?(""), do: nil
1644 defp blank?(n), do: n
1646 def insert_or_update_user(data) do
1648 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1649 |> remote_user_creation()
1650 |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
1654 def ap_enabled?(%User{local: true}), do: true
1655 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1656 def ap_enabled?(_), do: false
1658 @doc "Gets or fetch a user by uri or nickname."
1659 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1660 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1661 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1663 # wait a period of time and return newest version of the User structs
1664 # this is because we have synchronous follow APIs and need to simulate them
1665 # with an async handshake
1666 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1667 with %User{} = a <- get_cached_by_id(a.id),
1668 %User{} = b <- get_cached_by_id(b.id) do
1675 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1676 with :ok <- :timer.sleep(timeout),
1677 %User{} = a <- get_cached_by_id(a.id),
1678 %User{} = b <- get_cached_by_id(b.id) do
1685 def parse_bio(bio) when is_binary(bio) and bio != "" do
1687 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1691 def parse_bio(_), do: ""
1693 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1694 # TODO: get profile URLs other than user.ap_id
1695 profile_urls = [user.ap_id]
1698 |> CommonUtils.format_input("text/plain",
1699 mentions_format: :full,
1700 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1705 def parse_bio(_, _), do: ""
1707 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1708 Repo.transaction(fn ->
1709 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1713 def tag(nickname, tags) when is_binary(nickname),
1714 do: tag(get_by_nickname(nickname), tags)
1716 def tag(%User{} = user, tags),
1717 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1719 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1720 Repo.transaction(fn ->
1721 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1725 def untag(nickname, tags) when is_binary(nickname),
1726 do: untag(get_by_nickname(nickname), tags)
1728 def untag(%User{} = user, tags),
1729 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1731 defp update_tags(%User{} = user, new_tags) do
1732 {:ok, updated_user} =
1734 |> change(%{tags: new_tags})
1735 |> update_and_set_cache()
1740 defp normalize_tags(tags) do
1743 |> Enum.map(&String.downcase/1)
1746 defp local_nickname_regex do
1747 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1748 @extended_local_nickname_regex
1750 @strict_local_nickname_regex
1754 def local_nickname(nickname_or_mention) do
1757 |> String.split("@")
1761 def full_nickname(nickname_or_mention),
1762 do: String.trim_leading(nickname_or_mention, "@")
1764 def error_user(ap_id) do
1768 nickname: "erroruser@example.com",
1769 inserted_at: NaiveDateTime.utc_now()
1773 @spec all_superusers() :: [User.t()]
1774 def all_superusers do
1775 User.Query.build(%{super_users: true, local: true, deactivated: false})
1779 def muting_reblogs?(%User{} = user, %User{} = target) do
1780 UserRelationship.reblog_mute_exists?(user, target)
1783 def showing_reblogs?(%User{} = user, %User{} = target) do
1784 not muting_reblogs?(user, target)
1788 The function returns a query to get users with no activity for given interval of days.
1789 Inactive users are those who didn't read any notification, or had any activity where
1790 the user is the activity's actor, during `inactivity_threshold` days.
1791 Deactivated users will not appear in this list.
1795 iex> Pleroma.User.list_inactive_users()
1798 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1799 def list_inactive_users_query(inactivity_threshold \\ 7) do
1800 negative_inactivity_threshold = -inactivity_threshold
1801 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1802 # Subqueries are not supported in `where` clauses, join gets too complicated.
1803 has_read_notifications =
1804 from(n in Pleroma.Notification,
1805 where: n.seen == true,
1807 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1810 |> Pleroma.Repo.all()
1812 from(u in Pleroma.User,
1813 left_join: a in Pleroma.Activity,
1814 on: u.ap_id == a.actor,
1815 where: not is_nil(u.nickname),
1816 where: u.deactivated != ^true,
1817 where: u.id not in ^has_read_notifications,
1820 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1821 is_nil(max(a.inserted_at))
1826 Enable or disable email notifications for user
1830 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1831 Pleroma.User{email_notifications: %{"digest" => true}}
1833 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1834 Pleroma.User{email_notifications: %{"digest" => false}}
1836 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1837 {:ok, t()} | {:error, Ecto.Changeset.t()}
1838 def switch_email_notifications(user, type, status) do
1839 User.update_email_notifications(user, %{type => status})
1843 Set `last_digest_emailed_at` value for the user to current time
1845 @spec touch_last_digest_emailed_at(t()) :: t()
1846 def touch_last_digest_emailed_at(user) do
1847 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1849 {:ok, updated_user} =
1851 |> change(%{last_digest_emailed_at: now})
1852 |> update_and_set_cache()
1857 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1858 def toggle_confirmation(%User{} = user) do
1860 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1861 |> update_and_set_cache()
1864 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1865 def toggle_confirmation(users) do
1866 Enum.map(users, &toggle_confirmation/1)
1869 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1873 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1874 # use instance-default
1875 config = Pleroma.Config.get([:assets, :mascots])
1876 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1877 mascot = Keyword.get(config, default_mascot)
1880 "id" => "default-mascot",
1881 "url" => mascot[:url],
1882 "preview_url" => mascot[:url],
1884 "mime_type" => mascot[:mime_type]
1889 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1891 def ensure_keys_present(%User{} = user) do
1892 with {:ok, pem} <- Keys.generate_rsa_pem() do
1894 |> cast(%{keys: pem}, [:keys])
1895 |> validate_required([:keys])
1896 |> update_and_set_cache()
1900 def get_ap_ids_by_nicknames(nicknames) do
1902 where: u.nickname in ^nicknames,
1908 defdelegate search(query, opts \\ []), to: User.Search
1910 defp put_password_hash(
1911 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1913 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1916 defp put_password_hash(changeset), do: changeset
1918 def is_internal_user?(%User{nickname: nil}), do: true
1919 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1920 def is_internal_user?(_), do: false
1922 # A hack because user delete activities have a fake id for whatever reason
1923 # TODO: Get rid of this
1924 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1926 def get_delivered_users_by_object_id(object_id) do
1928 inner_join: delivery in assoc(u, :deliveries),
1929 where: delivery.object_id == ^object_id
1934 def change_email(user, email) do
1936 |> cast(%{email: email}, [:email])
1937 |> validate_required([:email])
1938 |> unique_constraint(:email)
1939 |> validate_format(:email, @email_regex)
1940 |> update_and_set_cache()
1943 # Internal function; public one is `deactivate/2`
1944 defp set_activation_status(user, deactivated) do
1946 |> cast(%{deactivated: deactivated}, [:deactivated])
1947 |> update_and_set_cache()
1950 def update_banner(user, banner) do
1952 |> cast(%{banner: banner}, [:banner])
1953 |> update_and_set_cache()
1956 def update_background(user, background) do
1958 |> cast(%{background: background}, [:background])
1959 |> update_and_set_cache()
1962 def update_source_data(user, source_data) do
1964 |> cast(%{source_data: source_data}, [:source_data])
1965 |> update_and_set_cache()
1968 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1971 moderator: is_moderator
1975 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1976 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1977 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1978 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1981 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1982 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1986 def fields(%{fields: nil}), do: []
1988 def fields(%{fields: fields}), do: fields
1990 def sanitized_fields(%User{} = user) do
1993 |> Enum.map(fn %{"name" => name, "value" => value} ->
1996 "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2001 def validate_fields(changeset, remote? \\ false) do
2002 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2003 limit = Pleroma.Config.get([:instance, limit_name], 0)
2006 |> validate_length(:fields, max: limit)
2007 |> validate_change(:fields, fn :fields, fields ->
2008 if Enum.all?(fields, &valid_field?/1) do
2016 defp valid_field?(%{"name" => name, "value" => value}) do
2017 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
2018 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
2020 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2021 String.length(value) <= value_limit
2024 defp valid_field?(_), do: false
2026 defp truncate_field(%{"name" => name, "value" => value}) do
2028 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
2031 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
2033 %{"name" => name, "value" => value}
2036 def admin_api_update(user, params) do
2043 |> update_and_set_cache()
2046 @doc "Signs user out of all applications"
2047 def global_sign_out(user) do
2048 OAuth.Authorization.delete_user_authorizations(user)
2049 OAuth.Token.delete_user_tokens(user)
2052 def mascot_update(user, url) do
2054 |> cast(%{mascot: url}, [:mascot])
2055 |> validate_required([:mascot])
2056 |> update_and_set_cache()
2059 def mastodon_settings_update(user, settings) do
2061 |> cast(%{settings: settings}, [:settings])
2062 |> validate_required([:settings])
2063 |> update_and_set_cache()
2066 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2067 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2069 if need_confirmation? do
2071 confirmation_pending: true,
2072 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2076 confirmation_pending: false,
2077 confirmation_token: nil
2081 cast(user, params, [:confirmation_pending, :confirmation_token])
2084 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2085 if id not in user.pinned_activities do
2086 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
2087 params = %{pinned_activities: user.pinned_activities ++ [id]}
2090 |> cast(params, [:pinned_activities])
2091 |> validate_length(:pinned_activities,
2092 max: max_pinned_statuses,
2093 message: "You have already pinned the maximum number of statuses"
2098 |> update_and_set_cache()
2101 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2102 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2105 |> cast(params, [:pinned_activities])
2106 |> update_and_set_cache()
2109 def update_email_notifications(user, settings) do
2110 email_notifications =
2111 user.email_notifications
2112 |> Map.merge(settings)
2113 |> Map.take(["digest"])
2115 params = %{email_notifications: email_notifications}
2116 fields = [:email_notifications]
2119 |> cast(params, fields)
2120 |> validate_required(fields)
2121 |> update_and_set_cache()
2124 defp set_domain_blocks(user, domain_blocks) do
2125 params = %{domain_blocks: domain_blocks}
2128 |> cast(params, [:domain_blocks])
2129 |> validate_required([:domain_blocks])
2130 |> update_and_set_cache()
2133 def block_domain(user, domain_blocked) do
2134 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2137 def unblock_domain(user, domain_blocked) do
2138 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2141 @spec add_to_block(User.t(), User.t()) ::
2142 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2143 defp add_to_block(%User{} = user, %User{} = blocked) do
2144 UserRelationship.create_block(user, blocked)
2147 @spec add_to_block(User.t(), User.t()) ::
2148 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2149 defp remove_from_block(%User{} = user, %User{} = blocked) do
2150 UserRelationship.delete_block(user, blocked)
2153 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2154 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2155 {:ok, user_notification_mute} <-
2156 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2158 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2162 defp remove_from_mutes(user, %User{} = muted_user) do
2163 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2164 {:ok, user_notification_mute} <-
2165 UserRelationship.delete_notification_mute(user, muted_user) do
2166 {:ok, [user_mute, user_notification_mute]}
2170 def set_invisible(user, invisible) do
2171 params = %{invisible: invisible}
2174 |> cast(params, [:invisible])
2175 |> validate_required([:invisible])
2176 |> update_and_set_cache()
2179 def sanitize_html(%User{} = user) do
2180 sanitize_html(user, nil)
2183 # User data that mastodon isn't filtering (treated as plaintext):
2186 def sanitize_html(%User{} = user, filter) do
2190 |> Enum.map(fn %{"name" => name, "value" => value} ->
2193 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2198 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2199 |> Map.put(:fields, fields)