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])?)*$/
50 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
51 @url_regex ~r/https?:\/\/[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/=]*)/
53 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
54 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
56 # AP ID user relationships (blocks, mutes etc.)
57 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
58 @user_relationships_config [
60 blocker_blocks: :blocked_users,
61 blockee_blocks: :blocker_users
64 muter_mutes: :muted_users,
65 mutee_mutes: :muter_users
68 reblog_muter_mutes: :reblog_muted_users,
69 reblog_mutee_mutes: :reblog_muter_users
72 notification_muter_mutes: :notification_muted_users,
73 notification_mutee_mutes: :notification_muter_users
75 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
76 inverse_subscription: [
77 subscribee_subscriptions: :subscriber_users,
78 subscriber_subscriptions: :subscribee_users
84 field(:raw_bio, :string)
85 field(:email, :string)
87 field(:nickname, :string)
88 field(:password_hash, :string)
89 field(:password, :string, virtual: true)
90 field(:password_confirmation, :string, virtual: true)
92 field(:public_key, :string)
93 field(:ap_id, :string)
94 field(:ap_aliases, {:array, :string}, default: [])
95 field(:avatar, :map, default: %{})
96 field(:local, :boolean, default: true)
97 field(:follower_address, :string)
98 field(:following_address, :string)
99 field(:search_rank, :float, virtual: true)
100 field(:search_type, :integer, virtual: true)
101 field(:tags, {:array, :string}, default: [])
102 field(:last_refreshed_at, :naive_datetime_usec)
103 field(:last_digest_emailed_at, :naive_datetime)
104 field(:banner, :map, default: %{})
105 field(:background, :map, default: %{})
106 field(:note_count, :integer, default: 0)
107 field(:follower_count, :integer, default: 0)
108 field(:following_count, :integer, default: 0)
109 field(:locked, :boolean, default: false)
110 field(:confirmation_pending, :boolean, default: false)
111 field(:password_reset_pending, :boolean, default: false)
112 field(:confirmation_token, :string, default: nil)
113 field(:default_scope, :string, default: "public")
114 field(:domain_blocks, {:array, :string}, default: [])
115 field(:deactivated, :boolean, default: false)
116 field(:no_rich_text, :boolean, default: false)
117 field(:ap_enabled, :boolean, default: false)
118 field(:is_moderator, :boolean, default: false)
119 field(:is_admin, :boolean, default: false)
120 field(:show_role, :boolean, default: true)
121 field(:mastofe_settings, :map, default: nil)
122 field(:uri, ObjectValidators.Uri, default: nil)
123 field(:hide_followers_count, :boolean, default: false)
124 field(:hide_follows_count, :boolean, default: false)
125 field(:hide_followers, :boolean, default: false)
126 field(:hide_follows, :boolean, default: false)
127 field(:hide_favorites, :boolean, default: true)
128 field(:unread_conversation_count, :integer, default: 0)
129 field(:pinned_activities, {:array, :string}, default: [])
130 field(:email_notifications, :map, default: %{"digest" => false})
131 field(:mascot, :map, default: nil)
132 field(:emoji, :map, default: %{})
133 field(:pleroma_settings_store, :map, default: %{})
134 field(:fields, {:array, :map}, default: [])
135 field(:raw_fields, {:array, :map}, default: [])
136 field(:discoverable, :boolean, default: false)
137 field(:invisible, :boolean, default: false)
138 field(:allow_following_move, :boolean, default: true)
139 field(:skip_thread_containment, :boolean, default: false)
140 field(:actor_type, :string, default: "Person")
141 field(:also_known_as, {:array, :string}, default: [])
142 field(:inbox, :string)
143 field(:shared_inbox, :string)
144 field(:accepts_chat_messages, :boolean, default: nil)
147 :notification_settings,
148 Pleroma.User.NotificationSetting,
152 has_many(:notifications, Notification)
153 has_many(:registrations, Registration)
154 has_many(:deliveries, Delivery)
156 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
157 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
159 for {relationship_type,
161 {outgoing_relation, outgoing_relation_target},
162 {incoming_relation, incoming_relation_source}
163 ]} <- @user_relationships_config do
164 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
165 # :notification_muter_mutes, :subscribee_subscriptions
166 has_many(outgoing_relation, UserRelationship,
167 foreign_key: :source_id,
168 where: [relationship_type: relationship_type]
171 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
172 # :notification_mutee_mutes, :subscriber_subscriptions
173 has_many(incoming_relation, UserRelationship,
174 foreign_key: :target_id,
175 where: [relationship_type: relationship_type]
178 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
179 # :notification_muted_users, :subscriber_users
180 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
182 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
183 # :notification_muter_users, :subscribee_users
184 has_many(incoming_relation_source, through: [incoming_relation, :source])
187 # `:blocks` is deprecated (replaced with `blocked_users` relation)
188 field(:blocks, {:array, :string}, default: [])
189 # `:mutes` is deprecated (replaced with `muted_users` relation)
190 field(:mutes, {:array, :string}, default: [])
191 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
192 field(:muted_reblogs, {:array, :string}, default: [])
193 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
194 field(:muted_notifications, {:array, :string}, default: [])
195 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
196 field(:subscribers, {:array, :string}, default: [])
199 :multi_factor_authentication_settings,
207 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
208 @user_relationships_config do
209 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
210 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
211 # `def subscriber_users/2`
212 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
213 target_users_query = assoc(user, unquote(outgoing_relation_target))
215 if restrict_deactivated? do
216 restrict_deactivated(target_users_query)
222 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
223 # `def notification_muted_users/2`, `def subscriber_users/2`
224 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
226 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
228 restrict_deactivated?
233 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
234 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
235 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
237 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
239 restrict_deactivated?
241 |> select([u], u.ap_id)
247 Dumps Flake Id to SQL-compatible format (16-byte UUID).
248 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
250 def binary_id(source_id) when is_binary(source_id) do
251 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
258 def binary_id(source_ids) when is_list(source_ids) do
259 Enum.map(source_ids, &binary_id/1)
262 def binary_id(%User{} = user), do: binary_id(user.id)
264 @doc "Returns status account"
265 @spec account_status(User.t()) :: account_status()
266 def account_status(%User{deactivated: true}), do: :deactivated
267 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
269 def account_status(%User{confirmation_pending: true}) do
270 if Config.get([:instance, :account_activation_required]) do
271 :confirmation_pending
277 def account_status(%User{}), do: :active
279 @spec visible_for(User.t(), User.t() | nil) ::
282 | :restricted_unauthenticated
284 | :confirmation_pending
285 def visible_for(user, for_user \\ nil)
287 def visible_for(%User{invisible: true}, _), do: :invisible
289 def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
291 def visible_for(%User{} = user, nil) do
292 if restrict_unauthenticated?(user) do
293 :restrict_unauthenticated
295 visible_account_status(user)
299 def visible_for(%User{} = user, for_user) do
300 if superuser?(for_user) do
303 visible_account_status(user)
307 def visible_for(_, _), do: :invisible
309 defp restrict_unauthenticated?(%User{local: local}) do
310 config_key = if local, do: :local, else: :remote
312 Config.get([:restrict_unauthenticated, :profiles, config_key], false)
315 defp visible_account_status(user) do
316 status = account_status(user)
318 if status in [:active, :password_reset_pending] do
325 @spec superuser?(User.t()) :: boolean()
326 def superuser?(%User{local: true, is_admin: true}), do: true
327 def superuser?(%User{local: true, is_moderator: true}), do: true
328 def superuser?(_), do: false
330 @spec invisible?(User.t()) :: boolean()
331 def invisible?(%User{invisible: true}), do: true
332 def invisible?(_), do: false
334 def avatar_url(user, options \\ []) do
336 %{"url" => [%{"href" => href} | _]} ->
340 unless options[:no_default] do
341 Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png")
346 def banner_url(user, options \\ []) do
348 %{"url" => [%{"href" => href} | _]} -> href
349 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
353 # Should probably be renamed or removed
354 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
356 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
357 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
359 @spec ap_following(User.t()) :: String.t()
360 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
361 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
363 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
364 def restrict_deactivated(query) do
365 from(u in query, where: u.deactivated != ^true)
368 defdelegate following_count(user), to: FollowingRelationship
370 defp truncate_fields_param(params) do
371 if Map.has_key?(params, :fields) do
372 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
378 defp truncate_if_exists(params, key, max_length) do
379 if Map.has_key?(params, key) and is_binary(params[key]) do
380 {value, _chopped} = String.split_at(params[key], max_length)
381 Map.put(params, key, value)
387 defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
389 defp fix_follower_address(%{nickname: nickname} = params),
390 do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
392 defp fix_follower_address(params), do: params
394 def remote_user_changeset(struct \\ %User{local: false}, params) do
395 bio_limit = Config.get([:instance, :user_bio_length], 5000)
396 name_limit = Config.get([:instance, :user_name_length], 100)
399 case params[:name] do
400 name when is_binary(name) and byte_size(name) > 0 -> name
401 _ -> params[:nickname]
406 |> Map.put(:name, name)
407 |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
408 |> truncate_if_exists(:name, name_limit)
409 |> truncate_if_exists(:bio, bio_limit)
410 |> truncate_fields_param()
411 |> fix_follower_address()
435 :hide_followers_count,
444 :accepts_chat_messages
447 |> validate_required([:name, :ap_id])
448 |> unique_constraint(:nickname)
449 |> validate_format(:nickname, @email_regex)
450 |> validate_length(:bio, max: bio_limit)
451 |> validate_length(:name, max: name_limit)
452 |> validate_fields(true)
455 def update_changeset(struct, params \\ %{}) do
456 bio_limit = Config.get([:instance, :user_bio_length], 5000)
457 name_limit = Config.get([:instance, :user_name_length], 100)
477 :hide_followers_count,
480 :allow_following_move,
483 :skip_thread_containment,
486 :pleroma_settings_store,
490 :accepts_chat_messages
493 |> unique_constraint(:nickname)
494 |> validate_format(:nickname, local_nickname_regex())
495 |> validate_length(:bio, max: bio_limit)
496 |> validate_length(:name, min: 1, max: name_limit)
497 |> validate_inclusion(:actor_type, ["Person", "Service"])
500 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
501 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
502 |> put_change_if_present(:banner, &put_upload(&1, :banner))
503 |> put_change_if_present(:background, &put_upload(&1, :background))
504 |> put_change_if_present(
505 :pleroma_settings_store,
506 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
508 |> validate_fields(false)
511 defp put_fields(changeset) do
512 if raw_fields = get_change(changeset, :raw_fields) do
515 |> Enum.filter(fn %{"name" => n} -> n != "" end)
519 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
522 |> put_change(:raw_fields, raw_fields)
523 |> put_change(:fields, fields)
529 defp parse_fields(value) do
531 |> Formatter.linkify(mentions_format: :full)
535 defp put_emoji(changeset) do
536 emojified_fields = [:bio, :name, :raw_fields]
538 if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
539 bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
540 name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
542 emoji = Map.merge(bio, name)
546 |> get_field(:raw_fields)
547 |> Enum.reduce(emoji, fn x, acc ->
548 Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
551 put_change(changeset, :emoji, emoji)
557 defp put_change_if_present(changeset, map_field, value_function) do
558 with {:ok, value} <- fetch_change(changeset, map_field),
559 {:ok, new_value} <- value_function.(value) do
560 put_change(changeset, map_field, new_value)
566 defp put_upload(value, type) do
567 with %Plug.Upload{} <- value,
568 {:ok, object} <- ActivityPub.upload(value, type: type) do
573 def update_as_admin_changeset(struct, params) do
575 |> update_changeset(params)
576 |> cast(params, [:email])
577 |> delete_change(:also_known_as)
578 |> unique_constraint(:email)
579 |> validate_format(:email, @email_regex)
580 |> validate_inclusion(:actor_type, ["Person", "Service"])
583 @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
584 def update_as_admin(user, params) do
585 params = Map.put(params, "password_confirmation", params["password"])
586 changeset = update_as_admin_changeset(user, params)
588 if params["password"] do
589 reset_password(user, changeset, params)
591 User.update_and_set_cache(changeset)
595 def password_update_changeset(struct, params) do
597 |> cast(params, [:password, :password_confirmation])
598 |> validate_required([:password, :password_confirmation])
599 |> validate_confirmation(:password)
600 |> put_password_hash()
601 |> put_change(:password_reset_pending, false)
604 @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
605 def reset_password(%User{} = user, params) do
606 reset_password(user, user, params)
609 def reset_password(%User{id: user_id} = user, struct, params) do
612 |> Multi.update(:user, password_update_changeset(struct, params))
613 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
614 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
616 case Repo.transaction(multi) do
617 {:ok, %{user: user} = _} -> set_cache(user)
618 {:error, _, changeset, _} -> {:error, changeset}
622 def update_password_reset_pending(user, value) do
625 |> put_change(:password_reset_pending, value)
626 |> update_and_set_cache()
629 def force_password_reset_async(user) do
630 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
633 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
634 def force_password_reset(user), do: update_password_reset_pending(user, true)
636 def register_changeset(struct, params \\ %{}, opts \\ []) do
637 bio_limit = Config.get([:instance, :user_bio_length], 5000)
638 name_limit = Config.get([:instance, :user_name_length], 100)
639 params = Map.put_new(params, :accepts_chat_messages, true)
642 if is_nil(opts[:need_confirmation]) do
643 Config.get([:instance, :account_activation_required])
645 opts[:need_confirmation]
649 |> confirmation_changeset(need_confirmation: need_confirmation?)
657 :password_confirmation,
659 :accepts_chat_messages
661 |> validate_required([:name, :nickname, :password, :password_confirmation])
662 |> validate_confirmation(:password)
663 |> unique_constraint(:email)
664 |> unique_constraint(:nickname)
665 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
666 |> validate_format(:nickname, local_nickname_regex())
667 |> validate_format(:email, @email_regex)
668 |> validate_length(:bio, max: bio_limit)
669 |> validate_length(:name, min: 1, max: name_limit)
670 |> maybe_validate_required_email(opts[:external])
673 |> unique_constraint(:ap_id)
674 |> put_following_and_follower_address()
677 def maybe_validate_required_email(changeset, true), do: changeset
679 def maybe_validate_required_email(changeset, _) do
680 if Config.get([:instance, :account_activation_required]) do
681 validate_required(changeset, [:email])
687 defp put_ap_id(changeset) do
688 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
689 put_change(changeset, :ap_id, ap_id)
692 defp put_following_and_follower_address(changeset) do
693 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
696 |> put_change(:follower_address, followers)
699 defp autofollow_users(user) do
700 candidates = Config.get([:instance, :autofollowed_nicknames])
703 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
706 follow_all(user, autofollowed_users)
709 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
710 def register(%Ecto.Changeset{} = changeset) do
711 with {:ok, user} <- Repo.insert(changeset) do
712 post_register_action(user)
716 def post_register_action(%User{} = user) do
717 with {:ok, user} <- autofollow_users(user),
718 {:ok, user} <- set_cache(user),
719 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
720 {:ok, _} <- try_send_confirmation_email(user) do
725 def try_send_confirmation_email(%User{} = user) do
726 if user.confirmation_pending &&
727 Config.get([:instance, :account_activation_required]) do
729 |> Pleroma.Emails.UserEmail.account_confirmation_email()
730 |> Pleroma.Emails.Mailer.deliver_async()
738 def try_send_confirmation_email(users) do
739 Enum.each(users, &try_send_confirmation_email/1)
742 def needs_update?(%User{local: true}), do: false
744 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
746 def needs_update?(%User{local: false} = user) do
747 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
750 def needs_update?(_), do: true
752 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
754 # "Locked" (self-locked) users demand explicit authorization of follow requests
755 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
756 follow(follower, followed, :follow_pending)
759 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
760 follow(follower, followed)
763 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
764 if not ap_enabled?(followed) do
765 follow(follower, followed)
771 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
772 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
773 def follow_all(follower, followeds) do
775 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
776 |> Enum.each(&follow(follower, &1, :follow_accept))
781 defdelegate following(user), to: FollowingRelationship
783 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
784 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
787 followed.deactivated ->
788 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
790 deny_follow_blocked and blocks?(followed, follower) ->
791 {:error, "Could not follow user: #{followed.nickname} blocked you."}
794 FollowingRelationship.follow(follower, followed, state)
796 {:ok, _} = update_follower_count(followed)
799 |> update_following_count()
803 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
804 {:error, "Not subscribed!"}
807 @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
808 def unfollow(%User{} = follower, %User{} = followed) do
809 case do_unfollow(follower, followed) do
810 {:ok, follower, followed} ->
811 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
818 @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
819 defp do_unfollow(%User{} = follower, %User{} = followed) do
820 case get_follow_state(follower, followed) do
821 state when state in [:follow_pending, :follow_accept] ->
822 FollowingRelationship.unfollow(follower, followed)
823 {:ok, followed} = update_follower_count(followed)
827 |> update_following_count()
829 {:ok, follower, followed}
832 {:error, "Not subscribed!"}
836 defdelegate following?(follower, followed), to: FollowingRelationship
838 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
839 def get_follow_state(%User{} = follower, %User{} = following) do
840 following_relationship = FollowingRelationship.get(follower, following)
841 get_follow_state(follower, following, following_relationship)
844 def get_follow_state(
847 following_relationship
849 case {following_relationship, following.local} do
851 case Utils.fetch_latest_follow(follower, following) do
852 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
853 FollowingRelationship.state_to_enum(state)
859 {%{state: state}, _} ->
867 def locked?(%User{} = user) do
872 Repo.get_by(User, id: id)
875 def get_by_ap_id(ap_id) do
876 Repo.get_by(User, ap_id: ap_id)
879 def get_all_by_ap_id(ap_ids) do
880 from(u in __MODULE__,
881 where: u.ap_id in ^ap_ids
886 def get_all_by_ids(ids) do
887 from(u in __MODULE__, where: u.id in ^ids)
891 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
892 # of the ap_id and the domain and tries to get that user
893 def get_by_guessed_nickname(ap_id) do
894 domain = URI.parse(ap_id).host
895 name = List.last(String.split(ap_id, "/"))
896 nickname = "#{name}@#{domain}"
898 get_cached_by_nickname(nickname)
901 def set_cache({:ok, user}), do: set_cache(user)
902 def set_cache({:error, err}), do: {:error, err}
904 def set_cache(%User{} = user) do
905 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
906 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
907 Cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
911 def update_and_set_cache(struct, params) do
913 |> update_changeset(params)
914 |> update_and_set_cache()
917 def update_and_set_cache(changeset) do
918 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
923 def get_user_friends_ap_ids(user) do
924 from(u in User.get_friends_query(user), select: u.ap_id)
928 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
929 def get_cached_user_friends_ap_ids(user) do
930 Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
931 get_user_friends_ap_ids(user)
935 def invalidate_cache(user) do
936 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
937 Cachex.del(:user_cache, "nickname:#{user.nickname}")
938 Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
941 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
942 def get_cached_by_ap_id(ap_id) do
943 key = "ap_id:#{ap_id}"
945 with {:ok, nil} <- Cachex.get(:user_cache, key),
946 user when not is_nil(user) <- get_by_ap_id(ap_id),
947 {:ok, true} <- Cachex.put(:user_cache, key, user) do
955 def get_cached_by_id(id) do
959 Cachex.fetch!(:user_cache, key, fn _ ->
963 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
964 {:commit, user.ap_id}
970 get_cached_by_ap_id(ap_id)
973 def get_cached_by_nickname(nickname) do
974 key = "nickname:#{nickname}"
976 Cachex.fetch!(:user_cache, key, fn ->
977 case get_or_fetch_by_nickname(nickname) do
978 {:ok, user} -> {:commit, user}
979 {:error, _error} -> {:ignore, nil}
984 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
985 restrict_to_local = Config.get([:instance, :limit_to_local_content])
988 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
989 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
991 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
992 get_cached_by_nickname(nickname_or_id)
994 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
995 get_cached_by_nickname(nickname_or_id)
1002 @spec get_by_nickname(String.t()) :: User.t() | nil
1003 def get_by_nickname(nickname) do
1004 Repo.get_by(User, nickname: nickname) ||
1005 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
1006 Repo.get_by(User, nickname: local_nickname(nickname))
1010 def get_by_email(email), do: Repo.get_by(User, email: email)
1012 def get_by_nickname_or_email(nickname_or_email) do
1013 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
1016 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
1018 def get_or_fetch_by_nickname(nickname) do
1019 with %User{} = user <- get_by_nickname(nickname) do
1023 with [_nick, _domain] <- String.split(nickname, "@"),
1024 {:ok, user} <- fetch_by_nickname(nickname) do
1027 _e -> {:error, "not found " <> nickname}
1032 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1033 def get_followers_query(%User{} = user, nil) do
1034 User.Query.build(%{followers: user, deactivated: false})
1037 def get_followers_query(user, page) do
1039 |> get_followers_query(nil)
1040 |> User.Query.paginate(page, 20)
1043 @spec get_followers_query(User.t()) :: Ecto.Query.t()
1044 def get_followers_query(user), do: get_followers_query(user, nil)
1046 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1047 def get_followers(user, page \\ nil) do
1049 |> get_followers_query(page)
1053 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1054 def get_external_followers(user, page \\ nil) do
1056 |> get_followers_query(page)
1057 |> User.Query.build(%{external: true})
1061 def get_followers_ids(user, page \\ nil) do
1063 |> get_followers_query(page)
1064 |> select([u], u.id)
1068 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1069 def get_friends_query(%User{} = user, nil) do
1070 User.Query.build(%{friends: user, deactivated: false})
1073 def get_friends_query(user, page) do
1075 |> get_friends_query(nil)
1076 |> User.Query.paginate(page, 20)
1079 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1080 def get_friends_query(user), do: get_friends_query(user, nil)
1082 def get_friends(user, page \\ nil) do
1084 |> get_friends_query(page)
1088 def get_friends_ap_ids(user) do
1090 |> get_friends_query(nil)
1091 |> select([u], u.ap_id)
1095 def get_friends_ids(user, page \\ nil) do
1097 |> get_friends_query(page)
1098 |> select([u], u.id)
1102 defdelegate get_follow_requests(user), to: FollowingRelationship
1104 def increase_note_count(%User{} = user) do
1106 |> where(id: ^user.id)
1107 |> update([u], inc: [note_count: 1])
1109 |> Repo.update_all([])
1111 {1, [user]} -> set_cache(user)
1116 def decrease_note_count(%User{} = user) do
1118 |> where(id: ^user.id)
1121 note_count: fragment("greatest(0, note_count - 1)")
1125 |> Repo.update_all([])
1127 {1, [user]} -> set_cache(user)
1132 def update_note_count(%User{} = user, note_count \\ nil) do
1137 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1143 |> cast(%{note_count: note_count}, [:note_count])
1144 |> update_and_set_cache()
1147 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1148 def maybe_fetch_follow_information(user) do
1149 with {:ok, user} <- fetch_follow_information(user) do
1153 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1159 def fetch_follow_information(user) do
1160 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1162 |> follow_information_changeset(info)
1163 |> update_and_set_cache()
1167 defp follow_information_changeset(user, params) do
1174 :hide_followers_count,
1179 @spec update_follower_count(User.t()) :: {:ok, User.t()}
1180 def update_follower_count(%User{} = user) do
1181 if user.local or !Config.get([:instance, :external_user_synchronization]) do
1182 follower_count = FollowingRelationship.follower_count(user)
1185 |> follow_information_changeset(%{follower_count: follower_count})
1186 |> update_and_set_cache
1188 {:ok, maybe_fetch_follow_information(user)}
1192 @spec update_following_count(User.t()) :: {:ok, User.t()}
1193 def update_following_count(%User{local: false} = user) do
1194 if Config.get([:instance, :external_user_synchronization]) do
1195 {:ok, maybe_fetch_follow_information(user)}
1201 def update_following_count(%User{local: true} = user) do
1202 following_count = FollowingRelationship.following_count(user)
1205 |> follow_information_changeset(%{following_count: following_count})
1206 |> update_and_set_cache()
1209 def set_unread_conversation_count(%User{local: true} = user) do
1210 unread_query = Participation.unread_conversation_count_for_user(user)
1213 |> join(:inner, [u], p in subquery(unread_query))
1215 set: [unread_conversation_count: p.count]
1217 |> where([u], u.id == ^user.id)
1219 |> Repo.update_all([])
1221 {1, [user]} -> set_cache(user)
1226 def set_unread_conversation_count(user), do: {:ok, user}
1228 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1230 Participation.unread_conversation_count_for_user(user)
1231 |> where([p], p.conversation_id == ^conversation.id)
1234 |> join(:inner, [u], p in subquery(unread_query))
1236 inc: [unread_conversation_count: 1]
1238 |> where([u], u.id == ^user.id)
1239 |> where([u, p], p.count == 0)
1241 |> Repo.update_all([])
1243 {1, [user]} -> set_cache(user)
1248 def increment_unread_conversation_count(_, user), do: {:ok, user}
1250 @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
1251 def get_users_from_set(ap_ids, opts \\ []) do
1252 local_only = Keyword.get(opts, :local_only, true)
1253 criteria = %{ap_id: ap_ids, deactivated: false}
1254 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1256 User.Query.build(criteria)
1260 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1261 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1264 query = User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1270 @spec mute(User.t(), User.t(), boolean()) ::
1271 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1272 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1273 add_to_mutes(muter, mutee, notifications?)
1276 def unmute(%User{} = muter, %User{} = mutee) do
1277 remove_from_mutes(muter, mutee)
1280 def subscribe(%User{} = subscriber, %User{} = target) do
1281 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
1283 if blocks?(target, subscriber) and deny_follow_blocked do
1284 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1286 # Note: the relationship is inverse: subscriber acts as relationship target
1287 UserRelationship.create_inverse_subscription(target, subscriber)
1291 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1292 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1293 subscribe(subscriber, subscribee)
1297 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1298 # Note: the relationship is inverse: subscriber acts as relationship target
1299 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1302 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1303 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1304 unsubscribe(unsubscriber, user)
1308 def block(%User{} = blocker, %User{} = blocked) do
1309 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1311 if following?(blocker, blocked) do
1312 {:ok, blocker, _} = unfollow(blocker, blocked)
1318 # clear any requested follows as well
1320 case CommonAPI.reject_follow_request(blocked, blocker) do
1321 {:ok, %User{} = updated_blocked} -> updated_blocked
1325 unsubscribe(blocked, blocker)
1327 unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
1328 if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
1330 {:ok, blocker} = update_follower_count(blocker)
1331 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1332 add_to_block(blocker, blocked)
1335 # helper to handle the block given only an actor's AP id
1336 def block(%User{} = blocker, %{ap_id: ap_id}) do
1337 block(blocker, get_cached_by_ap_id(ap_id))
1340 def unblock(%User{} = blocker, %User{} = blocked) do
1341 remove_from_block(blocker, blocked)
1344 # helper to handle the block given only an actor's AP id
1345 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1346 unblock(blocker, get_cached_by_ap_id(ap_id))
1349 def mutes?(nil, _), do: false
1350 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1352 def mutes_user?(%User{} = user, %User{} = target) do
1353 UserRelationship.mute_exists?(user, target)
1356 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1357 def muted_notifications?(nil, _), do: false
1359 def muted_notifications?(%User{} = user, %User{} = target),
1360 do: UserRelationship.notification_mute_exists?(user, target)
1362 def blocks?(nil, _), do: false
1364 def blocks?(%User{} = user, %User{} = target) do
1365 blocks_user?(user, target) ||
1366 (blocks_domain?(user, target) and not User.following?(user, target))
1369 def blocks_user?(%User{} = user, %User{} = target) do
1370 UserRelationship.block_exists?(user, target)
1373 def blocks_user?(_, _), do: false
1375 def blocks_domain?(%User{} = user, %User{} = target) do
1376 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1377 %{host: host} = URI.parse(target.ap_id)
1378 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1381 def blocks_domain?(_, _), do: false
1383 def subscribed_to?(%User{} = user, %User{} = target) do
1384 # Note: the relationship is inverse: subscriber acts as relationship target
1385 UserRelationship.inverse_subscription_exists?(target, user)
1388 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1389 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1390 subscribed_to?(user, target)
1395 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1396 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1398 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1399 def outgoing_relationships_ap_ids(_user, []), do: %{}
1401 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1403 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1404 when is_list(relationship_types) do
1407 |> assoc(:outgoing_relationships)
1408 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1409 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1410 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1411 |> group_by([user_rel, u], user_rel.relationship_type)
1413 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1418 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1422 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1424 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1426 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1428 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1429 when is_list(relationship_types) do
1431 |> assoc(:incoming_relationships)
1432 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1433 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1434 |> maybe_filter_on_ap_id(ap_ids)
1435 |> select([user_rel, u], u.ap_id)
1440 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1441 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1444 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1446 def deactivate_async(user, status \\ true) do
1447 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1450 def deactivate(user, status \\ true)
1452 def deactivate(users, status) when is_list(users) do
1453 Repo.transaction(fn ->
1454 for user <- users, do: deactivate(user, status)
1458 def deactivate(%User{} = user, status) do
1459 with {:ok, user} <- set_activation_status(user, status) do
1462 |> Enum.filter(& &1.local)
1463 |> Enum.each(&set_cache(update_following_count(&1)))
1465 # Only update local user counts, remote will be update during the next pull.
1468 |> Enum.filter(& &1.local)
1469 |> Enum.each(&do_unfollow(user, &1))
1475 def update_notification_settings(%User{} = user, settings) do
1477 |> cast(%{notification_settings: settings}, [])
1478 |> cast_embed(:notification_settings)
1479 |> validate_required([:notification_settings])
1480 |> update_and_set_cache()
1483 def delete(users) when is_list(users) do
1484 for user <- users, do: delete(user)
1487 def delete(%User{} = user) do
1488 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1491 defp delete_and_invalidate_cache(%User{} = user) do
1492 invalidate_cache(user)
1496 defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user)
1498 defp delete_or_deactivate(%User{local: true} = user) do
1499 status = account_status(user)
1501 if status == :confirmation_pending do
1502 delete_and_invalidate_cache(user)
1505 |> change(%{deactivated: true, email: nil})
1506 |> update_and_set_cache()
1510 def perform(:force_password_reset, user), do: force_password_reset(user)
1512 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1513 def perform(:delete, %User{} = user) do
1514 # Remove all relationships
1517 |> Enum.each(fn follower ->
1518 ActivityPub.unfollow(follower, user)
1519 unfollow(follower, user)
1524 |> Enum.each(fn followed ->
1525 ActivityPub.unfollow(user, followed)
1526 unfollow(user, followed)
1529 delete_user_activities(user)
1530 delete_notifications_from_user_activities(user)
1532 delete_outgoing_pending_follow_requests(user)
1534 delete_or_deactivate(user)
1537 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1539 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1540 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1541 when is_list(blocked_identifiers) do
1543 blocked_identifiers,
1544 fn blocked_identifier ->
1545 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1546 {:ok, _block} <- CommonAPI.block(blocker, blocked) do
1550 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1557 def perform(:follow_import, %User{} = follower, followed_identifiers)
1558 when is_list(followed_identifiers) do
1560 followed_identifiers,
1561 fn followed_identifier ->
1562 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1563 {:ok, follower} <- maybe_direct_follow(follower, followed),
1564 {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do
1568 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1575 @spec external_users_query() :: Ecto.Query.t()
1576 def external_users_query do
1584 @spec external_users(keyword()) :: [User.t()]
1585 def external_users(opts \\ []) do
1587 external_users_query()
1588 |> select([u], struct(u, [:id, :ap_id]))
1592 do: where(query, [u], u.id > ^opts[:max_id]),
1597 do: limit(query, ^opts[:limit]),
1603 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1604 BackgroundWorker.enqueue("blocks_import", %{
1605 "blocker_id" => blocker.id,
1606 "blocked_identifiers" => blocked_identifiers
1610 def follow_import(%User{} = follower, followed_identifiers)
1611 when is_list(followed_identifiers) do
1612 BackgroundWorker.enqueue("follow_import", %{
1613 "follower_id" => follower.id,
1614 "followed_identifiers" => followed_identifiers
1618 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1620 |> join(:inner, [n], activity in assoc(n, :activity))
1621 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1622 |> Repo.delete_all()
1625 def delete_user_activities(%User{ap_id: ap_id} = user) do
1627 |> Activity.Queries.by_actor()
1628 |> RepoStreamer.chunk_stream(50)
1629 |> Stream.each(fn activities ->
1630 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1635 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1636 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1637 {:ok, delete_data, _} <- Builder.delete(user, object) do
1638 Pipeline.common_pipeline(delete_data, local: user.local)
1640 {:find_object, nil} ->
1641 # We have the create activity, but not the object, it was probably pruned.
1642 # Insert a tombstone and try again
1643 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1644 {:ok, _tombstone} <- Object.create(tombstone_data) do
1645 delete_activity(activity, user)
1649 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1650 Logger.error("Error: #{inspect(e)}")
1654 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1655 when type in ["Like", "Announce"] do
1656 {:ok, undo, _} = Builder.undo(user, activity)
1657 Pipeline.common_pipeline(undo, local: user.local)
1660 defp delete_activity(_activity, _user), do: "Doing nothing"
1662 defp delete_outgoing_pending_follow_requests(user) do
1664 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1665 |> Repo.delete_all()
1668 def html_filter_policy(%User{no_rich_text: true}) do
1669 Pleroma.HTML.Scrubber.TwitterText
1672 def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
1674 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1676 def get_or_fetch_by_ap_id(ap_id) do
1677 cached_user = get_cached_by_ap_id(ap_id)
1679 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
1681 case {cached_user, maybe_fetched_user} do
1682 {_, {:ok, %User{} = user}} ->
1685 {%User{} = user, _} ->
1689 {:error, :not_found}
1694 Creates an internal service actor by URI if missing.
1695 Optionally takes nickname for addressing.
1697 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1698 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1700 case get_cached_by_ap_id(uri) do
1702 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1703 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1707 %User{invisible: false} = user ->
1717 @spec set_invisible(User.t()) :: {:ok, User.t()}
1718 defp set_invisible(user) do
1720 |> change(%{invisible: true})
1721 |> update_and_set_cache()
1724 @spec create_service_actor(String.t(), String.t()) ::
1725 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1726 defp create_service_actor(uri, nickname) do
1732 follower_address: uri <> "/followers"
1735 |> unique_constraint(:nickname)
1740 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1743 |> :public_key.pem_decode()
1745 |> :public_key.pem_entry_decode()
1750 def public_key(_), do: {:error, "key not found"}
1752 def get_public_key_for_ap_id(ap_id) do
1753 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1754 {:ok, public_key} <- public_key(user) do
1761 def ap_enabled?(%User{local: true}), do: true
1762 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1763 def ap_enabled?(_), do: false
1765 @doc "Gets or fetch a user by uri or nickname."
1766 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1767 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1768 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1770 # wait a period of time and return newest version of the User structs
1771 # this is because we have synchronous follow APIs and need to simulate them
1772 # with an async handshake
1773 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1774 with %User{} = a <- get_cached_by_id(a.id),
1775 %User{} = b <- get_cached_by_id(b.id) do
1782 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1783 with :ok <- :timer.sleep(timeout),
1784 %User{} = a <- get_cached_by_id(a.id),
1785 %User{} = b <- get_cached_by_id(b.id) do
1792 def parse_bio(bio) when is_binary(bio) and bio != "" do
1794 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1798 def parse_bio(_), do: ""
1800 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1801 # TODO: get profile URLs other than user.ap_id
1802 profile_urls = [user.ap_id]
1805 |> CommonUtils.format_input("text/plain",
1806 mentions_format: :full,
1807 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1812 def parse_bio(_, _), do: ""
1814 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1815 Repo.transaction(fn ->
1816 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1820 def tag(nickname, tags) when is_binary(nickname),
1821 do: tag(get_by_nickname(nickname), tags)
1823 def tag(%User{} = user, tags),
1824 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1826 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1827 Repo.transaction(fn ->
1828 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1832 def untag(nickname, tags) when is_binary(nickname),
1833 do: untag(get_by_nickname(nickname), tags)
1835 def untag(%User{} = user, tags),
1836 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1838 defp update_tags(%User{} = user, new_tags) do
1839 {:ok, updated_user} =
1841 |> change(%{tags: new_tags})
1842 |> update_and_set_cache()
1847 defp normalize_tags(tags) do
1850 |> Enum.map(&String.downcase/1)
1853 defp local_nickname_regex do
1854 if Config.get([:instance, :extended_nickname_format]) do
1855 @extended_local_nickname_regex
1857 @strict_local_nickname_regex
1861 def local_nickname(nickname_or_mention) do
1864 |> String.split("@")
1868 def full_nickname(nickname_or_mention),
1869 do: String.trim_leading(nickname_or_mention, "@")
1871 def error_user(ap_id) do
1875 nickname: "erroruser@example.com",
1876 inserted_at: NaiveDateTime.utc_now()
1880 @spec all_superusers() :: [User.t()]
1881 def all_superusers do
1882 User.Query.build(%{super_users: true, local: true, deactivated: false})
1886 def muting_reblogs?(%User{} = user, %User{} = target) do
1887 UserRelationship.reblog_mute_exists?(user, target)
1890 def showing_reblogs?(%User{} = user, %User{} = target) do
1891 not muting_reblogs?(user, target)
1895 The function returns a query to get users with no activity for given interval of days.
1896 Inactive users are those who didn't read any notification, or had any activity where
1897 the user is the activity's actor, during `inactivity_threshold` days.
1898 Deactivated users will not appear in this list.
1902 iex> Pleroma.User.list_inactive_users()
1905 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1906 def list_inactive_users_query(inactivity_threshold \\ 7) do
1907 negative_inactivity_threshold = -inactivity_threshold
1908 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1909 # Subqueries are not supported in `where` clauses, join gets too complicated.
1910 has_read_notifications =
1911 from(n in Pleroma.Notification,
1912 where: n.seen == true,
1914 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1917 |> Pleroma.Repo.all()
1919 from(u in Pleroma.User,
1920 left_join: a in Pleroma.Activity,
1921 on: u.ap_id == a.actor,
1922 where: not is_nil(u.nickname),
1923 where: u.deactivated != ^true,
1924 where: u.id not in ^has_read_notifications,
1927 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1928 is_nil(max(a.inserted_at))
1933 Enable or disable email notifications for user
1937 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1938 Pleroma.User{email_notifications: %{"digest" => true}}
1940 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1941 Pleroma.User{email_notifications: %{"digest" => false}}
1943 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1944 {:ok, t()} | {:error, Ecto.Changeset.t()}
1945 def switch_email_notifications(user, type, status) do
1946 User.update_email_notifications(user, %{type => status})
1950 Set `last_digest_emailed_at` value for the user to current time
1952 @spec touch_last_digest_emailed_at(t()) :: t()
1953 def touch_last_digest_emailed_at(user) do
1954 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1956 {:ok, updated_user} =
1958 |> change(%{last_digest_emailed_at: now})
1959 |> update_and_set_cache()
1964 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1965 def toggle_confirmation(%User{} = user) do
1967 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1968 |> update_and_set_cache()
1971 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1972 def toggle_confirmation(users) do
1973 Enum.map(users, &toggle_confirmation/1)
1976 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1980 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1981 # use instance-default
1982 config = Config.get([:assets, :mascots])
1983 default_mascot = Config.get([:assets, :default_mascot])
1984 mascot = Keyword.get(config, default_mascot)
1987 "id" => "default-mascot",
1988 "url" => mascot[:url],
1989 "preview_url" => mascot[:url],
1991 "mime_type" => mascot[:mime_type]
1996 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1998 def ensure_keys_present(%User{} = user) do
1999 with {:ok, pem} <- Keys.generate_rsa_pem() do
2001 |> cast(%{keys: pem}, [:keys])
2002 |> validate_required([:keys])
2003 |> update_and_set_cache()
2007 def get_ap_ids_by_nicknames(nicknames) do
2009 where: u.nickname in ^nicknames,
2015 defdelegate search(query, opts \\ []), to: User.Search
2017 defp put_password_hash(
2018 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2020 change(changeset, password_hash: Pbkdf2.hash_pwd_salt(password))
2023 defp put_password_hash(changeset), do: changeset
2025 def is_internal_user?(%User{nickname: nil}), do: true
2026 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2027 def is_internal_user?(_), do: false
2029 # A hack because user delete activities have a fake id for whatever reason
2030 # TODO: Get rid of this
2031 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2033 def get_delivered_users_by_object_id(object_id) do
2035 inner_join: delivery in assoc(u, :deliveries),
2036 where: delivery.object_id == ^object_id
2041 def change_email(user, email) do
2043 |> cast(%{email: email}, [:email])
2044 |> validate_required([:email])
2045 |> unique_constraint(:email)
2046 |> validate_format(:email, @email_regex)
2047 |> update_and_set_cache()
2050 # Internal function; public one is `deactivate/2`
2051 defp set_activation_status(user, deactivated) do
2053 |> cast(%{deactivated: deactivated}, [:deactivated])
2054 |> update_and_set_cache()
2057 def update_banner(user, banner) do
2059 |> cast(%{banner: banner}, [:banner])
2060 |> update_and_set_cache()
2063 def update_background(user, background) do
2065 |> cast(%{background: background}, [:background])
2066 |> update_and_set_cache()
2069 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
2072 moderator: is_moderator
2076 def validate_fields(changeset, remote? \\ false) do
2077 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2078 limit = Config.get([:instance, limit_name], 0)
2081 |> validate_length(:fields, max: limit)
2082 |> validate_change(:fields, fn :fields, fields ->
2083 if Enum.all?(fields, &valid_field?/1) do
2091 defp valid_field?(%{"name" => name, "value" => value}) do
2092 name_limit = Config.get([:instance, :account_field_name_length], 255)
2093 value_limit = Config.get([:instance, :account_field_value_length], 255)
2095 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2096 String.length(value) <= value_limit
2099 defp valid_field?(_), do: false
2101 defp truncate_field(%{"name" => name, "value" => value}) do
2103 String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
2106 String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
2108 %{"name" => name, "value" => value}
2111 def admin_api_update(user, params) do
2118 |> update_and_set_cache()
2121 @doc "Signs user out of all applications"
2122 def global_sign_out(user) do
2123 OAuth.Authorization.delete_user_authorizations(user)
2124 OAuth.Token.delete_user_tokens(user)
2127 def mascot_update(user, url) do
2129 |> cast(%{mascot: url}, [:mascot])
2130 |> validate_required([:mascot])
2131 |> update_and_set_cache()
2134 def mastodon_settings_update(user, settings) do
2136 |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
2137 |> validate_required([:mastofe_settings])
2138 |> update_and_set_cache()
2141 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2142 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2144 if need_confirmation? do
2146 confirmation_pending: true,
2147 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2151 confirmation_pending: false,
2152 confirmation_token: nil
2156 cast(user, params, [:confirmation_pending, :confirmation_token])
2159 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2160 if id not in user.pinned_activities do
2161 max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
2162 params = %{pinned_activities: user.pinned_activities ++ [id]}
2165 |> cast(params, [:pinned_activities])
2166 |> validate_length(:pinned_activities,
2167 max: max_pinned_statuses,
2168 message: "You have already pinned the maximum number of statuses"
2173 |> update_and_set_cache()
2176 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2177 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2180 |> cast(params, [:pinned_activities])
2181 |> update_and_set_cache()
2184 def update_email_notifications(user, settings) do
2185 email_notifications =
2186 user.email_notifications
2187 |> Map.merge(settings)
2188 |> Map.take(["digest"])
2190 params = %{email_notifications: email_notifications}
2191 fields = [:email_notifications]
2194 |> cast(params, fields)
2195 |> validate_required(fields)
2196 |> update_and_set_cache()
2199 defp set_domain_blocks(user, domain_blocks) do
2200 params = %{domain_blocks: domain_blocks}
2203 |> cast(params, [:domain_blocks])
2204 |> validate_required([:domain_blocks])
2205 |> update_and_set_cache()
2208 def block_domain(user, domain_blocked) do
2209 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2212 def unblock_domain(user, domain_blocked) do
2213 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2216 @spec add_to_block(User.t(), User.t()) ::
2217 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2218 defp add_to_block(%User{} = user, %User{} = blocked) do
2219 UserRelationship.create_block(user, blocked)
2222 @spec add_to_block(User.t(), User.t()) ::
2223 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2224 defp remove_from_block(%User{} = user, %User{} = blocked) do
2225 UserRelationship.delete_block(user, blocked)
2228 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2229 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2230 {:ok, user_notification_mute} <-
2231 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2233 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2237 defp remove_from_mutes(user, %User{} = muted_user) do
2238 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2239 {:ok, user_notification_mute} <-
2240 UserRelationship.delete_notification_mute(user, muted_user) do
2241 {:ok, [user_mute, user_notification_mute]}
2245 def set_invisible(user, invisible) do
2246 params = %{invisible: invisible}
2249 |> cast(params, [:invisible])
2250 |> validate_required([:invisible])
2251 |> update_and_set_cache()
2254 def sanitize_html(%User{} = user) do
2255 sanitize_html(user, nil)
2258 # User data that mastodon isn't filtering (treated as plaintext):
2261 def sanitize_html(%User{} = user, filter) do
2263 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2266 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2271 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2272 |> Map.put(:fields, fields)
2275 def add_aliases(%User{} = user, aliases) when is_list(aliases) do
2277 (user.ap_aliases ++ aliases)
2282 |> change(%{ap_aliases: alias_set})
2283 |> validate_ap_aliases()
2287 def delete_aliases(%User{} = user, aliases) when is_list(aliases) do
2291 |> MapSet.difference(MapSet.new(aliases))
2295 |> change(%{ap_aliases: alias_set})
2296 |> validate_ap_aliases()
2300 defp validate_ap_aliases(changeset) do
2301 validate_change(changeset, :ap_aliases, fn :ap_aliases, ap_aliases ->
2302 case Enum.all?(ap_aliases, fn a -> Regex.match?(@url_regex, a) end) do
2304 false -> [ap_aliases: "Invalid ap_id format. Must be a URL."]