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
18 alias Pleroma.FollowingRelationship
19 alias Pleroma.Formatter
23 alias Pleroma.Notification
25 alias Pleroma.Registration
27 alias Pleroma.RepoStreamer
29 alias Pleroma.UserRelationship
31 alias Pleroma.Web.ActivityPub.ActivityPub
32 alias Pleroma.Web.ActivityPub.Builder
33 alias Pleroma.Web.ActivityPub.ObjectValidators.Types
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)
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(:settings, :map, default: nil)
119 field(:uri, Types.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 case Config.get([:instance, :account_activation_required]) do
267 true -> :confirmation_pending
272 def account_status(%User{}), do: :active
274 @spec visible_for?(User.t(), User.t() | nil) :: boolean()
275 def visible_for?(user, for_user \\ nil)
277 def visible_for?(%User{invisible: true}, _), do: false
279 def visible_for?(%User{id: user_id}, %User{id: user_id}), do: true
281 def visible_for?(%User{local: local} = user, nil) do
287 if Config.get([:restrict_unauthenticated, :profiles, cfg_key]),
289 else: account_status(user) == :active
292 def visible_for?(%User{} = user, for_user) do
293 account_status(user) == :active || superuser?(for_user)
296 def visible_for?(_, _), do: false
298 @spec superuser?(User.t()) :: boolean()
299 def superuser?(%User{local: true, is_admin: true}), do: true
300 def superuser?(%User{local: true, is_moderator: true}), do: true
301 def superuser?(_), do: false
303 @spec invisible?(User.t()) :: boolean()
304 def invisible?(%User{invisible: true}), do: true
305 def invisible?(_), do: false
307 def avatar_url(user, options \\ []) do
309 %{"url" => [%{"href" => href} | _]} ->
313 unless options[:no_default] do
314 Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png")
319 def banner_url(user, options \\ []) do
321 %{"url" => [%{"href" => href} | _]} -> href
322 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
326 # Should probably be renamed or removed
327 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
329 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
330 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
332 @spec ap_following(User.t()) :: String.t()
333 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
334 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
336 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
337 def restrict_deactivated(query) do
338 from(u in query, where: u.deactivated != ^true)
341 defdelegate following_count(user), to: FollowingRelationship
343 defp truncate_fields_param(params) do
344 if Map.has_key?(params, :fields) do
345 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
351 defp truncate_if_exists(params, key, max_length) do
352 if Map.has_key?(params, key) and is_binary(params[key]) do
353 {value, _chopped} = String.split_at(params[key], max_length)
354 Map.put(params, key, value)
360 defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
362 defp fix_follower_address(%{nickname: nickname} = params),
363 do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
365 defp fix_follower_address(params), do: params
367 def remote_user_changeset(struct \\ %User{local: false}, params) do
368 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
369 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
372 case params[:name] do
373 name when is_binary(name) and byte_size(name) > 0 -> name
374 _ -> params[:nickname]
379 |> Map.put(:name, name)
380 |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
381 |> truncate_if_exists(:name, name_limit)
382 |> truncate_if_exists(:bio, bio_limit)
383 |> truncate_fields_param()
384 |> fix_follower_address()
408 :hide_followers_count,
419 |> validate_required([:name, :ap_id])
420 |> unique_constraint(:nickname)
421 |> validate_format(:nickname, @email_regex)
422 |> validate_length(:bio, max: bio_limit)
423 |> validate_length(:name, max: name_limit)
424 |> validate_fields(true)
427 def update_changeset(struct, params \\ %{}) do
428 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
429 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
449 :hide_followers_count,
452 :allow_following_move,
455 :skip_thread_containment,
458 :pleroma_settings_store,
464 |> unique_constraint(:nickname)
465 |> validate_format(:nickname, local_nickname_regex())
466 |> validate_length(:bio, max: bio_limit)
467 |> validate_length(:name, min: 1, max: name_limit)
470 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
471 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
472 |> put_change_if_present(:banner, &put_upload(&1, :banner))
473 |> put_change_if_present(:background, &put_upload(&1, :background))
474 |> put_change_if_present(
475 :pleroma_settings_store,
476 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
478 |> validate_fields(false)
481 defp put_fields(changeset) do
482 if raw_fields = get_change(changeset, :raw_fields) do
485 |> Enum.filter(fn %{"name" => n} -> n != "" end)
489 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
492 |> put_change(:raw_fields, raw_fields)
493 |> put_change(:fields, fields)
499 defp parse_fields(value) do
501 |> Formatter.linkify(mentions_format: :full)
505 defp put_emoji(changeset) do
506 bio = get_change(changeset, :bio)
507 name = get_change(changeset, :name)
510 emoji = Map.merge(Emoji.Formatter.get_emoji_map(bio), Emoji.Formatter.get_emoji_map(name))
511 put_change(changeset, :emoji, emoji)
517 defp put_change_if_present(changeset, map_field, value_function) do
518 if value = get_change(changeset, map_field) do
519 with {:ok, new_value} <- value_function.(value) do
520 put_change(changeset, map_field, new_value)
529 defp put_upload(value, type) do
530 with %Plug.Upload{} <- value,
531 {:ok, object} <- ActivityPub.upload(value, type: type) do
536 def update_as_admin_changeset(struct, params) do
538 |> update_changeset(params)
539 |> cast(params, [:email])
540 |> delete_change(:also_known_as)
541 |> unique_constraint(:email)
542 |> validate_format(:email, @email_regex)
543 |> validate_inclusion(:actor_type, ["Person", "Service"])
546 @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
547 def update_as_admin(user, params) do
548 params = Map.put(params, "password_confirmation", params["password"])
549 changeset = update_as_admin_changeset(user, params)
551 if params["password"] do
552 reset_password(user, changeset, params)
554 User.update_and_set_cache(changeset)
558 def password_update_changeset(struct, params) do
560 |> cast(params, [:password, :password_confirmation])
561 |> validate_required([:password, :password_confirmation])
562 |> validate_confirmation(:password)
563 |> put_password_hash()
564 |> put_change(:password_reset_pending, false)
567 @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
568 def reset_password(%User{} = user, params) do
569 reset_password(user, user, params)
572 def reset_password(%User{id: user_id} = user, struct, params) do
575 |> Multi.update(:user, password_update_changeset(struct, params))
576 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
577 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
579 case Repo.transaction(multi) do
580 {:ok, %{user: user} = _} -> set_cache(user)
581 {:error, _, changeset, _} -> {:error, changeset}
585 def update_password_reset_pending(user, value) do
588 |> put_change(:password_reset_pending, value)
589 |> update_and_set_cache()
592 def force_password_reset_async(user) do
593 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
596 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
597 def force_password_reset(user), do: update_password_reset_pending(user, true)
599 def register_changeset(struct, params \\ %{}, opts \\ []) do
600 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
601 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
604 if is_nil(opts[:need_confirmation]) do
605 Pleroma.Config.get([:instance, :account_activation_required])
607 opts[:need_confirmation]
611 |> confirmation_changeset(need_confirmation: need_confirmation?)
619 :password_confirmation,
622 |> validate_required([:name, :nickname, :password, :password_confirmation])
623 |> validate_confirmation(:password)
624 |> unique_constraint(:email)
625 |> unique_constraint(:nickname)
626 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
627 |> validate_format(:nickname, local_nickname_regex())
628 |> validate_format(:email, @email_regex)
629 |> validate_length(:bio, max: bio_limit)
630 |> validate_length(:name, min: 1, max: name_limit)
631 |> maybe_validate_required_email(opts[:external])
634 |> unique_constraint(:ap_id)
635 |> put_following_and_follower_address()
638 def maybe_validate_required_email(changeset, true), do: changeset
640 def maybe_validate_required_email(changeset, _) do
641 if Pleroma.Config.get([:instance, :account_activation_required]) do
642 validate_required(changeset, [:email])
648 defp put_ap_id(changeset) do
649 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
650 put_change(changeset, :ap_id, ap_id)
653 defp put_following_and_follower_address(changeset) do
654 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
657 |> put_change(:follower_address, followers)
660 defp autofollow_users(user) do
661 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
664 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
667 follow_all(user, autofollowed_users)
670 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
671 def register(%Ecto.Changeset{} = changeset) do
672 with {:ok, user} <- Repo.insert(changeset) do
673 post_register_action(user)
677 def post_register_action(%User{} = user) do
678 with {:ok, user} <- autofollow_users(user),
679 {:ok, user} <- set_cache(user),
680 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
681 {:ok, _} <- try_send_confirmation_email(user) do
686 def try_send_confirmation_email(%User{} = user) do
687 if user.confirmation_pending &&
688 Pleroma.Config.get([:instance, :account_activation_required]) do
690 |> Pleroma.Emails.UserEmail.account_confirmation_email()
691 |> Pleroma.Emails.Mailer.deliver_async()
699 def try_send_confirmation_email(users) do
700 Enum.each(users, &try_send_confirmation_email/1)
703 def needs_update?(%User{local: true}), do: false
705 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
707 def needs_update?(%User{local: false} = user) do
708 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
711 def needs_update?(_), do: true
713 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
715 # "Locked" (self-locked) users demand explicit authorization of follow requests
716 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
717 follow(follower, followed, :follow_pending)
720 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
721 follow(follower, followed)
724 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
725 if not ap_enabled?(followed) do
726 follow(follower, followed)
732 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
733 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
734 def follow_all(follower, followeds) do
736 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
737 |> Enum.each(&follow(follower, &1, :follow_accept))
742 defdelegate following(user), to: FollowingRelationship
744 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
745 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
748 followed.deactivated ->
749 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
751 deny_follow_blocked and blocks?(followed, follower) ->
752 {:error, "Could not follow user: #{followed.nickname} blocked you."}
755 FollowingRelationship.follow(follower, followed, state)
757 {:ok, _} = update_follower_count(followed)
760 |> update_following_count()
765 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
766 {:error, "Not subscribed!"}
769 @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
770 def unfollow(%User{} = follower, %User{} = followed) do
771 case do_unfollow(follower, followed) do
772 {:ok, follower, followed} ->
773 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
780 @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
781 defp do_unfollow(%User{} = follower, %User{} = followed) do
782 case get_follow_state(follower, followed) do
783 state when state in [:follow_pending, :follow_accept] ->
784 FollowingRelationship.unfollow(follower, followed)
785 {:ok, followed} = update_follower_count(followed)
789 |> update_following_count()
792 {:ok, follower, followed}
795 {:error, "Not subscribed!"}
799 defdelegate following?(follower, followed), to: FollowingRelationship
801 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
802 def get_follow_state(%User{} = follower, %User{} = following) do
803 following_relationship = FollowingRelationship.get(follower, following)
804 get_follow_state(follower, following, following_relationship)
807 def get_follow_state(
810 following_relationship
812 case {following_relationship, following.local} do
814 case Utils.fetch_latest_follow(follower, following) do
815 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
816 FollowingRelationship.state_to_enum(state)
822 {%{state: state}, _} ->
830 def locked?(%User{} = user) do
835 Repo.get_by(User, id: id)
838 def get_by_ap_id(ap_id) do
839 Repo.get_by(User, ap_id: ap_id)
842 def get_all_by_ap_id(ap_ids) do
843 from(u in __MODULE__,
844 where: u.ap_id in ^ap_ids
849 def get_all_by_ids(ids) do
850 from(u in __MODULE__, where: u.id in ^ids)
854 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
855 # of the ap_id and the domain and tries to get that user
856 def get_by_guessed_nickname(ap_id) do
857 domain = URI.parse(ap_id).host
858 name = List.last(String.split(ap_id, "/"))
859 nickname = "#{name}@#{domain}"
861 get_cached_by_nickname(nickname)
864 def set_cache({:ok, user}), do: set_cache(user)
865 def set_cache({:error, err}), do: {:error, err}
867 def set_cache(%User{} = user) do
868 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
869 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
870 Cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
874 def update_and_set_cache(struct, params) do
876 |> update_changeset(params)
877 |> update_and_set_cache()
880 def update_and_set_cache(changeset) do
881 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
886 def get_user_friends_ap_ids(user) do
887 from(u in User.get_friends_query(user), select: u.ap_id)
891 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
892 def get_cached_user_friends_ap_ids(user) do
893 Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
894 get_user_friends_ap_ids(user)
898 def invalidate_cache(user) do
899 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
900 Cachex.del(:user_cache, "nickname:#{user.nickname}")
901 Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
904 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
905 def get_cached_by_ap_id(ap_id) do
906 key = "ap_id:#{ap_id}"
908 with {:ok, nil} <- Cachex.get(:user_cache, key),
909 user when not is_nil(user) <- get_by_ap_id(ap_id),
910 {:ok, true} <- Cachex.put(:user_cache, key, user) do
918 def get_cached_by_id(id) do
922 Cachex.fetch!(:user_cache, key, fn _ ->
926 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
927 {:commit, user.ap_id}
933 get_cached_by_ap_id(ap_id)
936 def get_cached_by_nickname(nickname) do
937 key = "nickname:#{nickname}"
939 Cachex.fetch!(:user_cache, key, fn ->
940 case get_or_fetch_by_nickname(nickname) do
941 {:ok, user} -> {:commit, user}
942 {:error, _error} -> {:ignore, nil}
947 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
948 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
951 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
952 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
954 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
955 get_cached_by_nickname(nickname_or_id)
957 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
958 get_cached_by_nickname(nickname_or_id)
965 @spec get_by_nickname(String.t()) :: User.t() | nil
966 def get_by_nickname(nickname) do
967 Repo.get_by(User, nickname: nickname) ||
968 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
969 Repo.get_by(User, nickname: local_nickname(nickname))
973 def get_by_email(email), do: Repo.get_by(User, email: email)
975 def get_by_nickname_or_email(nickname_or_email) do
976 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
979 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
981 def get_or_fetch_by_nickname(nickname) do
982 with %User{} = user <- get_by_nickname(nickname) do
986 with [_nick, _domain] <- String.split(nickname, "@"),
987 {:ok, user} <- fetch_by_nickname(nickname) do
990 _e -> {:error, "not found " <> nickname}
995 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
996 def get_followers_query(%User{} = user, nil) do
997 User.Query.build(%{followers: user, deactivated: false})
1000 def get_followers_query(user, page) do
1002 |> get_followers_query(nil)
1003 |> User.Query.paginate(page, 20)
1006 @spec get_followers_query(User.t()) :: Ecto.Query.t()
1007 def get_followers_query(user), do: get_followers_query(user, nil)
1009 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1010 def get_followers(user, page \\ nil) do
1012 |> get_followers_query(page)
1016 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1017 def get_external_followers(user, page \\ nil) do
1019 |> get_followers_query(page)
1020 |> User.Query.build(%{external: true})
1024 def get_followers_ids(user, page \\ nil) do
1026 |> get_followers_query(page)
1027 |> select([u], u.id)
1031 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1032 def get_friends_query(%User{} = user, nil) do
1033 User.Query.build(%{friends: user, deactivated: false})
1036 def get_friends_query(user, page) do
1038 |> get_friends_query(nil)
1039 |> User.Query.paginate(page, 20)
1042 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1043 def get_friends_query(user), do: get_friends_query(user, nil)
1045 def get_friends(user, page \\ nil) do
1047 |> get_friends_query(page)
1051 def get_friends_ap_ids(user) do
1053 |> get_friends_query(nil)
1054 |> select([u], u.ap_id)
1058 def get_friends_ids(user, page \\ nil) do
1060 |> get_friends_query(page)
1061 |> select([u], u.id)
1065 defdelegate get_follow_requests(user), to: FollowingRelationship
1067 def increase_note_count(%User{} = user) do
1069 |> where(id: ^user.id)
1070 |> update([u], inc: [note_count: 1])
1072 |> Repo.update_all([])
1074 {1, [user]} -> set_cache(user)
1079 def decrease_note_count(%User{} = user) do
1081 |> where(id: ^user.id)
1084 note_count: fragment("greatest(0, note_count - 1)")
1088 |> Repo.update_all([])
1090 {1, [user]} -> set_cache(user)
1095 def update_note_count(%User{} = user, note_count \\ nil) do
1100 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1106 |> cast(%{note_count: note_count}, [:note_count])
1107 |> update_and_set_cache()
1110 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1111 def maybe_fetch_follow_information(user) do
1112 with {:ok, user} <- fetch_follow_information(user) do
1116 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1122 def fetch_follow_information(user) do
1123 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1125 |> follow_information_changeset(info)
1126 |> update_and_set_cache()
1130 defp follow_information_changeset(user, params) do
1137 :hide_followers_count,
1142 def update_follower_count(%User{} = user) do
1143 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
1144 follower_count_query =
1145 User.Query.build(%{followers: user, deactivated: false})
1146 |> select([u], %{count: count(u.id)})
1149 |> where(id: ^user.id)
1150 |> join(:inner, [u], s in subquery(follower_count_query))
1152 set: [follower_count: s.count]
1155 |> Repo.update_all([])
1157 {1, [user]} -> set_cache(user)
1161 {:ok, maybe_fetch_follow_information(user)}
1165 @spec update_following_count(User.t()) :: User.t()
1166 def update_following_count(%User{local: false} = user) do
1167 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1168 maybe_fetch_follow_information(user)
1174 def update_following_count(%User{local: true} = user) do
1175 following_count = FollowingRelationship.following_count(user)
1178 |> follow_information_changeset(%{following_count: following_count})
1182 def set_unread_conversation_count(%User{local: true} = user) do
1183 unread_query = Participation.unread_conversation_count_for_user(user)
1186 |> join(:inner, [u], p in subquery(unread_query))
1188 set: [unread_conversation_count: p.count]
1190 |> where([u], u.id == ^user.id)
1192 |> Repo.update_all([])
1194 {1, [user]} -> set_cache(user)
1199 def set_unread_conversation_count(user), do: {:ok, user}
1201 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1203 Participation.unread_conversation_count_for_user(user)
1204 |> where([p], p.conversation_id == ^conversation.id)
1207 |> join(:inner, [u], p in subquery(unread_query))
1209 inc: [unread_conversation_count: 1]
1211 |> where([u], u.id == ^user.id)
1212 |> where([u, p], p.count == 0)
1214 |> Repo.update_all([])
1216 {1, [user]} -> set_cache(user)
1221 def increment_unread_conversation_count(_, user), do: {:ok, user}
1223 @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
1224 def get_users_from_set(ap_ids, opts \\ []) do
1225 local_only = Keyword.get(opts, :local_only, true)
1226 criteria = %{ap_id: ap_ids, deactivated: false}
1227 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1229 User.Query.build(criteria)
1233 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1234 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1237 query = User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1243 @spec mute(User.t(), User.t(), boolean()) ::
1244 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1245 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1246 add_to_mutes(muter, mutee, notifications?)
1249 def unmute(%User{} = muter, %User{} = mutee) do
1250 remove_from_mutes(muter, mutee)
1253 def subscribe(%User{} = subscriber, %User{} = target) do
1254 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1256 if blocks?(target, subscriber) and deny_follow_blocked do
1257 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1259 # Note: the relationship is inverse: subscriber acts as relationship target
1260 UserRelationship.create_inverse_subscription(target, subscriber)
1264 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1265 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1266 subscribe(subscriber, subscribee)
1270 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1271 # Note: the relationship is inverse: subscriber acts as relationship target
1272 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1275 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1276 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1277 unsubscribe(unsubscriber, user)
1281 def block(%User{} = blocker, %User{} = blocked) do
1282 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1284 if following?(blocker, blocked) do
1285 {:ok, blocker, _} = unfollow(blocker, blocked)
1291 # clear any requested follows as well
1293 case CommonAPI.reject_follow_request(blocked, blocker) do
1294 {:ok, %User{} = updated_blocked} -> updated_blocked
1298 unsubscribe(blocked, blocker)
1300 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1302 {:ok, blocker} = update_follower_count(blocker)
1303 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1304 add_to_block(blocker, blocked)
1307 # helper to handle the block given only an actor's AP id
1308 def block(%User{} = blocker, %{ap_id: ap_id}) do
1309 block(blocker, get_cached_by_ap_id(ap_id))
1312 def unblock(%User{} = blocker, %User{} = blocked) do
1313 remove_from_block(blocker, blocked)
1316 # helper to handle the block given only an actor's AP id
1317 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1318 unblock(blocker, get_cached_by_ap_id(ap_id))
1321 def mutes?(nil, _), do: false
1322 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1324 def mutes_user?(%User{} = user, %User{} = target) do
1325 UserRelationship.mute_exists?(user, target)
1328 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1329 def muted_notifications?(nil, _), do: false
1331 def muted_notifications?(%User{} = user, %User{} = target),
1332 do: UserRelationship.notification_mute_exists?(user, target)
1334 def blocks?(nil, _), do: false
1336 def blocks?(%User{} = user, %User{} = target) do
1337 blocks_user?(user, target) ||
1338 (blocks_domain?(user, target) and not User.following?(user, target))
1341 def blocks_user?(%User{} = user, %User{} = target) do
1342 UserRelationship.block_exists?(user, target)
1345 def blocks_user?(_, _), do: false
1347 def blocks_domain?(%User{} = user, %User{} = target) do
1348 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1349 %{host: host} = URI.parse(target.ap_id)
1350 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1353 def blocks_domain?(_, _), do: false
1355 def subscribed_to?(%User{} = user, %User{} = target) do
1356 # Note: the relationship is inverse: subscriber acts as relationship target
1357 UserRelationship.inverse_subscription_exists?(target, user)
1360 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1361 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1362 subscribed_to?(user, target)
1367 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1368 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1370 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1371 def outgoing_relationships_ap_ids(_user, []), do: %{}
1373 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1375 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1376 when is_list(relationship_types) do
1379 |> assoc(:outgoing_relationships)
1380 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1381 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1382 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1383 |> group_by([user_rel, u], user_rel.relationship_type)
1385 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1390 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1394 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1396 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1398 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1400 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1401 when is_list(relationship_types) do
1403 |> assoc(:incoming_relationships)
1404 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1405 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1406 |> maybe_filter_on_ap_id(ap_ids)
1407 |> select([user_rel, u], u.ap_id)
1412 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1413 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1416 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1418 def deactivate_async(user, status \\ true) do
1419 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1422 def deactivate(user, status \\ true)
1424 def deactivate(users, status) when is_list(users) do
1425 Repo.transaction(fn ->
1426 for user <- users, do: deactivate(user, status)
1430 def deactivate(%User{} = user, status) do
1431 with {:ok, user} <- set_activation_status(user, status) do
1434 |> Enum.filter(& &1.local)
1435 |> Enum.each(&set_cache(update_following_count(&1)))
1437 # Only update local user counts, remote will be update during the next pull.
1440 |> Enum.filter(& &1.local)
1441 |> Enum.each(&do_unfollow(user, &1))
1447 def update_notification_settings(%User{} = user, settings) do
1449 |> cast(%{notification_settings: settings}, [])
1450 |> cast_embed(:notification_settings)
1451 |> validate_required([:notification_settings])
1452 |> update_and_set_cache()
1455 def delete(users) when is_list(users) do
1456 for user <- users, do: delete(user)
1459 def delete(%User{} = user) do
1460 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1463 defp delete_and_invalidate_cache(%User{} = user) do
1464 invalidate_cache(user)
1468 defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user)
1470 defp delete_or_deactivate(%User{local: true} = user) do
1471 status = account_status(user)
1473 if status == :confirmation_pending do
1474 delete_and_invalidate_cache(user)
1477 |> change(%{deactivated: true, email: nil})
1478 |> update_and_set_cache()
1482 def perform(:force_password_reset, user), do: force_password_reset(user)
1484 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1485 def perform(:delete, %User{} = user) do
1486 # Remove all relationships
1489 |> Enum.each(fn follower ->
1490 ActivityPub.unfollow(follower, user)
1491 unfollow(follower, user)
1496 |> Enum.each(fn followed ->
1497 ActivityPub.unfollow(user, followed)
1498 unfollow(user, followed)
1501 delete_user_activities(user)
1502 delete_notifications_from_user_activities(user)
1504 delete_outgoing_pending_follow_requests(user)
1506 delete_or_deactivate(user)
1509 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1511 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1512 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1513 when is_list(blocked_identifiers) do
1515 blocked_identifiers,
1516 fn blocked_identifier ->
1517 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1518 {:ok, _user_block} <- block(blocker, blocked),
1519 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1523 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1530 def perform(:follow_import, %User{} = follower, followed_identifiers)
1531 when is_list(followed_identifiers) do
1533 followed_identifiers,
1534 fn followed_identifier ->
1535 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1536 {:ok, follower} <- maybe_direct_follow(follower, followed),
1537 {:ok, _} <- ActivityPub.follow(follower, followed) do
1541 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1548 @spec external_users_query() :: Ecto.Query.t()
1549 def external_users_query do
1557 @spec external_users(keyword()) :: [User.t()]
1558 def external_users(opts \\ []) do
1560 external_users_query()
1561 |> select([u], struct(u, [:id, :ap_id]))
1565 do: where(query, [u], u.id > ^opts[:max_id]),
1570 do: limit(query, ^opts[:limit]),
1576 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1577 BackgroundWorker.enqueue("blocks_import", %{
1578 "blocker_id" => blocker.id,
1579 "blocked_identifiers" => blocked_identifiers
1583 def follow_import(%User{} = follower, followed_identifiers)
1584 when is_list(followed_identifiers) do
1585 BackgroundWorker.enqueue("follow_import", %{
1586 "follower_id" => follower.id,
1587 "followed_identifiers" => followed_identifiers
1591 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1593 |> join(:inner, [n], activity in assoc(n, :activity))
1594 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1595 |> Repo.delete_all()
1598 def delete_user_activities(%User{ap_id: ap_id} = user) do
1600 |> Activity.Queries.by_actor()
1601 |> RepoStreamer.chunk_stream(50)
1602 |> Stream.each(fn activities ->
1603 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1608 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1609 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1610 {:ok, delete_data, _} <- Builder.delete(user, object) do
1611 Pipeline.common_pipeline(delete_data, local: user.local)
1613 {:find_object, nil} ->
1614 # We have the create activity, but not the object, it was probably pruned.
1615 # Insert a tombstone and try again
1616 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1617 {:ok, _tombstone} <- Object.create(tombstone_data) do
1618 delete_activity(activity, user)
1622 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1623 Logger.error("Error: #{inspect(e)}")
1627 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1628 when type in ["Like", "Announce"] do
1629 {:ok, undo, _} = Builder.undo(user, activity)
1630 Pipeline.common_pipeline(undo, local: user.local)
1633 defp delete_activity(_activity, _user), do: "Doing nothing"
1635 defp delete_outgoing_pending_follow_requests(user) do
1637 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1638 |> Repo.delete_all()
1641 def html_filter_policy(%User{no_rich_text: true}) do
1642 Pleroma.HTML.Scrubber.TwitterText
1645 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1647 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1649 def get_or_fetch_by_ap_id(ap_id) do
1650 cached_user = get_cached_by_ap_id(ap_id)
1652 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
1654 case {cached_user, maybe_fetched_user} do
1655 {_, {:ok, %User{} = user}} ->
1658 {%User{} = user, _} ->
1662 {:error, :not_found}
1667 Creates an internal service actor by URI if missing.
1668 Optionally takes nickname for addressing.
1670 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1671 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1673 case get_cached_by_ap_id(uri) do
1675 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1676 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1680 %User{invisible: false} = user ->
1690 @spec set_invisible(User.t()) :: {:ok, User.t()}
1691 defp set_invisible(user) do
1693 |> change(%{invisible: true})
1694 |> update_and_set_cache()
1697 @spec create_service_actor(String.t(), String.t()) ::
1698 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1699 defp create_service_actor(uri, nickname) do
1705 follower_address: uri <> "/followers"
1708 |> unique_constraint(:nickname)
1713 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1716 |> :public_key.pem_decode()
1718 |> :public_key.pem_entry_decode()
1723 def public_key(_), do: {:error, "key not found"}
1725 def get_public_key_for_ap_id(ap_id) do
1726 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1727 {:ok, public_key} <- public_key(user) do
1734 def ap_enabled?(%User{local: true}), do: true
1735 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1736 def ap_enabled?(_), do: false
1738 @doc "Gets or fetch a user by uri or nickname."
1739 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1740 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1741 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1743 # wait a period of time and return newest version of the User structs
1744 # this is because we have synchronous follow APIs and need to simulate them
1745 # with an async handshake
1746 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1747 with %User{} = a <- get_cached_by_id(a.id),
1748 %User{} = b <- get_cached_by_id(b.id) do
1755 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1756 with :ok <- :timer.sleep(timeout),
1757 %User{} = a <- get_cached_by_id(a.id),
1758 %User{} = b <- get_cached_by_id(b.id) do
1765 def parse_bio(bio) when is_binary(bio) and bio != "" do
1767 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1771 def parse_bio(_), do: ""
1773 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1774 # TODO: get profile URLs other than user.ap_id
1775 profile_urls = [user.ap_id]
1778 |> CommonUtils.format_input("text/plain",
1779 mentions_format: :full,
1780 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1785 def parse_bio(_, _), do: ""
1787 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1788 Repo.transaction(fn ->
1789 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1793 def tag(nickname, tags) when is_binary(nickname),
1794 do: tag(get_by_nickname(nickname), tags)
1796 def tag(%User{} = user, tags),
1797 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1799 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1800 Repo.transaction(fn ->
1801 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1805 def untag(nickname, tags) when is_binary(nickname),
1806 do: untag(get_by_nickname(nickname), tags)
1808 def untag(%User{} = user, tags),
1809 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1811 defp update_tags(%User{} = user, new_tags) do
1812 {:ok, updated_user} =
1814 |> change(%{tags: new_tags})
1815 |> update_and_set_cache()
1820 defp normalize_tags(tags) do
1823 |> Enum.map(&String.downcase/1)
1826 defp local_nickname_regex do
1827 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1828 @extended_local_nickname_regex
1830 @strict_local_nickname_regex
1834 def local_nickname(nickname_or_mention) do
1837 |> String.split("@")
1841 def full_nickname(nickname_or_mention),
1842 do: String.trim_leading(nickname_or_mention, "@")
1844 def error_user(ap_id) do
1848 nickname: "erroruser@example.com",
1849 inserted_at: NaiveDateTime.utc_now()
1853 @spec all_superusers() :: [User.t()]
1854 def all_superusers do
1855 User.Query.build(%{super_users: true, local: true, deactivated: false})
1859 def muting_reblogs?(%User{} = user, %User{} = target) do
1860 UserRelationship.reblog_mute_exists?(user, target)
1863 def showing_reblogs?(%User{} = user, %User{} = target) do
1864 not muting_reblogs?(user, target)
1868 The function returns a query to get users with no activity for given interval of days.
1869 Inactive users are those who didn't read any notification, or had any activity where
1870 the user is the activity's actor, during `inactivity_threshold` days.
1871 Deactivated users will not appear in this list.
1875 iex> Pleroma.User.list_inactive_users()
1878 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1879 def list_inactive_users_query(inactivity_threshold \\ 7) do
1880 negative_inactivity_threshold = -inactivity_threshold
1881 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1882 # Subqueries are not supported in `where` clauses, join gets too complicated.
1883 has_read_notifications =
1884 from(n in Pleroma.Notification,
1885 where: n.seen == true,
1887 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1890 |> Pleroma.Repo.all()
1892 from(u in Pleroma.User,
1893 left_join: a in Pleroma.Activity,
1894 on: u.ap_id == a.actor,
1895 where: not is_nil(u.nickname),
1896 where: u.deactivated != ^true,
1897 where: u.id not in ^has_read_notifications,
1900 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1901 is_nil(max(a.inserted_at))
1906 Enable or disable email notifications for user
1910 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1911 Pleroma.User{email_notifications: %{"digest" => true}}
1913 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1914 Pleroma.User{email_notifications: %{"digest" => false}}
1916 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1917 {:ok, t()} | {:error, Ecto.Changeset.t()}
1918 def switch_email_notifications(user, type, status) do
1919 User.update_email_notifications(user, %{type => status})
1923 Set `last_digest_emailed_at` value for the user to current time
1925 @spec touch_last_digest_emailed_at(t()) :: t()
1926 def touch_last_digest_emailed_at(user) do
1927 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1929 {:ok, updated_user} =
1931 |> change(%{last_digest_emailed_at: now})
1932 |> update_and_set_cache()
1937 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1938 def toggle_confirmation(%User{} = user) do
1940 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1941 |> update_and_set_cache()
1944 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1945 def toggle_confirmation(users) do
1946 Enum.map(users, &toggle_confirmation/1)
1949 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1953 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1954 # use instance-default
1955 config = Pleroma.Config.get([:assets, :mascots])
1956 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1957 mascot = Keyword.get(config, default_mascot)
1960 "id" => "default-mascot",
1961 "url" => mascot[:url],
1962 "preview_url" => mascot[:url],
1964 "mime_type" => mascot[:mime_type]
1969 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1971 def ensure_keys_present(%User{} = user) do
1972 with {:ok, pem} <- Keys.generate_rsa_pem() do
1974 |> cast(%{keys: pem}, [:keys])
1975 |> validate_required([:keys])
1976 |> update_and_set_cache()
1980 def get_ap_ids_by_nicknames(nicknames) do
1982 where: u.nickname in ^nicknames,
1988 defdelegate search(query, opts \\ []), to: User.Search
1990 defp put_password_hash(
1991 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1993 change(changeset, password_hash: Pbkdf2.hash_pwd_salt(password))
1996 defp put_password_hash(changeset), do: changeset
1998 def is_internal_user?(%User{nickname: nil}), do: true
1999 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2000 def is_internal_user?(_), do: false
2002 # A hack because user delete activities have a fake id for whatever reason
2003 # TODO: Get rid of this
2004 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2006 def get_delivered_users_by_object_id(object_id) do
2008 inner_join: delivery in assoc(u, :deliveries),
2009 where: delivery.object_id == ^object_id
2014 def change_email(user, email) do
2016 |> cast(%{email: email}, [:email])
2017 |> validate_required([:email])
2018 |> unique_constraint(:email)
2019 |> validate_format(:email, @email_regex)
2020 |> update_and_set_cache()
2023 # Internal function; public one is `deactivate/2`
2024 defp set_activation_status(user, deactivated) do
2026 |> cast(%{deactivated: deactivated}, [:deactivated])
2027 |> update_and_set_cache()
2030 def update_banner(user, banner) do
2032 |> cast(%{banner: banner}, [:banner])
2033 |> update_and_set_cache()
2036 def update_background(user, background) do
2038 |> cast(%{background: background}, [:background])
2039 |> update_and_set_cache()
2042 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
2045 moderator: is_moderator
2049 def validate_fields(changeset, remote? \\ false) do
2050 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2051 limit = Pleroma.Config.get([:instance, limit_name], 0)
2054 |> validate_length(:fields, max: limit)
2055 |> validate_change(:fields, fn :fields, fields ->
2056 if Enum.all?(fields, &valid_field?/1) do
2064 defp valid_field?(%{"name" => name, "value" => value}) do
2065 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
2066 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
2068 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2069 String.length(value) <= value_limit
2072 defp valid_field?(_), do: false
2074 defp truncate_field(%{"name" => name, "value" => value}) do
2076 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
2079 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
2081 %{"name" => name, "value" => value}
2084 def admin_api_update(user, params) do
2091 |> update_and_set_cache()
2094 @doc "Signs user out of all applications"
2095 def global_sign_out(user) do
2096 OAuth.Authorization.delete_user_authorizations(user)
2097 OAuth.Token.delete_user_tokens(user)
2100 def mascot_update(user, url) do
2102 |> cast(%{mascot: url}, [:mascot])
2103 |> validate_required([:mascot])
2104 |> update_and_set_cache()
2107 def mastodon_settings_update(user, settings) do
2109 |> cast(%{settings: settings}, [:settings])
2110 |> validate_required([:settings])
2111 |> update_and_set_cache()
2114 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2115 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2117 if need_confirmation? do
2119 confirmation_pending: true,
2120 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2124 confirmation_pending: false,
2125 confirmation_token: nil
2129 cast(user, params, [:confirmation_pending, :confirmation_token])
2132 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2133 if id not in user.pinned_activities do
2134 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
2135 params = %{pinned_activities: user.pinned_activities ++ [id]}
2138 |> cast(params, [:pinned_activities])
2139 |> validate_length(:pinned_activities,
2140 max: max_pinned_statuses,
2141 message: "You have already pinned the maximum number of statuses"
2146 |> update_and_set_cache()
2149 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2150 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2153 |> cast(params, [:pinned_activities])
2154 |> update_and_set_cache()
2157 def update_email_notifications(user, settings) do
2158 email_notifications =
2159 user.email_notifications
2160 |> Map.merge(settings)
2161 |> Map.take(["digest"])
2163 params = %{email_notifications: email_notifications}
2164 fields = [:email_notifications]
2167 |> cast(params, fields)
2168 |> validate_required(fields)
2169 |> update_and_set_cache()
2172 defp set_domain_blocks(user, domain_blocks) do
2173 params = %{domain_blocks: domain_blocks}
2176 |> cast(params, [:domain_blocks])
2177 |> validate_required([:domain_blocks])
2178 |> update_and_set_cache()
2181 def block_domain(user, domain_blocked) do
2182 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2185 def unblock_domain(user, domain_blocked) do
2186 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2189 @spec add_to_block(User.t(), User.t()) ::
2190 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2191 defp add_to_block(%User{} = user, %User{} = blocked) do
2192 UserRelationship.create_block(user, blocked)
2195 @spec add_to_block(User.t(), User.t()) ::
2196 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2197 defp remove_from_block(%User{} = user, %User{} = blocked) do
2198 UserRelationship.delete_block(user, blocked)
2201 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2202 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2203 {:ok, user_notification_mute} <-
2204 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2206 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2210 defp remove_from_mutes(user, %User{} = muted_user) do
2211 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2212 {:ok, user_notification_mute} <-
2213 UserRelationship.delete_notification_mute(user, muted_user) do
2214 {:ok, [user_mute, user_notification_mute]}
2218 def set_invisible(user, invisible) do
2219 params = %{invisible: invisible}
2222 |> cast(params, [:invisible])
2223 |> validate_required([:invisible])
2224 |> update_and_set_cache()
2227 def sanitize_html(%User{} = user) do
2228 sanitize_html(user, nil)
2231 # User data that mastodon isn't filtering (treated as plaintext):
2234 def sanitize_html(%User{} = user, filter) do
2236 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2239 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2244 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2245 |> Map.put(:fields, fields)