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)
143 :notification_settings,
144 Pleroma.User.NotificationSetting,
148 has_many(:notifications, Notification)
149 has_many(:registrations, Registration)
150 has_many(:deliveries, Delivery)
152 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
153 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
155 for {relationship_type,
157 {outgoing_relation, outgoing_relation_target},
158 {incoming_relation, incoming_relation_source}
159 ]} <- @user_relationships_config do
160 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
161 # :notification_muter_mutes, :subscribee_subscriptions
162 has_many(outgoing_relation, UserRelationship,
163 foreign_key: :source_id,
164 where: [relationship_type: relationship_type]
167 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
168 # :notification_mutee_mutes, :subscriber_subscriptions
169 has_many(incoming_relation, UserRelationship,
170 foreign_key: :target_id,
171 where: [relationship_type: relationship_type]
174 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
175 # :notification_muted_users, :subscriber_users
176 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
178 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
179 # :notification_muter_users, :subscribee_users
180 has_many(incoming_relation_source, through: [incoming_relation, :source])
183 # `:blocks` is deprecated (replaced with `blocked_users` relation)
184 field(:blocks, {:array, :string}, default: [])
185 # `:mutes` is deprecated (replaced with `muted_users` relation)
186 field(:mutes, {:array, :string}, default: [])
187 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
188 field(:muted_reblogs, {:array, :string}, default: [])
189 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
190 field(:muted_notifications, {:array, :string}, default: [])
191 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
192 field(:subscribers, {:array, :string}, default: [])
195 :multi_factor_authentication_settings,
203 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
204 @user_relationships_config do
205 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
206 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
207 # `def subscriber_users/2`
208 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
209 target_users_query = assoc(user, unquote(outgoing_relation_target))
211 if restrict_deactivated? do
212 restrict_deactivated(target_users_query)
218 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
219 # `def notification_muted_users/2`, `def subscriber_users/2`
220 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
222 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
224 restrict_deactivated?
229 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
230 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
231 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
233 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
235 restrict_deactivated?
237 |> select([u], u.ap_id)
243 Dumps Flake Id to SQL-compatible format (16-byte UUID).
244 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
246 def binary_id(source_id) when is_binary(source_id) do
247 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
254 def binary_id(source_ids) when is_list(source_ids) do
255 Enum.map(source_ids, &binary_id/1)
258 def binary_id(%User{} = user), do: binary_id(user.id)
260 @doc "Returns status account"
261 @spec account_status(User.t()) :: account_status()
262 def account_status(%User{deactivated: true}), do: :deactivated
263 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
265 def account_status(%User{confirmation_pending: true}) do
266 if Config.get([:instance, :account_activation_required]) do
267 :confirmation_pending
273 def account_status(%User{}), do: :active
275 @spec visible_for(User.t(), User.t() | nil) ::
278 | :restricted_unauthenticated
280 | :confirmation_pending
281 def visible_for(user, for_user \\ nil)
283 def visible_for(%User{invisible: true}, _), do: :invisible
285 def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
287 def visible_for(%User{} = user, nil) do
288 if restrict_unauthenticated?(user) do
289 :restrict_unauthenticated
291 visible_account_status(user)
295 def visible_for(%User{} = user, for_user) do
296 if superuser?(for_user) do
299 visible_account_status(user)
303 def visible_for(_, _), do: :invisible
305 defp restrict_unauthenticated?(%User{local: local}) do
306 config_key = if local, do: :local, else: :remote
308 Config.get([:restrict_unauthenticated, :profiles, config_key], false)
311 defp visible_account_status(user) do
312 status = account_status(user)
314 if status in [:active, :password_reset_pending] do
321 @spec superuser?(User.t()) :: boolean()
322 def superuser?(%User{local: true, is_admin: true}), do: true
323 def superuser?(%User{local: true, is_moderator: true}), do: true
324 def superuser?(_), do: false
326 @spec invisible?(User.t()) :: boolean()
327 def invisible?(%User{invisible: true}), do: true
328 def invisible?(_), do: false
330 def avatar_url(user, options \\ []) do
332 %{"url" => [%{"href" => href} | _]} ->
336 unless options[:no_default] do
337 Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png")
342 def banner_url(user, options \\ []) do
344 %{"url" => [%{"href" => href} | _]} -> href
345 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
349 # Should probably be renamed or removed
350 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
352 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
353 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
355 @spec ap_following(User.t()) :: String.t()
356 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
357 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
359 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
360 def restrict_deactivated(query) do
361 from(u in query, where: u.deactivated != ^true)
364 defdelegate following_count(user), to: FollowingRelationship
366 defp truncate_fields_param(params) do
367 if Map.has_key?(params, :fields) do
368 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
374 defp truncate_if_exists(params, key, max_length) do
375 if Map.has_key?(params, key) and is_binary(params[key]) do
376 {value, _chopped} = String.split_at(params[key], max_length)
377 Map.put(params, key, value)
383 defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
385 defp fix_follower_address(%{nickname: nickname} = params),
386 do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
388 defp fix_follower_address(params), do: params
390 def remote_user_changeset(struct \\ %User{local: false}, params) do
391 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
392 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
395 case params[:name] do
396 name when is_binary(name) and byte_size(name) > 0 -> name
397 _ -> params[:nickname]
402 |> Map.put(:name, name)
403 |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
404 |> truncate_if_exists(:name, name_limit)
405 |> truncate_if_exists(:bio, bio_limit)
406 |> truncate_fields_param()
407 |> fix_follower_address()
431 :hide_followers_count,
442 |> validate_required([:name, :ap_id])
443 |> unique_constraint(:nickname)
444 |> validate_format(:nickname, @email_regex)
445 |> validate_length(:bio, max: bio_limit)
446 |> validate_length(:name, max: name_limit)
447 |> validate_fields(true)
450 def update_changeset(struct, params \\ %{}) do
451 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
452 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
472 :hide_followers_count,
475 :allow_following_move,
478 :skip_thread_containment,
481 :pleroma_settings_store,
487 |> unique_constraint(:nickname)
488 |> validate_format(:nickname, local_nickname_regex())
489 |> validate_length(:bio, max: bio_limit)
490 |> validate_length(:name, min: 1, max: name_limit)
491 |> validate_inclusion(:actor_type, ["Person", "Service"])
494 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
495 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
496 |> put_change_if_present(:banner, &put_upload(&1, :banner))
497 |> put_change_if_present(:background, &put_upload(&1, :background))
498 |> put_change_if_present(
499 :pleroma_settings_store,
500 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
502 |> validate_fields(false)
505 defp put_fields(changeset) do
506 if raw_fields = get_change(changeset, :raw_fields) do
509 |> Enum.filter(fn %{"name" => n} -> n != "" end)
513 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
516 |> put_change(:raw_fields, raw_fields)
517 |> put_change(:fields, fields)
523 defp parse_fields(value) do
525 |> Formatter.linkify(mentions_format: :full)
529 defp put_emoji(changeset) do
530 bio = get_change(changeset, :bio)
531 name = get_change(changeset, :name)
534 emoji = Map.merge(Emoji.Formatter.get_emoji_map(bio), Emoji.Formatter.get_emoji_map(name))
535 put_change(changeset, :emoji, emoji)
541 defp put_change_if_present(changeset, map_field, value_function) do
542 with {:ok, value} <- fetch_change(changeset, map_field),
543 {:ok, new_value} <- value_function.(value) do
544 put_change(changeset, map_field, new_value)
550 defp put_upload(value, type) do
551 with %Plug.Upload{} <- value,
552 {:ok, object} <- ActivityPub.upload(value, type: type) do
557 def update_as_admin_changeset(struct, params) do
559 |> update_changeset(params)
560 |> cast(params, [:email])
561 |> delete_change(:also_known_as)
562 |> unique_constraint(:email)
563 |> validate_format(:email, @email_regex)
564 |> validate_inclusion(:actor_type, ["Person", "Service"])
567 @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
568 def update_as_admin(user, params) do
569 params = Map.put(params, "password_confirmation", params["password"])
570 changeset = update_as_admin_changeset(user, params)
572 if params["password"] do
573 reset_password(user, changeset, params)
575 User.update_and_set_cache(changeset)
579 def password_update_changeset(struct, params) do
581 |> cast(params, [:password, :password_confirmation])
582 |> validate_required([:password, :password_confirmation])
583 |> validate_confirmation(:password)
584 |> put_password_hash()
585 |> put_change(:password_reset_pending, false)
588 @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
589 def reset_password(%User{} = user, params) do
590 reset_password(user, user, params)
593 def reset_password(%User{id: user_id} = user, struct, params) do
596 |> Multi.update(:user, password_update_changeset(struct, params))
597 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
598 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
600 case Repo.transaction(multi) do
601 {:ok, %{user: user} = _} -> set_cache(user)
602 {:error, _, changeset, _} -> {:error, changeset}
606 def update_password_reset_pending(user, value) do
609 |> put_change(:password_reset_pending, value)
610 |> update_and_set_cache()
613 def force_password_reset_async(user) do
614 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
617 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
618 def force_password_reset(user), do: update_password_reset_pending(user, true)
620 def register_changeset(struct, params \\ %{}, opts \\ []) do
621 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
622 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
625 if is_nil(opts[:need_confirmation]) do
626 Pleroma.Config.get([:instance, :account_activation_required])
628 opts[:need_confirmation]
632 |> confirmation_changeset(need_confirmation: need_confirmation?)
640 :password_confirmation,
643 |> validate_required([:name, :nickname, :password, :password_confirmation])
644 |> validate_confirmation(:password)
645 |> unique_constraint(:email)
646 |> unique_constraint(:nickname)
647 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
648 |> validate_format(:nickname, local_nickname_regex())
649 |> validate_format(:email, @email_regex)
650 |> validate_length(:bio, max: bio_limit)
651 |> validate_length(:name, min: 1, max: name_limit)
652 |> maybe_validate_required_email(opts[:external])
655 |> unique_constraint(:ap_id)
656 |> put_following_and_follower_address()
659 def maybe_validate_required_email(changeset, true), do: changeset
661 def maybe_validate_required_email(changeset, _) do
662 if Pleroma.Config.get([:instance, :account_activation_required]) do
663 validate_required(changeset, [:email])
669 defp put_ap_id(changeset) do
670 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
671 put_change(changeset, :ap_id, ap_id)
674 defp put_following_and_follower_address(changeset) do
675 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
678 |> put_change(:follower_address, followers)
681 defp autofollow_users(user) do
682 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
685 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
688 follow_all(user, autofollowed_users)
691 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
692 def register(%Ecto.Changeset{} = changeset) do
693 with {:ok, user} <- Repo.insert(changeset) do
694 post_register_action(user)
698 def post_register_action(%User{} = user) do
699 with {:ok, user} <- autofollow_users(user),
700 {:ok, user} <- set_cache(user),
701 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
702 {:ok, _} <- try_send_confirmation_email(user) do
707 def try_send_confirmation_email(%User{} = user) do
708 if user.confirmation_pending &&
709 Pleroma.Config.get([:instance, :account_activation_required]) do
711 |> Pleroma.Emails.UserEmail.account_confirmation_email()
712 |> Pleroma.Emails.Mailer.deliver_async()
720 def try_send_confirmation_email(users) do
721 Enum.each(users, &try_send_confirmation_email/1)
724 def needs_update?(%User{local: true}), do: false
726 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
728 def needs_update?(%User{local: false} = user) do
729 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
732 def needs_update?(_), do: true
734 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
736 # "Locked" (self-locked) users demand explicit authorization of follow requests
737 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
738 follow(follower, followed, :follow_pending)
741 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
742 follow(follower, followed)
745 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
746 if not ap_enabled?(followed) do
747 follow(follower, followed)
753 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
754 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
755 def follow_all(follower, followeds) do
757 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
758 |> Enum.each(&follow(follower, &1, :follow_accept))
763 defdelegate following(user), to: FollowingRelationship
765 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
766 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
769 followed.deactivated ->
770 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
772 deny_follow_blocked and blocks?(followed, follower) ->
773 {:error, "Could not follow user: #{followed.nickname} blocked you."}
776 FollowingRelationship.follow(follower, followed, state)
778 {:ok, _} = update_follower_count(followed)
781 |> update_following_count()
785 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
786 {:error, "Not subscribed!"}
789 @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
790 def unfollow(%User{} = follower, %User{} = followed) do
791 case do_unfollow(follower, followed) do
792 {:ok, follower, followed} ->
793 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
800 @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
801 defp do_unfollow(%User{} = follower, %User{} = followed) do
802 case get_follow_state(follower, followed) do
803 state when state in [:follow_pending, :follow_accept] ->
804 FollowingRelationship.unfollow(follower, followed)
805 {:ok, followed} = update_follower_count(followed)
809 |> update_following_count()
811 {:ok, follower, followed}
814 {:error, "Not subscribed!"}
818 defdelegate following?(follower, followed), to: FollowingRelationship
820 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
821 def get_follow_state(%User{} = follower, %User{} = following) do
822 following_relationship = FollowingRelationship.get(follower, following)
823 get_follow_state(follower, following, following_relationship)
826 def get_follow_state(
829 following_relationship
831 case {following_relationship, following.local} do
833 case Utils.fetch_latest_follow(follower, following) do
834 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
835 FollowingRelationship.state_to_enum(state)
841 {%{state: state}, _} ->
849 def locked?(%User{} = user) do
854 Repo.get_by(User, id: id)
857 def get_by_ap_id(ap_id) do
858 Repo.get_by(User, ap_id: ap_id)
861 def get_all_by_ap_id(ap_ids) do
862 from(u in __MODULE__,
863 where: u.ap_id in ^ap_ids
868 def get_all_by_ids(ids) do
869 from(u in __MODULE__, where: u.id in ^ids)
873 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
874 # of the ap_id and the domain and tries to get that user
875 def get_by_guessed_nickname(ap_id) do
876 domain = URI.parse(ap_id).host
877 name = List.last(String.split(ap_id, "/"))
878 nickname = "#{name}@#{domain}"
880 get_cached_by_nickname(nickname)
883 def set_cache({:ok, user}), do: set_cache(user)
884 def set_cache({:error, err}), do: {:error, err}
886 def set_cache(%User{} = user) do
887 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
888 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
889 Cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
893 def update_and_set_cache(struct, params) do
895 |> update_changeset(params)
896 |> update_and_set_cache()
899 def update_and_set_cache(changeset) do
900 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
905 def get_user_friends_ap_ids(user) do
906 from(u in User.get_friends_query(user), select: u.ap_id)
910 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
911 def get_cached_user_friends_ap_ids(user) do
912 Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
913 get_user_friends_ap_ids(user)
917 def invalidate_cache(user) do
918 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
919 Cachex.del(:user_cache, "nickname:#{user.nickname}")
920 Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
923 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
924 def get_cached_by_ap_id(ap_id) do
925 key = "ap_id:#{ap_id}"
927 with {:ok, nil} <- Cachex.get(:user_cache, key),
928 user when not is_nil(user) <- get_by_ap_id(ap_id),
929 {:ok, true} <- Cachex.put(:user_cache, key, user) do
937 def get_cached_by_id(id) do
941 Cachex.fetch!(:user_cache, key, fn _ ->
945 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
946 {:commit, user.ap_id}
952 get_cached_by_ap_id(ap_id)
955 def get_cached_by_nickname(nickname) do
956 key = "nickname:#{nickname}"
958 Cachex.fetch!(:user_cache, key, fn ->
959 case get_or_fetch_by_nickname(nickname) do
960 {:ok, user} -> {:commit, user}
961 {:error, _error} -> {:ignore, nil}
966 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
967 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
970 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
971 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
973 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
974 get_cached_by_nickname(nickname_or_id)
976 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
977 get_cached_by_nickname(nickname_or_id)
984 @spec get_by_nickname(String.t()) :: User.t() | nil
985 def get_by_nickname(nickname) do
986 Repo.get_by(User, nickname: nickname) ||
987 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
988 Repo.get_by(User, nickname: local_nickname(nickname))
992 def get_by_email(email), do: Repo.get_by(User, email: email)
994 def get_by_nickname_or_email(nickname_or_email) do
995 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
998 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
1000 def get_or_fetch_by_nickname(nickname) do
1001 with %User{} = user <- get_by_nickname(nickname) do
1005 with [_nick, _domain] <- String.split(nickname, "@"),
1006 {:ok, user} <- fetch_by_nickname(nickname) do
1009 _e -> {:error, "not found " <> nickname}
1014 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1015 def get_followers_query(%User{} = user, nil) do
1016 User.Query.build(%{followers: user, deactivated: false})
1019 def get_followers_query(user, page) do
1021 |> get_followers_query(nil)
1022 |> User.Query.paginate(page, 20)
1025 @spec get_followers_query(User.t()) :: Ecto.Query.t()
1026 def get_followers_query(user), do: get_followers_query(user, nil)
1028 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1029 def get_followers(user, page \\ nil) do
1031 |> get_followers_query(page)
1035 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1036 def get_external_followers(user, page \\ nil) do
1038 |> get_followers_query(page)
1039 |> User.Query.build(%{external: true})
1043 def get_followers_ids(user, page \\ nil) do
1045 |> get_followers_query(page)
1046 |> select([u], u.id)
1050 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1051 def get_friends_query(%User{} = user, nil) do
1052 User.Query.build(%{friends: user, deactivated: false})
1055 def get_friends_query(user, page) do
1057 |> get_friends_query(nil)
1058 |> User.Query.paginate(page, 20)
1061 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1062 def get_friends_query(user), do: get_friends_query(user, nil)
1064 def get_friends(user, page \\ nil) do
1066 |> get_friends_query(page)
1070 def get_friends_ap_ids(user) do
1072 |> get_friends_query(nil)
1073 |> select([u], u.ap_id)
1077 def get_friends_ids(user, page \\ nil) do
1079 |> get_friends_query(page)
1080 |> select([u], u.id)
1084 defdelegate get_follow_requests(user), to: FollowingRelationship
1086 def increase_note_count(%User{} = user) do
1088 |> where(id: ^user.id)
1089 |> update([u], inc: [note_count: 1])
1091 |> Repo.update_all([])
1093 {1, [user]} -> set_cache(user)
1098 def decrease_note_count(%User{} = user) do
1100 |> where(id: ^user.id)
1103 note_count: fragment("greatest(0, note_count - 1)")
1107 |> Repo.update_all([])
1109 {1, [user]} -> set_cache(user)
1114 def update_note_count(%User{} = user, note_count \\ nil) do
1119 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1125 |> cast(%{note_count: note_count}, [:note_count])
1126 |> update_and_set_cache()
1129 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1130 def maybe_fetch_follow_information(user) do
1131 with {:ok, user} <- fetch_follow_information(user) do
1135 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1141 def fetch_follow_information(user) do
1142 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1144 |> follow_information_changeset(info)
1145 |> update_and_set_cache()
1149 defp follow_information_changeset(user, params) do
1156 :hide_followers_count,
1161 @spec update_follower_count(User.t()) :: {:ok, User.t()}
1162 def update_follower_count(%User{} = user) do
1163 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
1164 follower_count = FollowingRelationship.follower_count(user)
1167 |> follow_information_changeset(%{follower_count: follower_count})
1168 |> update_and_set_cache
1170 {:ok, maybe_fetch_follow_information(user)}
1174 @spec update_following_count(User.t()) :: {:ok, User.t()}
1175 def update_following_count(%User{local: false} = user) do
1176 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1177 {:ok, maybe_fetch_follow_information(user)}
1183 def update_following_count(%User{local: true} = user) do
1184 following_count = FollowingRelationship.following_count(user)
1187 |> follow_information_changeset(%{following_count: following_count})
1188 |> update_and_set_cache()
1191 def set_unread_conversation_count(%User{local: true} = user) do
1192 unread_query = Participation.unread_conversation_count_for_user(user)
1195 |> join(:inner, [u], p in subquery(unread_query))
1197 set: [unread_conversation_count: p.count]
1199 |> where([u], u.id == ^user.id)
1201 |> Repo.update_all([])
1203 {1, [user]} -> set_cache(user)
1208 def set_unread_conversation_count(user), do: {:ok, user}
1210 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1212 Participation.unread_conversation_count_for_user(user)
1213 |> where([p], p.conversation_id == ^conversation.id)
1216 |> join(:inner, [u], p in subquery(unread_query))
1218 inc: [unread_conversation_count: 1]
1220 |> where([u], u.id == ^user.id)
1221 |> where([u, p], p.count == 0)
1223 |> Repo.update_all([])
1225 {1, [user]} -> set_cache(user)
1230 def increment_unread_conversation_count(_, user), do: {:ok, user}
1232 @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
1233 def get_users_from_set(ap_ids, opts \\ []) do
1234 local_only = Keyword.get(opts, :local_only, true)
1235 criteria = %{ap_id: ap_ids, deactivated: false}
1236 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1238 User.Query.build(criteria)
1242 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1243 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1246 query = User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1252 @spec mute(User.t(), User.t(), boolean()) ::
1253 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1254 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1255 add_to_mutes(muter, mutee, notifications?)
1258 def unmute(%User{} = muter, %User{} = mutee) do
1259 remove_from_mutes(muter, mutee)
1262 def subscribe(%User{} = subscriber, %User{} = target) do
1263 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1265 if blocks?(target, subscriber) and deny_follow_blocked do
1266 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1268 # Note: the relationship is inverse: subscriber acts as relationship target
1269 UserRelationship.create_inverse_subscription(target, subscriber)
1273 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1274 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1275 subscribe(subscriber, subscribee)
1279 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1280 # Note: the relationship is inverse: subscriber acts as relationship target
1281 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1284 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1285 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1286 unsubscribe(unsubscriber, user)
1290 def block(%User{} = blocker, %User{} = blocked) do
1291 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1293 if following?(blocker, blocked) do
1294 {:ok, blocker, _} = unfollow(blocker, blocked)
1300 # clear any requested follows as well
1302 case CommonAPI.reject_follow_request(blocked, blocker) do
1303 {:ok, %User{} = updated_blocked} -> updated_blocked
1307 unsubscribe(blocked, blocker)
1309 unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
1310 if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
1312 {:ok, blocker} = update_follower_count(blocker)
1313 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1314 add_to_block(blocker, blocked)
1317 # helper to handle the block given only an actor's AP id
1318 def block(%User{} = blocker, %{ap_id: ap_id}) do
1319 block(blocker, get_cached_by_ap_id(ap_id))
1322 def unblock(%User{} = blocker, %User{} = blocked) do
1323 remove_from_block(blocker, blocked)
1326 # helper to handle the block given only an actor's AP id
1327 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1328 unblock(blocker, get_cached_by_ap_id(ap_id))
1331 def mutes?(nil, _), do: false
1332 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1334 def mutes_user?(%User{} = user, %User{} = target) do
1335 UserRelationship.mute_exists?(user, target)
1338 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1339 def muted_notifications?(nil, _), do: false
1341 def muted_notifications?(%User{} = user, %User{} = target),
1342 do: UserRelationship.notification_mute_exists?(user, target)
1344 def blocks?(nil, _), do: false
1346 def blocks?(%User{} = user, %User{} = target) do
1347 blocks_user?(user, target) ||
1348 (blocks_domain?(user, target) and not User.following?(user, target))
1351 def blocks_user?(%User{} = user, %User{} = target) do
1352 UserRelationship.block_exists?(user, target)
1355 def blocks_user?(_, _), do: false
1357 def blocks_domain?(%User{} = user, %User{} = target) do
1358 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1359 %{host: host} = URI.parse(target.ap_id)
1360 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1363 def blocks_domain?(_, _), do: false
1365 def subscribed_to?(%User{} = user, %User{} = target) do
1366 # Note: the relationship is inverse: subscriber acts as relationship target
1367 UserRelationship.inverse_subscription_exists?(target, user)
1370 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1371 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1372 subscribed_to?(user, target)
1377 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1378 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1380 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1381 def outgoing_relationships_ap_ids(_user, []), do: %{}
1383 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1385 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1386 when is_list(relationship_types) do
1389 |> assoc(:outgoing_relationships)
1390 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1391 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1392 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1393 |> group_by([user_rel, u], user_rel.relationship_type)
1395 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1400 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1404 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1406 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1408 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1410 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1411 when is_list(relationship_types) do
1413 |> assoc(:incoming_relationships)
1414 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1415 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1416 |> maybe_filter_on_ap_id(ap_ids)
1417 |> select([user_rel, u], u.ap_id)
1422 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1423 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1426 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1428 def deactivate_async(user, status \\ true) do
1429 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1432 def deactivate(user, status \\ true)
1434 def deactivate(users, status) when is_list(users) do
1435 Repo.transaction(fn ->
1436 for user <- users, do: deactivate(user, status)
1440 def deactivate(%User{} = user, status) do
1441 with {:ok, user} <- set_activation_status(user, status) do
1444 |> Enum.filter(& &1.local)
1445 |> Enum.each(&set_cache(update_following_count(&1)))
1447 # Only update local user counts, remote will be update during the next pull.
1450 |> Enum.filter(& &1.local)
1451 |> Enum.each(&do_unfollow(user, &1))
1457 def update_notification_settings(%User{} = user, settings) do
1459 |> cast(%{notification_settings: settings}, [])
1460 |> cast_embed(:notification_settings)
1461 |> validate_required([:notification_settings])
1462 |> update_and_set_cache()
1465 def delete(users) when is_list(users) do
1466 for user <- users, do: delete(user)
1469 def delete(%User{} = user) do
1470 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1473 defp delete_and_invalidate_cache(%User{} = user) do
1474 invalidate_cache(user)
1478 defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user)
1480 defp delete_or_deactivate(%User{local: true} = user) do
1481 status = account_status(user)
1483 if status == :confirmation_pending do
1484 delete_and_invalidate_cache(user)
1487 |> change(%{deactivated: true, email: nil})
1488 |> update_and_set_cache()
1492 def perform(:force_password_reset, user), do: force_password_reset(user)
1494 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1495 def perform(:delete, %User{} = user) do
1496 # Remove all relationships
1499 |> Enum.each(fn follower ->
1500 ActivityPub.unfollow(follower, user)
1501 unfollow(follower, user)
1506 |> Enum.each(fn followed ->
1507 ActivityPub.unfollow(user, followed)
1508 unfollow(user, followed)
1511 delete_user_activities(user)
1512 delete_notifications_from_user_activities(user)
1514 delete_outgoing_pending_follow_requests(user)
1516 delete_or_deactivate(user)
1519 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1521 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1522 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1523 when is_list(blocked_identifiers) do
1525 blocked_identifiers,
1526 fn blocked_identifier ->
1527 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1528 {:ok, _block} <- CommonAPI.block(blocker, blocked) do
1532 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1539 def perform(:follow_import, %User{} = follower, followed_identifiers)
1540 when is_list(followed_identifiers) do
1542 followed_identifiers,
1543 fn followed_identifier ->
1544 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1545 {:ok, follower} <- maybe_direct_follow(follower, followed),
1546 {:ok, _} <- ActivityPub.follow(follower, followed) do
1550 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1557 @spec external_users_query() :: Ecto.Query.t()
1558 def external_users_query do
1566 @spec external_users(keyword()) :: [User.t()]
1567 def external_users(opts \\ []) do
1569 external_users_query()
1570 |> select([u], struct(u, [:id, :ap_id]))
1574 do: where(query, [u], u.id > ^opts[:max_id]),
1579 do: limit(query, ^opts[:limit]),
1585 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1586 BackgroundWorker.enqueue("blocks_import", %{
1587 "blocker_id" => blocker.id,
1588 "blocked_identifiers" => blocked_identifiers
1592 def follow_import(%User{} = follower, followed_identifiers)
1593 when is_list(followed_identifiers) do
1594 BackgroundWorker.enqueue("follow_import", %{
1595 "follower_id" => follower.id,
1596 "followed_identifiers" => followed_identifiers
1600 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1602 |> join(:inner, [n], activity in assoc(n, :activity))
1603 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1604 |> Repo.delete_all()
1607 def delete_user_activities(%User{ap_id: ap_id} = user) do
1609 |> Activity.Queries.by_actor()
1610 |> RepoStreamer.chunk_stream(50)
1611 |> Stream.each(fn activities ->
1612 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1617 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1618 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1619 {:ok, delete_data, _} <- Builder.delete(user, object) do
1620 Pipeline.common_pipeline(delete_data, local: user.local)
1622 {:find_object, nil} ->
1623 # We have the create activity, but not the object, it was probably pruned.
1624 # Insert a tombstone and try again
1625 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1626 {:ok, _tombstone} <- Object.create(tombstone_data) do
1627 delete_activity(activity, user)
1631 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1632 Logger.error("Error: #{inspect(e)}")
1636 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1637 when type in ["Like", "Announce"] do
1638 {:ok, undo, _} = Builder.undo(user, activity)
1639 Pipeline.common_pipeline(undo, local: user.local)
1642 defp delete_activity(_activity, _user), do: "Doing nothing"
1644 defp delete_outgoing_pending_follow_requests(user) do
1646 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1647 |> Repo.delete_all()
1650 def html_filter_policy(%User{no_rich_text: true}) do
1651 Pleroma.HTML.Scrubber.TwitterText
1654 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1656 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1658 def get_or_fetch_by_ap_id(ap_id) do
1659 cached_user = get_cached_by_ap_id(ap_id)
1661 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
1663 case {cached_user, maybe_fetched_user} do
1664 {_, {:ok, %User{} = user}} ->
1667 {%User{} = user, _} ->
1671 {:error, :not_found}
1676 Creates an internal service actor by URI if missing.
1677 Optionally takes nickname for addressing.
1679 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1680 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1682 case get_cached_by_ap_id(uri) do
1684 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1685 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1689 %User{invisible: false} = user ->
1699 @spec set_invisible(User.t()) :: {:ok, User.t()}
1700 defp set_invisible(user) do
1702 |> change(%{invisible: true})
1703 |> update_and_set_cache()
1706 @spec create_service_actor(String.t(), String.t()) ::
1707 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1708 defp create_service_actor(uri, nickname) do
1714 follower_address: uri <> "/followers"
1717 |> unique_constraint(:nickname)
1722 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1725 |> :public_key.pem_decode()
1727 |> :public_key.pem_entry_decode()
1732 def public_key(_), do: {:error, "key not found"}
1734 def get_public_key_for_ap_id(ap_id) do
1735 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1736 {:ok, public_key} <- public_key(user) do
1743 def ap_enabled?(%User{local: true}), do: true
1744 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1745 def ap_enabled?(_), do: false
1747 @doc "Gets or fetch a user by uri or nickname."
1748 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1749 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1750 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1752 # wait a period of time and return newest version of the User structs
1753 # this is because we have synchronous follow APIs and need to simulate them
1754 # with an async handshake
1755 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1756 with %User{} = a <- get_cached_by_id(a.id),
1757 %User{} = b <- get_cached_by_id(b.id) do
1764 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1765 with :ok <- :timer.sleep(timeout),
1766 %User{} = a <- get_cached_by_id(a.id),
1767 %User{} = b <- get_cached_by_id(b.id) do
1774 def parse_bio(bio) when is_binary(bio) and bio != "" do
1776 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1780 def parse_bio(_), do: ""
1782 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1783 # TODO: get profile URLs other than user.ap_id
1784 profile_urls = [user.ap_id]
1787 |> CommonUtils.format_input("text/plain",
1788 mentions_format: :full,
1789 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1794 def parse_bio(_, _), do: ""
1796 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1797 Repo.transaction(fn ->
1798 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1802 def tag(nickname, tags) when is_binary(nickname),
1803 do: tag(get_by_nickname(nickname), tags)
1805 def tag(%User{} = user, tags),
1806 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1808 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1809 Repo.transaction(fn ->
1810 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1814 def untag(nickname, tags) when is_binary(nickname),
1815 do: untag(get_by_nickname(nickname), tags)
1817 def untag(%User{} = user, tags),
1818 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1820 defp update_tags(%User{} = user, new_tags) do
1821 {:ok, updated_user} =
1823 |> change(%{tags: new_tags})
1824 |> update_and_set_cache()
1829 defp normalize_tags(tags) do
1832 |> Enum.map(&String.downcase/1)
1835 defp local_nickname_regex do
1836 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1837 @extended_local_nickname_regex
1839 @strict_local_nickname_regex
1843 def local_nickname(nickname_or_mention) do
1846 |> String.split("@")
1850 def full_nickname(nickname_or_mention),
1851 do: String.trim_leading(nickname_or_mention, "@")
1853 def error_user(ap_id) do
1857 nickname: "erroruser@example.com",
1858 inserted_at: NaiveDateTime.utc_now()
1862 @spec all_superusers() :: [User.t()]
1863 def all_superusers do
1864 User.Query.build(%{super_users: true, local: true, deactivated: false})
1868 def muting_reblogs?(%User{} = user, %User{} = target) do
1869 UserRelationship.reblog_mute_exists?(user, target)
1872 def showing_reblogs?(%User{} = user, %User{} = target) do
1873 not muting_reblogs?(user, target)
1877 The function returns a query to get users with no activity for given interval of days.
1878 Inactive users are those who didn't read any notification, or had any activity where
1879 the user is the activity's actor, during `inactivity_threshold` days.
1880 Deactivated users will not appear in this list.
1884 iex> Pleroma.User.list_inactive_users()
1887 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1888 def list_inactive_users_query(inactivity_threshold \\ 7) do
1889 negative_inactivity_threshold = -inactivity_threshold
1890 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1891 # Subqueries are not supported in `where` clauses, join gets too complicated.
1892 has_read_notifications =
1893 from(n in Pleroma.Notification,
1894 where: n.seen == true,
1896 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1899 |> Pleroma.Repo.all()
1901 from(u in Pleroma.User,
1902 left_join: a in Pleroma.Activity,
1903 on: u.ap_id == a.actor,
1904 where: not is_nil(u.nickname),
1905 where: u.deactivated != ^true,
1906 where: u.id not in ^has_read_notifications,
1909 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1910 is_nil(max(a.inserted_at))
1915 Enable or disable email notifications for user
1919 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1920 Pleroma.User{email_notifications: %{"digest" => true}}
1922 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1923 Pleroma.User{email_notifications: %{"digest" => false}}
1925 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1926 {:ok, t()} | {:error, Ecto.Changeset.t()}
1927 def switch_email_notifications(user, type, status) do
1928 User.update_email_notifications(user, %{type => status})
1932 Set `last_digest_emailed_at` value for the user to current time
1934 @spec touch_last_digest_emailed_at(t()) :: t()
1935 def touch_last_digest_emailed_at(user) do
1936 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1938 {:ok, updated_user} =
1940 |> change(%{last_digest_emailed_at: now})
1941 |> update_and_set_cache()
1946 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1947 def toggle_confirmation(%User{} = user) do
1949 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1950 |> update_and_set_cache()
1953 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1954 def toggle_confirmation(users) do
1955 Enum.map(users, &toggle_confirmation/1)
1958 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1962 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1963 # use instance-default
1964 config = Pleroma.Config.get([:assets, :mascots])
1965 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1966 mascot = Keyword.get(config, default_mascot)
1969 "id" => "default-mascot",
1970 "url" => mascot[:url],
1971 "preview_url" => mascot[:url],
1973 "mime_type" => mascot[:mime_type]
1978 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1980 def ensure_keys_present(%User{} = user) do
1981 with {:ok, pem} <- Keys.generate_rsa_pem() do
1983 |> cast(%{keys: pem}, [:keys])
1984 |> validate_required([:keys])
1985 |> update_and_set_cache()
1989 def get_ap_ids_by_nicknames(nicknames) do
1991 where: u.nickname in ^nicknames,
1997 defdelegate search(query, opts \\ []), to: User.Search
1999 defp put_password_hash(
2000 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2002 change(changeset, password_hash: Pbkdf2.hash_pwd_salt(password))
2005 defp put_password_hash(changeset), do: changeset
2007 def is_internal_user?(%User{nickname: nil}), do: true
2008 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2009 def is_internal_user?(_), do: false
2011 # A hack because user delete activities have a fake id for whatever reason
2012 # TODO: Get rid of this
2013 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2015 def get_delivered_users_by_object_id(object_id) do
2017 inner_join: delivery in assoc(u, :deliveries),
2018 where: delivery.object_id == ^object_id
2023 def change_email(user, email) do
2025 |> cast(%{email: email}, [:email])
2026 |> validate_required([:email])
2027 |> unique_constraint(:email)
2028 |> validate_format(:email, @email_regex)
2029 |> update_and_set_cache()
2032 # Internal function; public one is `deactivate/2`
2033 defp set_activation_status(user, deactivated) do
2035 |> cast(%{deactivated: deactivated}, [:deactivated])
2036 |> update_and_set_cache()
2039 def update_banner(user, banner) do
2041 |> cast(%{banner: banner}, [:banner])
2042 |> update_and_set_cache()
2045 def update_background(user, background) do
2047 |> cast(%{background: background}, [:background])
2048 |> update_and_set_cache()
2051 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
2054 moderator: is_moderator
2058 def validate_fields(changeset, remote? \\ false) do
2059 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2060 limit = Pleroma.Config.get([:instance, limit_name], 0)
2063 |> validate_length(:fields, max: limit)
2064 |> validate_change(:fields, fn :fields, fields ->
2065 if Enum.all?(fields, &valid_field?/1) do
2073 defp valid_field?(%{"name" => name, "value" => value}) do
2074 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
2075 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
2077 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2078 String.length(value) <= value_limit
2081 defp valid_field?(_), do: false
2083 defp truncate_field(%{"name" => name, "value" => value}) do
2085 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
2088 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
2090 %{"name" => name, "value" => value}
2093 def admin_api_update(user, params) do
2100 |> update_and_set_cache()
2103 @doc "Signs user out of all applications"
2104 def global_sign_out(user) do
2105 OAuth.Authorization.delete_user_authorizations(user)
2106 OAuth.Token.delete_user_tokens(user)
2109 def mascot_update(user, url) do
2111 |> cast(%{mascot: url}, [:mascot])
2112 |> validate_required([:mascot])
2113 |> update_and_set_cache()
2116 def mastodon_settings_update(user, settings) do
2118 |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
2119 |> validate_required([:mastofe_settings])
2120 |> update_and_set_cache()
2123 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2124 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2126 if need_confirmation? do
2128 confirmation_pending: true,
2129 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2133 confirmation_pending: false,
2134 confirmation_token: nil
2138 cast(user, params, [:confirmation_pending, :confirmation_token])
2141 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2142 if id not in user.pinned_activities do
2143 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
2144 params = %{pinned_activities: user.pinned_activities ++ [id]}
2147 |> cast(params, [:pinned_activities])
2148 |> validate_length(:pinned_activities,
2149 max: max_pinned_statuses,
2150 message: "You have already pinned the maximum number of statuses"
2155 |> update_and_set_cache()
2158 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2159 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2162 |> cast(params, [:pinned_activities])
2163 |> update_and_set_cache()
2166 def update_email_notifications(user, settings) do
2167 email_notifications =
2168 user.email_notifications
2169 |> Map.merge(settings)
2170 |> Map.take(["digest"])
2172 params = %{email_notifications: email_notifications}
2173 fields = [:email_notifications]
2176 |> cast(params, fields)
2177 |> validate_required(fields)
2178 |> update_and_set_cache()
2181 defp set_domain_blocks(user, domain_blocks) do
2182 params = %{domain_blocks: domain_blocks}
2185 |> cast(params, [:domain_blocks])
2186 |> validate_required([:domain_blocks])
2187 |> update_and_set_cache()
2190 def block_domain(user, domain_blocked) do
2191 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2194 def unblock_domain(user, domain_blocked) do
2195 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2198 @spec add_to_block(User.t(), User.t()) ::
2199 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2200 defp add_to_block(%User{} = user, %User{} = blocked) do
2201 UserRelationship.create_block(user, blocked)
2204 @spec add_to_block(User.t(), User.t()) ::
2205 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2206 defp remove_from_block(%User{} = user, %User{} = blocked) do
2207 UserRelationship.delete_block(user, blocked)
2210 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2211 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2212 {:ok, user_notification_mute} <-
2213 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2215 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2219 defp remove_from_mutes(user, %User{} = muted_user) do
2220 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2221 {:ok, user_notification_mute} <-
2222 UserRelationship.delete_notification_mute(user, muted_user) do
2223 {:ok, [user_mute, user_notification_mute]}
2227 def set_invisible(user, invisible) do
2228 params = %{invisible: invisible}
2231 |> cast(params, [:invisible])
2232 |> validate_required([:invisible])
2233 |> update_and_set_cache()
2236 def sanitize_html(%User{} = user) do
2237 sanitize_html(user, nil)
2240 # User data that mastodon isn't filtering (treated as plaintext):
2243 def sanitize_html(%User{} = user, filter) do
2245 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2248 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2253 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2254 |> Map.put(:fields, fields)