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]
13 alias Pleroma.Activity
15 alias Pleroma.Conversation.Participation
16 alias Pleroma.Delivery
17 alias Pleroma.EctoType.ActivityPub.ObjectValidators
19 alias Pleroma.FollowingRelationship
20 alias Pleroma.Formatter
24 alias Pleroma.Notification
26 alias Pleroma.Registration
28 alias Pleroma.RepoStreamer
30 alias Pleroma.UserRelationship
32 alias Pleroma.Web.ActivityPub.ActivityPub
33 alias Pleroma.Web.ActivityPub.Builder
34 alias Pleroma.Web.ActivityPub.Pipeline
35 alias Pleroma.Web.ActivityPub.Utils
36 alias Pleroma.Web.CommonAPI
37 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
38 alias Pleroma.Web.OAuth
39 alias Pleroma.Web.RelMe
40 alias Pleroma.Workers.BackgroundWorker
44 @type t :: %__MODULE__{}
45 @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
46 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
48 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
49 @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])?)*$/
51 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
52 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
54 # AP ID user relationships (blocks, mutes etc.)
55 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
56 @user_relationships_config [
58 blocker_blocks: :blocked_users,
59 blockee_blocks: :blocker_users
62 muter_mutes: :muted_users,
63 mutee_mutes: :muter_users
66 reblog_muter_mutes: :reblog_muted_users,
67 reblog_mutee_mutes: :reblog_muter_users
70 notification_muter_mutes: :notification_muted_users,
71 notification_mutee_mutes: :notification_muter_users
73 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
74 inverse_subscription: [
75 subscribee_subscriptions: :subscriber_users,
76 subscriber_subscriptions: :subscribee_users
82 field(:raw_bio, :string)
83 field(:email, :string)
85 field(:nickname, :string)
86 field(:password_hash, :string)
87 field(:password, :string, virtual: true)
88 field(:password_confirmation, :string, virtual: true)
90 field(:public_key, :string)
91 field(:ap_id, :string)
92 field(:avatar, :map, default: %{})
93 field(:local, :boolean, default: true)
94 field(:follower_address, :string)
95 field(:following_address, :string)
96 field(:search_rank, :float, virtual: true)
97 field(:search_type, :integer, virtual: true)
98 field(:tags, {:array, :string}, default: [])
99 field(:last_refreshed_at, :naive_datetime_usec)
100 field(:last_digest_emailed_at, :naive_datetime)
101 field(:banner, :map, default: %{})
102 field(:background, :map, default: %{})
103 field(:note_count, :integer, default: 0)
104 field(:follower_count, :integer, default: 0)
105 field(:following_count, :integer, default: 0)
106 field(:locked, :boolean, default: false)
107 field(:confirmation_pending, :boolean, default: false)
108 field(:password_reset_pending, :boolean, default: false)
109 field(:confirmation_token, :string, default: nil)
110 field(:default_scope, :string, default: "public")
111 field(:domain_blocks, {:array, :string}, default: [])
112 field(:deactivated, :boolean, default: false)
113 field(:no_rich_text, :boolean, default: false)
114 field(:ap_enabled, :boolean, default: false)
115 field(:is_moderator, :boolean, default: false)
116 field(:is_admin, :boolean, default: false)
117 field(:show_role, :boolean, default: true)
118 field(:mastofe_settings, :map, default: nil)
119 field(:uri, ObjectValidators.Uri, default: nil)
120 field(:hide_followers_count, :boolean, default: false)
121 field(:hide_follows_count, :boolean, default: false)
122 field(:hide_followers, :boolean, default: false)
123 field(:hide_follows, :boolean, default: false)
124 field(:hide_favorites, :boolean, default: true)
125 field(:unread_conversation_count, :integer, default: 0)
126 field(:pinned_activities, {:array, :string}, default: [])
127 field(:email_notifications, :map, default: %{"digest" => false})
128 field(:mascot, :map, default: nil)
129 field(:emoji, :map, default: %{})
130 field(:pleroma_settings_store, :map, default: %{})
131 field(:fields, {:array, :map}, default: [])
132 field(:raw_fields, {:array, :map}, default: [])
133 field(:discoverable, :boolean, default: false)
134 field(:invisible, :boolean, default: false)
135 field(:allow_following_move, :boolean, default: true)
136 field(:skip_thread_containment, :boolean, default: false)
137 field(:actor_type, :string, default: "Person")
138 field(:also_known_as, {:array, :string}, default: [])
139 field(:inbox, :string)
140 field(:shared_inbox, :string)
141 field(:accepts_chat_messages, :boolean, default: nil)
144 :notification_settings,
145 Pleroma.User.NotificationSetting,
149 has_many(:notifications, Notification)
150 has_many(:registrations, Registration)
151 has_many(:deliveries, Delivery)
153 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
154 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
156 for {relationship_type,
158 {outgoing_relation, outgoing_relation_target},
159 {incoming_relation, incoming_relation_source}
160 ]} <- @user_relationships_config do
161 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
162 # :notification_muter_mutes, :subscribee_subscriptions
163 has_many(outgoing_relation, UserRelationship,
164 foreign_key: :source_id,
165 where: [relationship_type: relationship_type]
168 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
169 # :notification_mutee_mutes, :subscriber_subscriptions
170 has_many(incoming_relation, UserRelationship,
171 foreign_key: :target_id,
172 where: [relationship_type: relationship_type]
175 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
176 # :notification_muted_users, :subscriber_users
177 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
179 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
180 # :notification_muter_users, :subscribee_users
181 has_many(incoming_relation_source, through: [incoming_relation, :source])
184 # `:blocks` is deprecated (replaced with `blocked_users` relation)
185 field(:blocks, {:array, :string}, default: [])
186 # `:mutes` is deprecated (replaced with `muted_users` relation)
187 field(:mutes, {:array, :string}, default: [])
188 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
189 field(:muted_reblogs, {:array, :string}, default: [])
190 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
191 field(:muted_notifications, {:array, :string}, default: [])
192 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
193 field(:subscribers, {:array, :string}, default: [])
196 :multi_factor_authentication_settings,
204 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
205 @user_relationships_config do
206 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
207 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
208 # `def subscriber_users/2`
209 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
210 target_users_query = assoc(user, unquote(outgoing_relation_target))
212 if restrict_deactivated? do
213 restrict_deactivated(target_users_query)
219 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
220 # `def notification_muted_users/2`, `def subscriber_users/2`
221 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
223 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
225 restrict_deactivated?
230 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
231 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
232 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
234 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
236 restrict_deactivated?
238 |> select([u], u.ap_id)
244 Dumps Flake Id to SQL-compatible format (16-byte UUID).
245 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
247 def binary_id(source_id) when is_binary(source_id) do
248 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
255 def binary_id(source_ids) when is_list(source_ids) do
256 Enum.map(source_ids, &binary_id/1)
259 def binary_id(%User{} = user), do: binary_id(user.id)
261 @doc "Returns status account"
262 @spec account_status(User.t()) :: account_status()
263 def account_status(%User{deactivated: true}), do: :deactivated
264 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
266 def account_status(%User{confirmation_pending: true}) do
267 if Config.get([:instance, :account_activation_required]) do
268 :confirmation_pending
274 def account_status(%User{}), do: :active
276 @spec visible_for(User.t(), User.t() | nil) ::
279 | :restricted_unauthenticated
281 | :confirmation_pending
282 def visible_for(user, for_user \\ nil)
284 def visible_for(%User{invisible: true}, _), do: :invisible
286 def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
288 def visible_for(%User{} = user, nil) do
289 if restrict_unauthenticated?(user) do
290 :restrict_unauthenticated
292 visible_account_status(user)
296 def visible_for(%User{} = user, for_user) do
297 if superuser?(for_user) do
300 visible_account_status(user)
304 def visible_for(_, _), do: :invisible
306 defp restrict_unauthenticated?(%User{local: local}) do
307 config_key = if local, do: :local, else: :remote
309 Config.get([:restrict_unauthenticated, :profiles, config_key], false)
312 defp visible_account_status(user) do
313 status = account_status(user)
315 if status in [:active, :password_reset_pending] do
322 @spec superuser?(User.t()) :: boolean()
323 def superuser?(%User{local: true, is_admin: true}), do: true
324 def superuser?(%User{local: true, is_moderator: true}), do: true
325 def superuser?(_), do: false
327 @spec invisible?(User.t()) :: boolean()
328 def invisible?(%User{invisible: true}), do: true
329 def invisible?(_), do: false
331 def avatar_url(user, options \\ []) do
333 %{"url" => [%{"href" => href} | _]} ->
337 unless options[:no_default] do
338 Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png")
343 def banner_url(user, options \\ []) do
345 %{"url" => [%{"href" => href} | _]} -> href
346 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
350 # Should probably be renamed or removed
351 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
353 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
354 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
356 @spec ap_following(User.t()) :: String.t()
357 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
358 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
360 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
361 def restrict_deactivated(query) do
362 from(u in query, where: u.deactivated != ^true)
365 defdelegate following_count(user), to: FollowingRelationship
367 defp truncate_fields_param(params) do
368 if Map.has_key?(params, :fields) do
369 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
375 defp truncate_if_exists(params, key, max_length) do
376 if Map.has_key?(params, key) and is_binary(params[key]) do
377 {value, _chopped} = String.split_at(params[key], max_length)
378 Map.put(params, key, value)
384 defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
386 defp fix_follower_address(%{nickname: nickname} = params),
387 do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
389 defp fix_follower_address(params), do: params
391 def remote_user_changeset(struct \\ %User{local: false}, params) do
392 bio_limit = Config.get([:instance, :user_bio_length], 5000)
393 name_limit = Config.get([:instance, :user_name_length], 100)
396 case params[:name] do
397 name when is_binary(name) and byte_size(name) > 0 -> name
398 _ -> params[:nickname]
403 |> Map.put(:name, name)
404 |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
405 |> truncate_if_exists(:name, name_limit)
406 |> truncate_if_exists(:bio, bio_limit)
407 |> truncate_fields_param()
408 |> fix_follower_address()
432 :hide_followers_count,
441 :accepts_chat_messages
444 |> validate_required([:name, :ap_id])
445 |> unique_constraint(:nickname)
446 |> validate_format(:nickname, @email_regex)
447 |> validate_length(:bio, max: bio_limit)
448 |> validate_length(:name, max: name_limit)
449 |> validate_fields(true)
452 def update_changeset(struct, params \\ %{}) do
453 bio_limit = Config.get([:instance, :user_bio_length], 5000)
454 name_limit = Config.get([:instance, :user_name_length], 100)
474 :hide_followers_count,
477 :allow_following_move,
480 :skip_thread_containment,
483 :pleroma_settings_store,
487 :accepts_chat_messages
490 |> unique_constraint(:nickname)
491 |> validate_format(:nickname, local_nickname_regex())
492 |> validate_length(:bio, max: bio_limit)
493 |> validate_length(:name, min: 1, max: name_limit)
494 |> validate_inclusion(:actor_type, ["Person", "Service"])
497 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
498 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
499 |> put_change_if_present(:banner, &put_upload(&1, :banner))
500 |> put_change_if_present(:background, &put_upload(&1, :background))
501 |> put_change_if_present(
502 :pleroma_settings_store,
503 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
505 |> validate_fields(false)
508 defp put_fields(changeset) do
509 if raw_fields = get_change(changeset, :raw_fields) do
512 |> Enum.filter(fn %{"name" => n} -> n != "" end)
516 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
519 |> put_change(:raw_fields, raw_fields)
520 |> put_change(:fields, fields)
526 defp parse_fields(value) do
528 |> Formatter.linkify(mentions_format: :full)
532 defp put_emoji(changeset) do
533 bio = get_change(changeset, :bio)
534 name = get_change(changeset, :name)
537 emoji = Map.merge(Emoji.Formatter.get_emoji_map(bio), Emoji.Formatter.get_emoji_map(name))
538 put_change(changeset, :emoji, emoji)
544 defp put_change_if_present(changeset, map_field, value_function) do
545 with {:ok, value} <- fetch_change(changeset, map_field),
546 {:ok, new_value} <- value_function.(value) do
547 put_change(changeset, map_field, new_value)
553 defp put_upload(value, type) do
554 with %Plug.Upload{} <- value,
555 {:ok, object} <- ActivityPub.upload(value, type: type) do
560 def update_as_admin_changeset(struct, params) do
562 |> update_changeset(params)
563 |> cast(params, [:email])
564 |> delete_change(:also_known_as)
565 |> unique_constraint(:email)
566 |> validate_format(:email, @email_regex)
567 |> validate_inclusion(:actor_type, ["Person", "Service"])
570 @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
571 def update_as_admin(user, params) do
572 params = Map.put(params, "password_confirmation", params["password"])
573 changeset = update_as_admin_changeset(user, params)
575 if params["password"] do
576 reset_password(user, changeset, params)
578 User.update_and_set_cache(changeset)
582 def password_update_changeset(struct, params) do
584 |> cast(params, [:password, :password_confirmation])
585 |> validate_required([:password, :password_confirmation])
586 |> validate_confirmation(:password)
587 |> put_password_hash()
588 |> put_change(:password_reset_pending, false)
591 @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
592 def reset_password(%User{} = user, params) do
593 reset_password(user, user, params)
596 def reset_password(%User{id: user_id} = user, struct, params) do
599 |> Multi.update(:user, password_update_changeset(struct, params))
600 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
601 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
603 case Repo.transaction(multi) do
604 {:ok, %{user: user} = _} -> set_cache(user)
605 {:error, _, changeset, _} -> {:error, changeset}
609 def update_password_reset_pending(user, value) do
612 |> put_change(:password_reset_pending, value)
613 |> update_and_set_cache()
616 def force_password_reset_async(user) do
617 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
620 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
621 def force_password_reset(user), do: update_password_reset_pending(user, true)
623 def register_changeset(struct, params \\ %{}, opts \\ []) do
624 bio_limit = Config.get([:instance, :user_bio_length], 5000)
625 name_limit = Config.get([:instance, :user_name_length], 100)
626 params = Map.put_new(params, :accepts_chat_messages, true)
629 if is_nil(opts[:need_confirmation]) do
630 Config.get([:instance, :account_activation_required])
632 opts[:need_confirmation]
636 |> confirmation_changeset(need_confirmation: need_confirmation?)
644 :password_confirmation,
646 :accepts_chat_messages
648 |> validate_required([:name, :nickname, :password, :password_confirmation])
649 |> validate_confirmation(:password)
650 |> unique_constraint(:email)
651 |> unique_constraint(:nickname)
652 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
653 |> validate_format(:nickname, local_nickname_regex())
654 |> validate_format(:email, @email_regex)
655 |> validate_length(:bio, max: bio_limit)
656 |> validate_length(:name, min: 1, max: name_limit)
657 |> maybe_validate_required_email(opts[:external])
660 |> unique_constraint(:ap_id)
661 |> put_following_and_follower_address()
664 def maybe_validate_required_email(changeset, true), do: changeset
666 def maybe_validate_required_email(changeset, _) do
667 if Config.get([:instance, :account_activation_required]) do
668 validate_required(changeset, [:email])
674 defp put_ap_id(changeset) do
675 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
676 put_change(changeset, :ap_id, ap_id)
679 defp put_following_and_follower_address(changeset) do
680 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
683 |> put_change(:follower_address, followers)
686 defp autofollow_users(user) do
687 candidates = Config.get([:instance, :autofollowed_nicknames])
690 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
693 follow_all(user, autofollowed_users)
696 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
697 def register(%Ecto.Changeset{} = changeset) do
698 with {:ok, user} <- Repo.insert(changeset) do
699 post_register_action(user)
703 def post_register_action(%User{} = user) do
704 with {:ok, user} <- autofollow_users(user),
705 {:ok, user} <- set_cache(user),
706 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
707 {:ok, _} <- try_send_confirmation_email(user) do
712 @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
713 def try_send_confirmation_email(%User{confirmation_pending: true} = user) do
714 if Config.get([:instance, :account_activation_required]) do
715 send_confirmation_email(user)
722 def try_send_confirmation_email(_), do: {:ok, :noop}
724 @spec send_confirmation_email(Uset.t()) :: User.t()
725 def send_confirmation_email(%User{} = user) do
727 |> Pleroma.Emails.UserEmail.account_confirmation_email()
728 |> Pleroma.Emails.Mailer.deliver_async()
733 def needs_update?(%User{local: true}), do: false
735 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
737 def needs_update?(%User{local: false} = user) do
738 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
741 def needs_update?(_), do: true
743 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
745 # "Locked" (self-locked) users demand explicit authorization of follow requests
746 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
747 follow(follower, followed, :follow_pending)
750 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
751 follow(follower, followed)
754 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
755 if not ap_enabled?(followed) do
756 follow(follower, followed)
762 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
763 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
764 def follow_all(follower, followeds) do
766 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
767 |> Enum.each(&follow(follower, &1, :follow_accept))
772 defdelegate following(user), to: FollowingRelationship
774 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
775 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
778 followed.deactivated ->
779 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
781 deny_follow_blocked and blocks?(followed, follower) ->
782 {:error, "Could not follow user: #{followed.nickname} blocked you."}
785 FollowingRelationship.follow(follower, followed, state)
787 {:ok, _} = update_follower_count(followed)
790 |> update_following_count()
794 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
795 {:error, "Not subscribed!"}
798 @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
799 def unfollow(%User{} = follower, %User{} = followed) do
800 case do_unfollow(follower, followed) do
801 {:ok, follower, followed} ->
802 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
809 @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
810 defp do_unfollow(%User{} = follower, %User{} = followed) do
811 case get_follow_state(follower, followed) do
812 state when state in [:follow_pending, :follow_accept] ->
813 FollowingRelationship.unfollow(follower, followed)
814 {:ok, followed} = update_follower_count(followed)
818 |> update_following_count()
820 {:ok, follower, followed}
823 {:error, "Not subscribed!"}
827 defdelegate following?(follower, followed), to: FollowingRelationship
829 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
830 def get_follow_state(%User{} = follower, %User{} = following) do
831 following_relationship = FollowingRelationship.get(follower, following)
832 get_follow_state(follower, following, following_relationship)
835 def get_follow_state(
838 following_relationship
840 case {following_relationship, following.local} do
842 case Utils.fetch_latest_follow(follower, following) do
843 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
844 FollowingRelationship.state_to_enum(state)
850 {%{state: state}, _} ->
858 def locked?(%User{} = user) do
863 Repo.get_by(User, id: id)
866 def get_by_ap_id(ap_id) do
867 Repo.get_by(User, ap_id: ap_id)
870 def get_all_by_ap_id(ap_ids) do
871 from(u in __MODULE__,
872 where: u.ap_id in ^ap_ids
877 def get_all_by_ids(ids) do
878 from(u in __MODULE__, where: u.id in ^ids)
882 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
883 # of the ap_id and the domain and tries to get that user
884 def get_by_guessed_nickname(ap_id) do
885 domain = URI.parse(ap_id).host
886 name = List.last(String.split(ap_id, "/"))
887 nickname = "#{name}@#{domain}"
889 get_cached_by_nickname(nickname)
892 def set_cache({:ok, user}), do: set_cache(user)
893 def set_cache({:error, err}), do: {:error, err}
895 def set_cache(%User{} = user) do
896 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
897 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
898 Cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
902 def update_and_set_cache(struct, params) do
904 |> update_changeset(params)
905 |> update_and_set_cache()
908 def update_and_set_cache(changeset) do
909 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
914 def get_user_friends_ap_ids(user) do
915 from(u in User.get_friends_query(user), select: u.ap_id)
919 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
920 def get_cached_user_friends_ap_ids(user) do
921 Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
922 get_user_friends_ap_ids(user)
926 def invalidate_cache(user) do
927 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
928 Cachex.del(:user_cache, "nickname:#{user.nickname}")
929 Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
932 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
933 def get_cached_by_ap_id(ap_id) do
934 key = "ap_id:#{ap_id}"
936 with {:ok, nil} <- Cachex.get(:user_cache, key),
937 user when not is_nil(user) <- get_by_ap_id(ap_id),
938 {:ok, true} <- Cachex.put(:user_cache, key, user) do
946 def get_cached_by_id(id) do
950 Cachex.fetch!(:user_cache, key, fn _ ->
954 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
955 {:commit, user.ap_id}
961 get_cached_by_ap_id(ap_id)
964 def get_cached_by_nickname(nickname) do
965 key = "nickname:#{nickname}"
967 Cachex.fetch!(:user_cache, key, fn ->
968 case get_or_fetch_by_nickname(nickname) do
969 {:ok, user} -> {:commit, user}
970 {:error, _error} -> {:ignore, nil}
975 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
976 restrict_to_local = Config.get([:instance, :limit_to_local_content])
979 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
980 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
982 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
983 get_cached_by_nickname(nickname_or_id)
985 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
986 get_cached_by_nickname(nickname_or_id)
993 @spec get_by_nickname(String.t()) :: User.t() | nil
994 def get_by_nickname(nickname) do
995 Repo.get_by(User, nickname: nickname) ||
996 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
997 Repo.get_by(User, nickname: local_nickname(nickname))
1001 def get_by_email(email), do: Repo.get_by(User, email: email)
1003 def get_by_nickname_or_email(nickname_or_email) do
1004 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
1007 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
1009 def get_or_fetch_by_nickname(nickname) do
1010 with %User{} = user <- get_by_nickname(nickname) do
1014 with [_nick, _domain] <- String.split(nickname, "@"),
1015 {:ok, user} <- fetch_by_nickname(nickname) do
1018 _e -> {:error, "not found " <> nickname}
1023 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1024 def get_followers_query(%User{} = user, nil) do
1025 User.Query.build(%{followers: user, deactivated: false})
1028 def get_followers_query(user, page) do
1030 |> get_followers_query(nil)
1031 |> User.Query.paginate(page, 20)
1034 @spec get_followers_query(User.t()) :: Ecto.Query.t()
1035 def get_followers_query(user), do: get_followers_query(user, nil)
1037 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1038 def get_followers(user, page \\ nil) do
1040 |> get_followers_query(page)
1044 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1045 def get_external_followers(user, page \\ nil) do
1047 |> get_followers_query(page)
1048 |> User.Query.build(%{external: true})
1052 def get_followers_ids(user, page \\ nil) do
1054 |> get_followers_query(page)
1055 |> select([u], u.id)
1059 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1060 def get_friends_query(%User{} = user, nil) do
1061 User.Query.build(%{friends: user, deactivated: false})
1064 def get_friends_query(user, page) do
1066 |> get_friends_query(nil)
1067 |> User.Query.paginate(page, 20)
1070 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1071 def get_friends_query(user), do: get_friends_query(user, nil)
1073 def get_friends(user, page \\ nil) do
1075 |> get_friends_query(page)
1079 def get_friends_ap_ids(user) do
1081 |> get_friends_query(nil)
1082 |> select([u], u.ap_id)
1086 def get_friends_ids(user, page \\ nil) do
1088 |> get_friends_query(page)
1089 |> select([u], u.id)
1093 defdelegate get_follow_requests(user), to: FollowingRelationship
1095 def increase_note_count(%User{} = user) do
1097 |> where(id: ^user.id)
1098 |> update([u], inc: [note_count: 1])
1100 |> Repo.update_all([])
1102 {1, [user]} -> set_cache(user)
1107 def decrease_note_count(%User{} = user) do
1109 |> where(id: ^user.id)
1112 note_count: fragment("greatest(0, note_count - 1)")
1116 |> Repo.update_all([])
1118 {1, [user]} -> set_cache(user)
1123 def update_note_count(%User{} = user, note_count \\ nil) do
1128 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1134 |> cast(%{note_count: note_count}, [:note_count])
1135 |> update_and_set_cache()
1138 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1139 def maybe_fetch_follow_information(user) do
1140 with {:ok, user} <- fetch_follow_information(user) do
1144 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1150 def fetch_follow_information(user) do
1151 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1153 |> follow_information_changeset(info)
1154 |> update_and_set_cache()
1158 defp follow_information_changeset(user, params) do
1165 :hide_followers_count,
1170 @spec update_follower_count(User.t()) :: {:ok, User.t()}
1171 def update_follower_count(%User{} = user) do
1172 if user.local or !Config.get([:instance, :external_user_synchronization]) do
1173 follower_count = FollowingRelationship.follower_count(user)
1176 |> follow_information_changeset(%{follower_count: follower_count})
1177 |> update_and_set_cache
1179 {:ok, maybe_fetch_follow_information(user)}
1183 @spec update_following_count(User.t()) :: {:ok, User.t()}
1184 def update_following_count(%User{local: false} = user) do
1185 if Config.get([:instance, :external_user_synchronization]) do
1186 {:ok, maybe_fetch_follow_information(user)}
1192 def update_following_count(%User{local: true} = user) do
1193 following_count = FollowingRelationship.following_count(user)
1196 |> follow_information_changeset(%{following_count: following_count})
1197 |> update_and_set_cache()
1200 def set_unread_conversation_count(%User{local: true} = user) do
1201 unread_query = Participation.unread_conversation_count_for_user(user)
1204 |> join(:inner, [u], p in subquery(unread_query))
1206 set: [unread_conversation_count: p.count]
1208 |> where([u], u.id == ^user.id)
1210 |> Repo.update_all([])
1212 {1, [user]} -> set_cache(user)
1217 def set_unread_conversation_count(user), do: {:ok, user}
1219 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1221 Participation.unread_conversation_count_for_user(user)
1222 |> where([p], p.conversation_id == ^conversation.id)
1225 |> join(:inner, [u], p in subquery(unread_query))
1227 inc: [unread_conversation_count: 1]
1229 |> where([u], u.id == ^user.id)
1230 |> where([u, p], p.count == 0)
1232 |> Repo.update_all([])
1234 {1, [user]} -> set_cache(user)
1239 def increment_unread_conversation_count(_, user), do: {:ok, user}
1241 @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
1242 def get_users_from_set(ap_ids, opts \\ []) do
1243 local_only = Keyword.get(opts, :local_only, true)
1244 criteria = %{ap_id: ap_ids, deactivated: false}
1245 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1247 User.Query.build(criteria)
1251 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1252 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1255 query = User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1261 @spec mute(User.t(), User.t(), boolean()) ::
1262 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1263 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1264 add_to_mutes(muter, mutee, notifications?)
1267 def unmute(%User{} = muter, %User{} = mutee) do
1268 remove_from_mutes(muter, mutee)
1271 def subscribe(%User{} = subscriber, %User{} = target) do
1272 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
1274 if blocks?(target, subscriber) and deny_follow_blocked do
1275 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1277 # Note: the relationship is inverse: subscriber acts as relationship target
1278 UserRelationship.create_inverse_subscription(target, subscriber)
1282 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1283 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1284 subscribe(subscriber, subscribee)
1288 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1289 # Note: the relationship is inverse: subscriber acts as relationship target
1290 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1293 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1294 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1295 unsubscribe(unsubscriber, user)
1299 def block(%User{} = blocker, %User{} = blocked) do
1300 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1302 if following?(blocker, blocked) do
1303 {:ok, blocker, _} = unfollow(blocker, blocked)
1309 # clear any requested follows as well
1311 case CommonAPI.reject_follow_request(blocked, blocker) do
1312 {:ok, %User{} = updated_blocked} -> updated_blocked
1316 unsubscribe(blocked, blocker)
1318 unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
1319 if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
1321 {:ok, blocker} = update_follower_count(blocker)
1322 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1323 add_to_block(blocker, blocked)
1326 # helper to handle the block given only an actor's AP id
1327 def block(%User{} = blocker, %{ap_id: ap_id}) do
1328 block(blocker, get_cached_by_ap_id(ap_id))
1331 def unblock(%User{} = blocker, %User{} = blocked) do
1332 remove_from_block(blocker, blocked)
1335 # helper to handle the block given only an actor's AP id
1336 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1337 unblock(blocker, get_cached_by_ap_id(ap_id))
1340 def mutes?(nil, _), do: false
1341 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1343 def mutes_user?(%User{} = user, %User{} = target) do
1344 UserRelationship.mute_exists?(user, target)
1347 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1348 def muted_notifications?(nil, _), do: false
1350 def muted_notifications?(%User{} = user, %User{} = target),
1351 do: UserRelationship.notification_mute_exists?(user, target)
1353 def blocks?(nil, _), do: false
1355 def blocks?(%User{} = user, %User{} = target) do
1356 blocks_user?(user, target) ||
1357 (blocks_domain?(user, target) and not User.following?(user, target))
1360 def blocks_user?(%User{} = user, %User{} = target) do
1361 UserRelationship.block_exists?(user, target)
1364 def blocks_user?(_, _), do: false
1366 def blocks_domain?(%User{} = user, %User{} = target) do
1367 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1368 %{host: host} = URI.parse(target.ap_id)
1369 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1372 def blocks_domain?(_, _), do: false
1374 def subscribed_to?(%User{} = user, %User{} = target) do
1375 # Note: the relationship is inverse: subscriber acts as relationship target
1376 UserRelationship.inverse_subscription_exists?(target, user)
1379 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1380 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1381 subscribed_to?(user, target)
1386 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1387 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1389 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1390 def outgoing_relationships_ap_ids(_user, []), do: %{}
1392 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1394 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1395 when is_list(relationship_types) do
1398 |> assoc(:outgoing_relationships)
1399 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1400 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1401 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1402 |> group_by([user_rel, u], user_rel.relationship_type)
1404 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1409 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1413 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1415 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1417 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1419 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1420 when is_list(relationship_types) do
1422 |> assoc(:incoming_relationships)
1423 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1424 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1425 |> maybe_filter_on_ap_id(ap_ids)
1426 |> select([user_rel, u], u.ap_id)
1431 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1432 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1435 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1437 def deactivate_async(user, status \\ true) do
1438 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1441 def deactivate(user, status \\ true)
1443 def deactivate(users, status) when is_list(users) do
1444 Repo.transaction(fn ->
1445 for user <- users, do: deactivate(user, status)
1449 def deactivate(%User{} = user, status) do
1450 with {:ok, user} <- set_activation_status(user, status) do
1453 |> Enum.filter(& &1.local)
1454 |> Enum.each(&set_cache(update_following_count(&1)))
1456 # Only update local user counts, remote will be update during the next pull.
1459 |> Enum.filter(& &1.local)
1460 |> Enum.each(&do_unfollow(user, &1))
1466 def update_notification_settings(%User{} = user, settings) do
1468 |> cast(%{notification_settings: settings}, [])
1469 |> cast_embed(:notification_settings)
1470 |> validate_required([:notification_settings])
1471 |> update_and_set_cache()
1474 def delete(users) when is_list(users) do
1475 for user <- users, do: delete(user)
1478 def delete(%User{} = user) do
1479 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1482 defp delete_and_invalidate_cache(%User{} = user) do
1483 invalidate_cache(user)
1487 defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user)
1489 defp delete_or_deactivate(%User{local: true} = user) do
1490 status = account_status(user)
1492 if status == :confirmation_pending do
1493 delete_and_invalidate_cache(user)
1496 |> change(%{deactivated: true, email: nil})
1497 |> update_and_set_cache()
1501 def perform(:force_password_reset, user), do: force_password_reset(user)
1503 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1504 def perform(:delete, %User{} = user) do
1505 # Remove all relationships
1508 |> Enum.each(fn follower ->
1509 ActivityPub.unfollow(follower, user)
1510 unfollow(follower, user)
1515 |> Enum.each(fn followed ->
1516 ActivityPub.unfollow(user, followed)
1517 unfollow(user, followed)
1520 delete_user_activities(user)
1521 delete_notifications_from_user_activities(user)
1523 delete_outgoing_pending_follow_requests(user)
1525 delete_or_deactivate(user)
1528 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1530 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1531 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1532 when is_list(blocked_identifiers) do
1534 blocked_identifiers,
1535 fn blocked_identifier ->
1536 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1537 {:ok, _block} <- CommonAPI.block(blocker, blocked) do
1541 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1548 def perform(:follow_import, %User{} = follower, followed_identifiers)
1549 when is_list(followed_identifiers) do
1551 followed_identifiers,
1552 fn followed_identifier ->
1553 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1554 {:ok, follower} <- maybe_direct_follow(follower, followed),
1555 {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do
1559 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1566 @spec external_users_query() :: Ecto.Query.t()
1567 def external_users_query do
1575 @spec external_users(keyword()) :: [User.t()]
1576 def external_users(opts \\ []) do
1578 external_users_query()
1579 |> select([u], struct(u, [:id, :ap_id]))
1583 do: where(query, [u], u.id > ^opts[:max_id]),
1588 do: limit(query, ^opts[:limit]),
1594 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1595 BackgroundWorker.enqueue("blocks_import", %{
1596 "blocker_id" => blocker.id,
1597 "blocked_identifiers" => blocked_identifiers
1601 def follow_import(%User{} = follower, followed_identifiers)
1602 when is_list(followed_identifiers) do
1603 BackgroundWorker.enqueue("follow_import", %{
1604 "follower_id" => follower.id,
1605 "followed_identifiers" => followed_identifiers
1609 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1611 |> join(:inner, [n], activity in assoc(n, :activity))
1612 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1613 |> Repo.delete_all()
1616 def delete_user_activities(%User{ap_id: ap_id} = user) do
1618 |> Activity.Queries.by_actor()
1619 |> RepoStreamer.chunk_stream(50)
1620 |> Stream.each(fn activities ->
1621 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1626 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1627 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1628 {:ok, delete_data, _} <- Builder.delete(user, object) do
1629 Pipeline.common_pipeline(delete_data, local: user.local)
1631 {:find_object, nil} ->
1632 # We have the create activity, but not the object, it was probably pruned.
1633 # Insert a tombstone and try again
1634 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1635 {:ok, _tombstone} <- Object.create(tombstone_data) do
1636 delete_activity(activity, user)
1640 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1641 Logger.error("Error: #{inspect(e)}")
1645 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1646 when type in ["Like", "Announce"] do
1647 {:ok, undo, _} = Builder.undo(user, activity)
1648 Pipeline.common_pipeline(undo, local: user.local)
1651 defp delete_activity(_activity, _user), do: "Doing nothing"
1653 defp delete_outgoing_pending_follow_requests(user) do
1655 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1656 |> Repo.delete_all()
1659 def html_filter_policy(%User{no_rich_text: true}) do
1660 Pleroma.HTML.Scrubber.TwitterText
1663 def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
1665 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1667 def get_or_fetch_by_ap_id(ap_id) do
1668 cached_user = get_cached_by_ap_id(ap_id)
1670 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
1672 case {cached_user, maybe_fetched_user} do
1673 {_, {:ok, %User{} = user}} ->
1676 {%User{} = user, _} ->
1680 {:error, :not_found}
1685 Creates an internal service actor by URI if missing.
1686 Optionally takes nickname for addressing.
1688 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1689 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1691 case get_cached_by_ap_id(uri) do
1693 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1694 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1698 %User{invisible: false} = user ->
1708 @spec set_invisible(User.t()) :: {:ok, User.t()}
1709 defp set_invisible(user) do
1711 |> change(%{invisible: true})
1712 |> update_and_set_cache()
1715 @spec create_service_actor(String.t(), String.t()) ::
1716 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1717 defp create_service_actor(uri, nickname) do
1723 follower_address: uri <> "/followers"
1726 |> unique_constraint(:nickname)
1731 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1734 |> :public_key.pem_decode()
1736 |> :public_key.pem_entry_decode()
1741 def public_key(_), do: {:error, "key not found"}
1743 def get_public_key_for_ap_id(ap_id) do
1744 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1745 {:ok, public_key} <- public_key(user) do
1752 def ap_enabled?(%User{local: true}), do: true
1753 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1754 def ap_enabled?(_), do: false
1756 @doc "Gets or fetch a user by uri or nickname."
1757 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1758 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1759 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1761 # wait a period of time and return newest version of the User structs
1762 # this is because we have synchronous follow APIs and need to simulate them
1763 # with an async handshake
1764 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1765 with %User{} = a <- get_cached_by_id(a.id),
1766 %User{} = b <- get_cached_by_id(b.id) do
1773 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1774 with :ok <- :timer.sleep(timeout),
1775 %User{} = a <- get_cached_by_id(a.id),
1776 %User{} = b <- get_cached_by_id(b.id) do
1783 def parse_bio(bio) when is_binary(bio) and bio != "" do
1785 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1789 def parse_bio(_), do: ""
1791 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1792 # TODO: get profile URLs other than user.ap_id
1793 profile_urls = [user.ap_id]
1796 |> CommonUtils.format_input("text/plain",
1797 mentions_format: :full,
1798 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1803 def parse_bio(_, _), do: ""
1805 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1806 Repo.transaction(fn ->
1807 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1811 def tag(nickname, tags) when is_binary(nickname),
1812 do: tag(get_by_nickname(nickname), tags)
1814 def tag(%User{} = user, tags),
1815 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1817 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1818 Repo.transaction(fn ->
1819 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1823 def untag(nickname, tags) when is_binary(nickname),
1824 do: untag(get_by_nickname(nickname), tags)
1826 def untag(%User{} = user, tags),
1827 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1829 defp update_tags(%User{} = user, new_tags) do
1830 {:ok, updated_user} =
1832 |> change(%{tags: new_tags})
1833 |> update_and_set_cache()
1838 defp normalize_tags(tags) do
1841 |> Enum.map(&String.downcase/1)
1844 defp local_nickname_regex do
1845 if Config.get([:instance, :extended_nickname_format]) do
1846 @extended_local_nickname_regex
1848 @strict_local_nickname_regex
1852 def local_nickname(nickname_or_mention) do
1855 |> String.split("@")
1859 def full_nickname(nickname_or_mention),
1860 do: String.trim_leading(nickname_or_mention, "@")
1862 def error_user(ap_id) do
1866 nickname: "erroruser@example.com",
1867 inserted_at: NaiveDateTime.utc_now()
1871 @spec all_superusers() :: [User.t()]
1872 def all_superusers do
1873 User.Query.build(%{super_users: true, local: true, deactivated: false})
1877 def muting_reblogs?(%User{} = user, %User{} = target) do
1878 UserRelationship.reblog_mute_exists?(user, target)
1881 def showing_reblogs?(%User{} = user, %User{} = target) do
1882 not muting_reblogs?(user, target)
1886 The function returns a query to get users with no activity for given interval of days.
1887 Inactive users are those who didn't read any notification, or had any activity where
1888 the user is the activity's actor, during `inactivity_threshold` days.
1889 Deactivated users will not appear in this list.
1893 iex> Pleroma.User.list_inactive_users()
1896 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1897 def list_inactive_users_query(inactivity_threshold \\ 7) do
1898 negative_inactivity_threshold = -inactivity_threshold
1899 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1900 # Subqueries are not supported in `where` clauses, join gets too complicated.
1901 has_read_notifications =
1902 from(n in Pleroma.Notification,
1903 where: n.seen == true,
1905 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1908 |> Pleroma.Repo.all()
1910 from(u in Pleroma.User,
1911 left_join: a in Pleroma.Activity,
1912 on: u.ap_id == a.actor,
1913 where: not is_nil(u.nickname),
1914 where: u.deactivated != ^true,
1915 where: u.id not in ^has_read_notifications,
1918 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1919 is_nil(max(a.inserted_at))
1924 Enable or disable email notifications for user
1928 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1929 Pleroma.User{email_notifications: %{"digest" => true}}
1931 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1932 Pleroma.User{email_notifications: %{"digest" => false}}
1934 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1935 {:ok, t()} | {:error, Ecto.Changeset.t()}
1936 def switch_email_notifications(user, type, status) do
1937 User.update_email_notifications(user, %{type => status})
1941 Set `last_digest_emailed_at` value for the user to current time
1943 @spec touch_last_digest_emailed_at(t()) :: t()
1944 def touch_last_digest_emailed_at(user) do
1945 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1947 {:ok, updated_user} =
1949 |> change(%{last_digest_emailed_at: now})
1950 |> update_and_set_cache()
1955 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1956 def toggle_confirmation(%User{} = user) do
1958 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1959 |> update_and_set_cache()
1962 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1963 def toggle_confirmation(users) do
1964 Enum.map(users, &toggle_confirmation/1)
1967 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1971 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1972 # use instance-default
1973 config = Config.get([:assets, :mascots])
1974 default_mascot = Config.get([:assets, :default_mascot])
1975 mascot = Keyword.get(config, default_mascot)
1978 "id" => "default-mascot",
1979 "url" => mascot[:url],
1980 "preview_url" => mascot[:url],
1982 "mime_type" => mascot[:mime_type]
1987 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1989 def ensure_keys_present(%User{} = user) do
1990 with {:ok, pem} <- Keys.generate_rsa_pem() do
1992 |> cast(%{keys: pem}, [:keys])
1993 |> validate_required([:keys])
1994 |> update_and_set_cache()
1998 def get_ap_ids_by_nicknames(nicknames) do
2000 where: u.nickname in ^nicknames,
2006 defdelegate search(query, opts \\ []), to: User.Search
2008 defp put_password_hash(
2009 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2011 change(changeset, password_hash: Pbkdf2.hash_pwd_salt(password))
2014 defp put_password_hash(changeset), do: changeset
2016 def is_internal_user?(%User{nickname: nil}), do: true
2017 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2018 def is_internal_user?(_), do: false
2020 # A hack because user delete activities have a fake id for whatever reason
2021 # TODO: Get rid of this
2022 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2024 def get_delivered_users_by_object_id(object_id) do
2026 inner_join: delivery in assoc(u, :deliveries),
2027 where: delivery.object_id == ^object_id
2032 def change_email(user, email) do
2034 |> cast(%{email: email}, [:email])
2035 |> validate_required([:email])
2036 |> unique_constraint(:email)
2037 |> validate_format(:email, @email_regex)
2038 |> update_and_set_cache()
2041 # Internal function; public one is `deactivate/2`
2042 defp set_activation_status(user, deactivated) do
2044 |> cast(%{deactivated: deactivated}, [:deactivated])
2045 |> update_and_set_cache()
2048 def update_banner(user, banner) do
2050 |> cast(%{banner: banner}, [:banner])
2051 |> update_and_set_cache()
2054 def update_background(user, background) do
2056 |> cast(%{background: background}, [:background])
2057 |> update_and_set_cache()
2060 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
2063 moderator: is_moderator
2067 def validate_fields(changeset, remote? \\ false) do
2068 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2069 limit = Config.get([:instance, limit_name], 0)
2072 |> validate_length(:fields, max: limit)
2073 |> validate_change(:fields, fn :fields, fields ->
2074 if Enum.all?(fields, &valid_field?/1) do
2082 defp valid_field?(%{"name" => name, "value" => value}) do
2083 name_limit = Config.get([:instance, :account_field_name_length], 255)
2084 value_limit = Config.get([:instance, :account_field_value_length], 255)
2086 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2087 String.length(value) <= value_limit
2090 defp valid_field?(_), do: false
2092 defp truncate_field(%{"name" => name, "value" => value}) do
2094 String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
2097 String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
2099 %{"name" => name, "value" => value}
2102 def admin_api_update(user, params) do
2109 |> update_and_set_cache()
2112 @doc "Signs user out of all applications"
2113 def global_sign_out(user) do
2114 OAuth.Authorization.delete_user_authorizations(user)
2115 OAuth.Token.delete_user_tokens(user)
2118 def mascot_update(user, url) do
2120 |> cast(%{mascot: url}, [:mascot])
2121 |> validate_required([:mascot])
2122 |> update_and_set_cache()
2125 def mastodon_settings_update(user, settings) do
2127 |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
2128 |> validate_required([:mastofe_settings])
2129 |> update_and_set_cache()
2132 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2133 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2135 if need_confirmation? do
2137 confirmation_pending: true,
2138 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2142 confirmation_pending: false,
2143 confirmation_token: nil
2147 cast(user, params, [:confirmation_pending, :confirmation_token])
2150 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2151 if id not in user.pinned_activities do
2152 max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
2153 params = %{pinned_activities: user.pinned_activities ++ [id]}
2156 |> cast(params, [:pinned_activities])
2157 |> validate_length(:pinned_activities,
2158 max: max_pinned_statuses,
2159 message: "You have already pinned the maximum number of statuses"
2164 |> update_and_set_cache()
2167 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2168 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2171 |> cast(params, [:pinned_activities])
2172 |> update_and_set_cache()
2175 def update_email_notifications(user, settings) do
2176 email_notifications =
2177 user.email_notifications
2178 |> Map.merge(settings)
2179 |> Map.take(["digest"])
2181 params = %{email_notifications: email_notifications}
2182 fields = [:email_notifications]
2185 |> cast(params, fields)
2186 |> validate_required(fields)
2187 |> update_and_set_cache()
2190 defp set_domain_blocks(user, domain_blocks) do
2191 params = %{domain_blocks: domain_blocks}
2194 |> cast(params, [:domain_blocks])
2195 |> validate_required([:domain_blocks])
2196 |> update_and_set_cache()
2199 def block_domain(user, domain_blocked) do
2200 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2203 def unblock_domain(user, domain_blocked) do
2204 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2207 @spec add_to_block(User.t(), User.t()) ::
2208 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2209 defp add_to_block(%User{} = user, %User{} = blocked) do
2210 UserRelationship.create_block(user, blocked)
2213 @spec add_to_block(User.t(), User.t()) ::
2214 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2215 defp remove_from_block(%User{} = user, %User{} = blocked) do
2216 UserRelationship.delete_block(user, blocked)
2219 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2220 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2221 {:ok, user_notification_mute} <-
2222 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2224 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2228 defp remove_from_mutes(user, %User{} = muted_user) do
2229 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2230 {:ok, user_notification_mute} <-
2231 UserRelationship.delete_notification_mute(user, muted_user) do
2232 {:ok, [user_mute, user_notification_mute]}
2236 def set_invisible(user, invisible) do
2237 params = %{invisible: invisible}
2240 |> cast(params, [:invisible])
2241 |> validate_required([:invisible])
2242 |> update_and_set_cache()
2245 def sanitize_html(%User{} = user) do
2246 sanitize_html(user, nil)
2249 # User data that mastodon isn't filtering (treated as plaintext):
2252 def sanitize_html(%User{} = user, filter) do
2254 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2257 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2262 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2263 |> Map.put(:fields, fields)