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 ::
48 | :password_reset_pending
49 | :confirmation_pending
51 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
53 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
54 @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])?)*$/
56 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
57 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
59 # AP ID user relationships (blocks, mutes etc.)
60 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
61 @user_relationships_config [
63 blocker_blocks: :blocked_users,
64 blockee_blocks: :blocker_users
67 muter_mutes: :muted_users,
68 mutee_mutes: :muter_users
71 reblog_muter_mutes: :reblog_muted_users,
72 reblog_mutee_mutes: :reblog_muter_users
75 notification_muter_mutes: :notification_muted_users,
76 notification_mutee_mutes: :notification_muter_users
78 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
79 inverse_subscription: [
80 subscribee_subscriptions: :subscriber_users,
81 subscriber_subscriptions: :subscribee_users
87 field(:raw_bio, :string)
88 field(:email, :string)
90 field(:nickname, :string)
91 field(:password_hash, :string)
92 field(:password, :string, virtual: true)
93 field(:password_confirmation, :string, virtual: true)
95 field(:public_key, :string)
96 field(:ap_id, :string)
97 field(:avatar, :map, default: %{})
98 field(:local, :boolean, default: true)
99 field(:follower_address, :string)
100 field(:following_address, :string)
101 field(:search_rank, :float, virtual: true)
102 field(:search_type, :integer, virtual: true)
103 field(:tags, {:array, :string}, default: [])
104 field(:last_refreshed_at, :naive_datetime_usec)
105 field(:last_digest_emailed_at, :naive_datetime)
106 field(:banner, :map, default: %{})
107 field(:background, :map, default: %{})
108 field(:note_count, :integer, default: 0)
109 field(:follower_count, :integer, default: 0)
110 field(:following_count, :integer, default: 0)
111 field(:locked, :boolean, default: false)
112 field(:confirmation_pending, :boolean, default: false)
113 field(:password_reset_pending, :boolean, default: false)
114 field(:approval_pending, :boolean, default: false)
115 field(:registration_reason, :string, default: nil)
116 field(:confirmation_token, :string, default: nil)
117 field(:default_scope, :string, default: "public")
118 field(:domain_blocks, {:array, :string}, default: [])
119 field(:deactivated, :boolean, default: false)
120 field(:no_rich_text, :boolean, default: false)
121 field(:ap_enabled, :boolean, default: false)
122 field(:is_moderator, :boolean, default: false)
123 field(:is_admin, :boolean, default: false)
124 field(:show_role, :boolean, default: true)
125 field(:mastofe_settings, :map, default: nil)
126 field(:uri, ObjectValidators.Uri, default: nil)
127 field(:hide_followers_count, :boolean, default: false)
128 field(:hide_follows_count, :boolean, default: false)
129 field(:hide_followers, :boolean, default: false)
130 field(:hide_follows, :boolean, default: false)
131 field(:hide_favorites, :boolean, default: true)
132 field(:unread_conversation_count, :integer, default: 0)
133 field(:pinned_activities, {:array, :string}, default: [])
134 field(:email_notifications, :map, default: %{"digest" => false})
135 field(:mascot, :map, default: nil)
136 field(:emoji, :map, default: %{})
137 field(:pleroma_settings_store, :map, default: %{})
138 field(:fields, {:array, :map}, default: [])
139 field(:raw_fields, {:array, :map}, default: [])
140 field(:discoverable, :boolean, default: false)
141 field(:invisible, :boolean, default: false)
142 field(:allow_following_move, :boolean, default: true)
143 field(:skip_thread_containment, :boolean, default: false)
144 field(:actor_type, :string, default: "Person")
145 field(:also_known_as, {:array, :string}, default: [])
146 field(:inbox, :string)
147 field(:shared_inbox, :string)
148 field(:accepts_chat_messages, :boolean, default: nil)
151 :notification_settings,
152 Pleroma.User.NotificationSetting,
156 has_many(:notifications, Notification)
157 has_many(:registrations, Registration)
158 has_many(:deliveries, Delivery)
160 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
161 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
163 for {relationship_type,
165 {outgoing_relation, outgoing_relation_target},
166 {incoming_relation, incoming_relation_source}
167 ]} <- @user_relationships_config do
168 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
169 # :notification_muter_mutes, :subscribee_subscriptions
170 has_many(outgoing_relation, UserRelationship,
171 foreign_key: :source_id,
172 where: [relationship_type: relationship_type]
175 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
176 # :notification_mutee_mutes, :subscriber_subscriptions
177 has_many(incoming_relation, UserRelationship,
178 foreign_key: :target_id,
179 where: [relationship_type: relationship_type]
182 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
183 # :notification_muted_users, :subscriber_users
184 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
186 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
187 # :notification_muter_users, :subscribee_users
188 has_many(incoming_relation_source, through: [incoming_relation, :source])
191 # `:blocks` is deprecated (replaced with `blocked_users` relation)
192 field(:blocks, {:array, :string}, default: [])
193 # `:mutes` is deprecated (replaced with `muted_users` relation)
194 field(:mutes, {:array, :string}, default: [])
195 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
196 field(:muted_reblogs, {:array, :string}, default: [])
197 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
198 field(:muted_notifications, {:array, :string}, default: [])
199 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
200 field(:subscribers, {:array, :string}, default: [])
203 :multi_factor_authentication_settings,
211 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
212 @user_relationships_config do
213 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
214 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
215 # `def subscriber_users/2`
216 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
217 target_users_query = assoc(user, unquote(outgoing_relation_target))
219 if restrict_deactivated? do
220 restrict_deactivated(target_users_query)
226 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
227 # `def notification_muted_users/2`, `def subscriber_users/2`
228 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
230 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
232 restrict_deactivated?
237 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
238 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
239 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
241 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
243 restrict_deactivated?
245 |> select([u], u.ap_id)
251 Dumps Flake Id to SQL-compatible format (16-byte UUID).
252 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
254 def binary_id(source_id) when is_binary(source_id) do
255 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
262 def binary_id(source_ids) when is_list(source_ids) do
263 Enum.map(source_ids, &binary_id/1)
266 def binary_id(%User{} = user), do: binary_id(user.id)
268 @doc "Returns status account"
269 @spec account_status(User.t()) :: account_status()
270 def account_status(%User{deactivated: true}), do: :deactivated
271 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
272 def account_status(%User{approval_pending: true}), do: :approval_pending
274 def account_status(%User{confirmation_pending: true}) do
275 if Config.get([:instance, :account_activation_required]) do
276 :confirmation_pending
282 def account_status(%User{}), do: :active
284 @spec visible_for(User.t(), User.t() | nil) ::
287 | :restricted_unauthenticated
289 | :confirmation_pending
290 def visible_for(user, for_user \\ nil)
292 def visible_for(%User{invisible: true}, _), do: :invisible
294 def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
296 def visible_for(%User{} = user, nil) do
297 if restrict_unauthenticated?(user) do
298 :restrict_unauthenticated
300 visible_account_status(user)
304 def visible_for(%User{} = user, for_user) do
305 if superuser?(for_user) do
308 visible_account_status(user)
312 def visible_for(_, _), do: :invisible
314 defp restrict_unauthenticated?(%User{local: local}) do
315 config_key = if local, do: :local, else: :remote
317 Config.get([:restrict_unauthenticated, :profiles, config_key], false)
320 defp visible_account_status(user) do
321 status = account_status(user)
323 if status in [:active, :password_reset_pending] do
330 @spec superuser?(User.t()) :: boolean()
331 def superuser?(%User{local: true, is_admin: true}), do: true
332 def superuser?(%User{local: true, is_moderator: true}), do: true
333 def superuser?(_), do: false
335 @spec invisible?(User.t()) :: boolean()
336 def invisible?(%User{invisible: true}), do: true
337 def invisible?(_), do: false
339 def avatar_url(user, options \\ []) do
341 %{"url" => [%{"href" => href} | _]} ->
345 unless options[:no_default] do
346 Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png")
351 def banner_url(user, options \\ []) do
353 %{"url" => [%{"href" => href} | _]} -> href
354 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
358 # Should probably be renamed or removed
359 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
361 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
362 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
364 @spec ap_following(User.t()) :: String.t()
365 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
366 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
368 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
369 def restrict_deactivated(query) do
370 from(u in query, where: u.deactivated != ^true)
373 defdelegate following_count(user), to: FollowingRelationship
375 defp truncate_fields_param(params) do
376 if Map.has_key?(params, :fields) do
377 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
383 defp truncate_if_exists(params, key, max_length) do
384 if Map.has_key?(params, key) and is_binary(params[key]) do
385 {value, _chopped} = String.split_at(params[key], max_length)
386 Map.put(params, key, value)
392 defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
394 defp fix_follower_address(%{nickname: nickname} = params),
395 do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
397 defp fix_follower_address(params), do: params
399 def remote_user_changeset(struct \\ %User{local: false}, params) do
400 bio_limit = Config.get([:instance, :user_bio_length], 5000)
401 name_limit = Config.get([:instance, :user_name_length], 100)
404 case params[:name] do
405 name when is_binary(name) and byte_size(name) > 0 -> name
406 _ -> params[:nickname]
411 |> Map.put(:name, name)
412 |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
413 |> truncate_if_exists(:name, name_limit)
414 |> truncate_if_exists(:bio, bio_limit)
415 |> truncate_fields_param()
416 |> fix_follower_address()
440 :hide_followers_count,
449 :accepts_chat_messages
452 |> validate_required([:name, :ap_id])
453 |> unique_constraint(:nickname)
454 |> validate_format(:nickname, @email_regex)
455 |> validate_length(:bio, max: bio_limit)
456 |> validate_length(:name, max: name_limit)
457 |> validate_fields(true)
460 def update_changeset(struct, params \\ %{}) do
461 bio_limit = Config.get([:instance, :user_bio_length], 5000)
462 name_limit = Config.get([:instance, :user_name_length], 100)
482 :hide_followers_count,
485 :allow_following_move,
488 :skip_thread_containment,
491 :pleroma_settings_store,
495 :accepts_chat_messages
498 |> unique_constraint(:nickname)
499 |> validate_format(:nickname, local_nickname_regex())
500 |> validate_length(:bio, max: bio_limit)
501 |> validate_length(:name, min: 1, max: name_limit)
502 |> validate_inclusion(:actor_type, ["Person", "Service"])
505 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
506 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
507 |> put_change_if_present(:banner, &put_upload(&1, :banner))
508 |> put_change_if_present(:background, &put_upload(&1, :background))
509 |> put_change_if_present(
510 :pleroma_settings_store,
511 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
513 |> validate_fields(false)
516 defp put_fields(changeset) do
517 if raw_fields = get_change(changeset, :raw_fields) do
520 |> Enum.filter(fn %{"name" => n} -> n != "" end)
524 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
527 |> put_change(:raw_fields, raw_fields)
528 |> put_change(:fields, fields)
534 defp parse_fields(value) do
536 |> Formatter.linkify(mentions_format: :full)
540 defp put_emoji(changeset) do
541 emojified_fields = [:bio, :name, :raw_fields]
543 if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
544 bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
545 name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
547 emoji = Map.merge(bio, name)
551 |> get_field(:raw_fields)
552 |> Enum.reduce(emoji, fn x, acc ->
553 Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
556 put_change(changeset, :emoji, emoji)
562 defp put_change_if_present(changeset, map_field, value_function) do
563 with {:ok, value} <- fetch_change(changeset, map_field),
564 {:ok, new_value} <- value_function.(value) do
565 put_change(changeset, map_field, new_value)
571 defp put_upload(value, type) do
572 with %Plug.Upload{} <- value,
573 {:ok, object} <- ActivityPub.upload(value, type: type) do
578 def update_as_admin_changeset(struct, params) do
580 |> update_changeset(params)
581 |> cast(params, [:email])
582 |> delete_change(:also_known_as)
583 |> unique_constraint(:email)
584 |> validate_format(:email, @email_regex)
585 |> validate_inclusion(:actor_type, ["Person", "Service"])
588 @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
589 def update_as_admin(user, params) do
590 params = Map.put(params, "password_confirmation", params["password"])
591 changeset = update_as_admin_changeset(user, params)
593 if params["password"] do
594 reset_password(user, changeset, params)
596 User.update_and_set_cache(changeset)
600 def password_update_changeset(struct, params) do
602 |> cast(params, [:password, :password_confirmation])
603 |> validate_required([:password, :password_confirmation])
604 |> validate_confirmation(:password)
605 |> put_password_hash()
606 |> put_change(:password_reset_pending, false)
609 @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
610 def reset_password(%User{} = user, params) do
611 reset_password(user, user, params)
614 def reset_password(%User{id: user_id} = user, struct, params) do
617 |> Multi.update(:user, password_update_changeset(struct, params))
618 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
619 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
621 case Repo.transaction(multi) do
622 {:ok, %{user: user} = _} -> set_cache(user)
623 {:error, _, changeset, _} -> {:error, changeset}
627 def update_password_reset_pending(user, value) do
630 |> put_change(:password_reset_pending, value)
631 |> update_and_set_cache()
634 def force_password_reset_async(user) do
635 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
638 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
639 def force_password_reset(user), do: update_password_reset_pending(user, true)
641 def register_changeset(struct, params \\ %{}, opts \\ []) do
642 bio_limit = Config.get([:instance, :user_bio_length], 5000)
643 name_limit = Config.get([:instance, :user_name_length], 100)
644 reason_limit = Config.get([:instance, :registration_reason_length], 500)
645 params = Map.put_new(params, :accepts_chat_messages, true)
648 if is_nil(opts[:need_confirmation]) do
649 Config.get([:instance, :account_activation_required])
651 opts[:need_confirmation]
655 if is_nil(opts[:need_approval]) do
656 Config.get([:instance, :account_approval_required])
662 |> confirmation_changeset(need_confirmation: need_confirmation?)
663 |> approval_changeset(need_approval: need_approval?)
671 :password_confirmation,
673 :accepts_chat_messages,
676 |> validate_required([:name, :nickname, :password, :password_confirmation])
677 |> validate_confirmation(:password)
678 |> unique_constraint(:email)
679 |> validate_format(:email, @email_regex)
680 |> validate_change(:email, fn :email, email ->
682 Config.get([User, :email_blacklist])
683 |> Enum.all?(fn blacklisted_domain ->
684 !String.ends_with?(email, ["@" <> blacklisted_domain, "." <> blacklisted_domain])
687 if valid?, do: [], else: [email: "Email domain is blacklisted"]
689 |> unique_constraint(:nickname)
690 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
691 |> validate_format(:nickname, local_nickname_regex())
692 |> validate_length(:bio, max: bio_limit)
693 |> validate_length(:name, min: 1, max: name_limit)
694 |> validate_length(:registration_reason, max: reason_limit)
695 |> maybe_validate_required_email(opts[:external])
698 |> unique_constraint(:ap_id)
699 |> put_following_and_follower_address()
702 def maybe_validate_required_email(changeset, true), do: changeset
704 def maybe_validate_required_email(changeset, _) do
705 if Config.get([:instance, :account_activation_required]) do
706 validate_required(changeset, [:email])
712 defp put_ap_id(changeset) do
713 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
714 put_change(changeset, :ap_id, ap_id)
717 defp put_following_and_follower_address(changeset) do
718 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
721 |> put_change(:follower_address, followers)
724 defp autofollow_users(user) do
725 candidates = Config.get([:instance, :autofollowed_nicknames])
728 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
731 follow_all(user, autofollowed_users)
734 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
735 def register(%Ecto.Changeset{} = changeset) do
736 with {:ok, user} <- Repo.insert(changeset) do
737 post_register_action(user)
741 def post_register_action(%User{} = user) do
742 with {:ok, user} <- autofollow_users(user),
743 {:ok, user} <- set_cache(user),
744 {:ok, _} <- send_welcome_email(user),
745 {:ok, _} <- send_welcome_message(user),
746 {:ok, _} <- try_send_confirmation_email(user) do
751 def send_welcome_message(user) do
752 if User.WelcomeMessage.enabled?() do
753 User.WelcomeMessage.post_message(user)
760 def send_welcome_email(%User{email: email} = user) when is_binary(email) do
761 if User.WelcomeEmail.enabled?() do
762 User.WelcomeEmail.send_email(user)
769 def send_welcome_email(_), do: {:ok, :noop}
771 @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
772 def try_send_confirmation_email(%User{confirmation_pending: true} = user) do
773 if Config.get([:instance, :account_activation_required]) do
774 send_confirmation_email(user)
781 def try_send_confirmation_email(_), do: {:ok, :noop}
783 @spec send_confirmation_email(Uset.t()) :: User.t()
784 def send_confirmation_email(%User{} = user) do
786 |> Pleroma.Emails.UserEmail.account_confirmation_email()
787 |> Pleroma.Emails.Mailer.deliver_async()
792 def needs_update?(%User{local: true}), do: false
794 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
796 def needs_update?(%User{local: false} = user) do
797 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
800 def needs_update?(_), do: true
802 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
804 # "Locked" (self-locked) users demand explicit authorization of follow requests
805 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
806 follow(follower, followed, :follow_pending)
809 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
810 follow(follower, followed)
813 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
814 if not ap_enabled?(followed) do
815 follow(follower, followed)
821 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
822 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
823 def follow_all(follower, followeds) do
825 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
826 |> Enum.each(&follow(follower, &1, :follow_accept))
831 defdelegate following(user), to: FollowingRelationship
833 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
834 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
837 followed.deactivated ->
838 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
840 deny_follow_blocked and blocks?(followed, follower) ->
841 {:error, "Could not follow user: #{followed.nickname} blocked you."}
844 FollowingRelationship.follow(follower, followed, state)
846 {:ok, _} = update_follower_count(followed)
849 |> update_following_count()
853 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
854 {:error, "Not subscribed!"}
857 @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
858 def unfollow(%User{} = follower, %User{} = followed) do
859 case do_unfollow(follower, followed) do
860 {:ok, follower, followed} ->
861 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
868 @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
869 defp do_unfollow(%User{} = follower, %User{} = followed) do
870 case get_follow_state(follower, followed) do
871 state when state in [:follow_pending, :follow_accept] ->
872 FollowingRelationship.unfollow(follower, followed)
873 {:ok, followed} = update_follower_count(followed)
877 |> update_following_count()
879 {:ok, follower, followed}
882 {:error, "Not subscribed!"}
886 defdelegate following?(follower, followed), to: FollowingRelationship
888 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
889 def get_follow_state(%User{} = follower, %User{} = following) do
890 following_relationship = FollowingRelationship.get(follower, following)
891 get_follow_state(follower, following, following_relationship)
894 def get_follow_state(
897 following_relationship
899 case {following_relationship, following.local} do
901 case Utils.fetch_latest_follow(follower, following) do
902 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
903 FollowingRelationship.state_to_enum(state)
909 {%{state: state}, _} ->
917 def locked?(%User{} = user) do
922 Repo.get_by(User, id: id)
925 def get_by_ap_id(ap_id) do
926 Repo.get_by(User, ap_id: ap_id)
929 def get_all_by_ap_id(ap_ids) do
930 from(u in __MODULE__,
931 where: u.ap_id in ^ap_ids
936 def get_all_by_ids(ids) do
937 from(u in __MODULE__, where: u.id in ^ids)
941 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
942 # of the ap_id and the domain and tries to get that user
943 def get_by_guessed_nickname(ap_id) do
944 domain = URI.parse(ap_id).host
945 name = List.last(String.split(ap_id, "/"))
946 nickname = "#{name}@#{domain}"
948 get_cached_by_nickname(nickname)
951 def set_cache({:ok, user}), do: set_cache(user)
952 def set_cache({:error, err}), do: {:error, err}
954 def set_cache(%User{} = user) do
955 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
956 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
957 Cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
961 def update_and_set_cache(struct, params) do
963 |> update_changeset(params)
964 |> update_and_set_cache()
967 def update_and_set_cache(changeset) do
968 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
973 def get_user_friends_ap_ids(user) do
974 from(u in User.get_friends_query(user), select: u.ap_id)
978 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
979 def get_cached_user_friends_ap_ids(user) do
980 Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
981 get_user_friends_ap_ids(user)
985 def invalidate_cache(user) do
986 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
987 Cachex.del(:user_cache, "nickname:#{user.nickname}")
988 Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
991 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
992 def get_cached_by_ap_id(ap_id) do
993 key = "ap_id:#{ap_id}"
995 with {:ok, nil} <- Cachex.get(:user_cache, key),
996 user when not is_nil(user) <- get_by_ap_id(ap_id),
997 {:ok, true} <- Cachex.put(:user_cache, key, user) do
1005 def get_cached_by_id(id) do
1009 Cachex.fetch!(:user_cache, key, fn _ ->
1010 user = get_by_id(id)
1013 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1014 {:commit, user.ap_id}
1020 get_cached_by_ap_id(ap_id)
1023 def get_cached_by_nickname(nickname) do
1024 key = "nickname:#{nickname}"
1026 Cachex.fetch!(:user_cache, key, fn ->
1027 case get_or_fetch_by_nickname(nickname) do
1028 {:ok, user} -> {:commit, user}
1029 {:error, _error} -> {:ignore, nil}
1034 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
1035 restrict_to_local = Config.get([:instance, :limit_to_local_content])
1038 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
1039 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
1041 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
1042 get_cached_by_nickname(nickname_or_id)
1044 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
1045 get_cached_by_nickname(nickname_or_id)
1052 @spec get_by_nickname(String.t()) :: User.t() | nil
1053 def get_by_nickname(nickname) do
1054 Repo.get_by(User, nickname: nickname) ||
1055 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
1056 Repo.get_by(User, nickname: local_nickname(nickname))
1060 def get_by_email(email), do: Repo.get_by(User, email: email)
1062 def get_by_nickname_or_email(nickname_or_email) do
1063 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
1066 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
1068 def get_or_fetch_by_nickname(nickname) do
1069 with %User{} = user <- get_by_nickname(nickname) do
1073 with [_nick, _domain] <- String.split(nickname, "@"),
1074 {:ok, user} <- fetch_by_nickname(nickname) do
1077 _e -> {:error, "not found " <> nickname}
1082 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1083 def get_followers_query(%User{} = user, nil) do
1084 User.Query.build(%{followers: user, deactivated: false})
1087 def get_followers_query(user, page) do
1089 |> get_followers_query(nil)
1090 |> User.Query.paginate(page, 20)
1093 @spec get_followers_query(User.t()) :: Ecto.Query.t()
1094 def get_followers_query(user), do: get_followers_query(user, nil)
1096 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1097 def get_followers(user, page \\ nil) do
1099 |> get_followers_query(page)
1103 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1104 def get_external_followers(user, page \\ nil) do
1106 |> get_followers_query(page)
1107 |> User.Query.build(%{external: true})
1111 def get_followers_ids(user, page \\ nil) do
1113 |> get_followers_query(page)
1114 |> select([u], u.id)
1118 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1119 def get_friends_query(%User{} = user, nil) do
1120 User.Query.build(%{friends: user, deactivated: false})
1123 def get_friends_query(user, page) do
1125 |> get_friends_query(nil)
1126 |> User.Query.paginate(page, 20)
1129 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1130 def get_friends_query(user), do: get_friends_query(user, nil)
1132 def get_friends(user, page \\ nil) do
1134 |> get_friends_query(page)
1138 def get_friends_ap_ids(user) do
1140 |> get_friends_query(nil)
1141 |> select([u], u.ap_id)
1145 def get_friends_ids(user, page \\ nil) do
1147 |> get_friends_query(page)
1148 |> select([u], u.id)
1152 defdelegate get_follow_requests(user), to: FollowingRelationship
1154 def increase_note_count(%User{} = user) do
1156 |> where(id: ^user.id)
1157 |> update([u], inc: [note_count: 1])
1159 |> Repo.update_all([])
1161 {1, [user]} -> set_cache(user)
1166 def decrease_note_count(%User{} = user) do
1168 |> where(id: ^user.id)
1171 note_count: fragment("greatest(0, note_count - 1)")
1175 |> Repo.update_all([])
1177 {1, [user]} -> set_cache(user)
1182 def update_note_count(%User{} = user, note_count \\ nil) do
1187 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1193 |> cast(%{note_count: note_count}, [:note_count])
1194 |> update_and_set_cache()
1197 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1198 def maybe_fetch_follow_information(user) do
1199 with {:ok, user} <- fetch_follow_information(user) do
1203 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1209 def fetch_follow_information(user) do
1210 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1212 |> follow_information_changeset(info)
1213 |> update_and_set_cache()
1217 defp follow_information_changeset(user, params) do
1224 :hide_followers_count,
1229 @spec update_follower_count(User.t()) :: {:ok, User.t()}
1230 def update_follower_count(%User{} = user) do
1231 if user.local or !Config.get([:instance, :external_user_synchronization]) do
1232 follower_count = FollowingRelationship.follower_count(user)
1235 |> follow_information_changeset(%{follower_count: follower_count})
1236 |> update_and_set_cache
1238 {:ok, maybe_fetch_follow_information(user)}
1242 @spec update_following_count(User.t()) :: {:ok, User.t()}
1243 def update_following_count(%User{local: false} = user) do
1244 if Config.get([:instance, :external_user_synchronization]) do
1245 {:ok, maybe_fetch_follow_information(user)}
1251 def update_following_count(%User{local: true} = user) do
1252 following_count = FollowingRelationship.following_count(user)
1255 |> follow_information_changeset(%{following_count: following_count})
1256 |> update_and_set_cache()
1259 def set_unread_conversation_count(%User{local: true} = user) do
1260 unread_query = Participation.unread_conversation_count_for_user(user)
1263 |> join(:inner, [u], p in subquery(unread_query))
1265 set: [unread_conversation_count: p.count]
1267 |> where([u], u.id == ^user.id)
1269 |> Repo.update_all([])
1271 {1, [user]} -> set_cache(user)
1276 def set_unread_conversation_count(user), do: {:ok, user}
1278 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1280 Participation.unread_conversation_count_for_user(user)
1281 |> where([p], p.conversation_id == ^conversation.id)
1284 |> join(:inner, [u], p in subquery(unread_query))
1286 inc: [unread_conversation_count: 1]
1288 |> where([u], u.id == ^user.id)
1289 |> where([u, p], p.count == 0)
1291 |> Repo.update_all([])
1293 {1, [user]} -> set_cache(user)
1298 def increment_unread_conversation_count(_, user), do: {:ok, user}
1300 @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
1301 def get_users_from_set(ap_ids, opts \\ []) do
1302 local_only = Keyword.get(opts, :local_only, true)
1303 criteria = %{ap_id: ap_ids, deactivated: false}
1304 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1306 User.Query.build(criteria)
1310 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1311 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1314 query = User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1320 @spec mute(User.t(), User.t(), boolean()) ::
1321 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1322 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1323 add_to_mutes(muter, mutee, notifications?)
1326 def unmute(%User{} = muter, %User{} = mutee) do
1327 remove_from_mutes(muter, mutee)
1330 def subscribe(%User{} = subscriber, %User{} = target) do
1331 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
1333 if blocks?(target, subscriber) and deny_follow_blocked do
1334 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1336 # Note: the relationship is inverse: subscriber acts as relationship target
1337 UserRelationship.create_inverse_subscription(target, subscriber)
1341 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1342 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1343 subscribe(subscriber, subscribee)
1347 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1348 # Note: the relationship is inverse: subscriber acts as relationship target
1349 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1352 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1353 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1354 unsubscribe(unsubscriber, user)
1358 def block(%User{} = blocker, %User{} = blocked) do
1359 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1361 if following?(blocker, blocked) do
1362 {:ok, blocker, _} = unfollow(blocker, blocked)
1368 # clear any requested follows as well
1370 case CommonAPI.reject_follow_request(blocked, blocker) do
1371 {:ok, %User{} = updated_blocked} -> updated_blocked
1375 unsubscribe(blocked, blocker)
1377 unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
1378 if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
1380 {:ok, blocker} = update_follower_count(blocker)
1381 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1382 add_to_block(blocker, blocked)
1385 # helper to handle the block given only an actor's AP id
1386 def block(%User{} = blocker, %{ap_id: ap_id}) do
1387 block(blocker, get_cached_by_ap_id(ap_id))
1390 def unblock(%User{} = blocker, %User{} = blocked) do
1391 remove_from_block(blocker, blocked)
1394 # helper to handle the block given only an actor's AP id
1395 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1396 unblock(blocker, get_cached_by_ap_id(ap_id))
1399 def mutes?(nil, _), do: false
1400 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1402 def mutes_user?(%User{} = user, %User{} = target) do
1403 UserRelationship.mute_exists?(user, target)
1406 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1407 def muted_notifications?(nil, _), do: false
1409 def muted_notifications?(%User{} = user, %User{} = target),
1410 do: UserRelationship.notification_mute_exists?(user, target)
1412 def blocks?(nil, _), do: false
1414 def blocks?(%User{} = user, %User{} = target) do
1415 blocks_user?(user, target) ||
1416 (blocks_domain?(user, target) and not User.following?(user, target))
1419 def blocks_user?(%User{} = user, %User{} = target) do
1420 UserRelationship.block_exists?(user, target)
1423 def blocks_user?(_, _), do: false
1425 def blocks_domain?(%User{} = user, %User{} = target) do
1426 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1427 %{host: host} = URI.parse(target.ap_id)
1428 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1431 def blocks_domain?(_, _), do: false
1433 def subscribed_to?(%User{} = user, %User{} = target) do
1434 # Note: the relationship is inverse: subscriber acts as relationship target
1435 UserRelationship.inverse_subscription_exists?(target, user)
1438 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1439 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1440 subscribed_to?(user, target)
1445 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1446 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1448 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1449 def outgoing_relationships_ap_ids(_user, []), do: %{}
1451 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1453 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1454 when is_list(relationship_types) do
1457 |> assoc(:outgoing_relationships)
1458 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1459 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1460 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1461 |> group_by([user_rel, u], user_rel.relationship_type)
1463 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1468 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1472 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1474 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1476 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1478 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1479 when is_list(relationship_types) do
1481 |> assoc(:incoming_relationships)
1482 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1483 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1484 |> maybe_filter_on_ap_id(ap_ids)
1485 |> select([user_rel, u], u.ap_id)
1490 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1491 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1494 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1496 def deactivate_async(user, status \\ true) do
1497 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1500 def deactivate(user, status \\ true)
1502 def deactivate(users, status) when is_list(users) do
1503 Repo.transaction(fn ->
1504 for user <- users, do: deactivate(user, status)
1508 def deactivate(%User{} = user, status) do
1509 with {:ok, user} <- set_activation_status(user, status) do
1512 |> Enum.filter(& &1.local)
1513 |> Enum.each(&set_cache(update_following_count(&1)))
1515 # Only update local user counts, remote will be update during the next pull.
1518 |> Enum.filter(& &1.local)
1519 |> Enum.each(&do_unfollow(user, &1))
1525 def approve(users) when is_list(users) do
1526 Repo.transaction(fn ->
1527 Enum.map(users, fn user ->
1528 with {:ok, user} <- approve(user), do: user
1533 def approve(%User{} = user) do
1534 change(user, approval_pending: false)
1535 |> update_and_set_cache()
1538 def update_notification_settings(%User{} = user, settings) do
1540 |> cast(%{notification_settings: settings}, [])
1541 |> cast_embed(:notification_settings)
1542 |> validate_required([:notification_settings])
1543 |> update_and_set_cache()
1546 def delete(users) when is_list(users) do
1547 for user <- users, do: delete(user)
1550 def delete(%User{} = user) do
1551 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1554 defp delete_and_invalidate_cache(%User{} = user) do
1555 invalidate_cache(user)
1559 defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user)
1561 defp delete_or_deactivate(%User{local: true} = user) do
1562 status = account_status(user)
1565 :confirmation_pending ->
1566 delete_and_invalidate_cache(user)
1568 :approval_pending ->
1569 delete_and_invalidate_cache(user)
1573 |> change(%{deactivated: true, email: nil})
1574 |> update_and_set_cache()
1578 def perform(:force_password_reset, user), do: force_password_reset(user)
1580 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1581 def perform(:delete, %User{} = user) do
1582 # Remove all relationships
1585 |> Enum.each(fn follower ->
1586 ActivityPub.unfollow(follower, user)
1587 unfollow(follower, user)
1592 |> Enum.each(fn followed ->
1593 ActivityPub.unfollow(user, followed)
1594 unfollow(user, followed)
1597 delete_user_activities(user)
1598 delete_notifications_from_user_activities(user)
1600 delete_outgoing_pending_follow_requests(user)
1602 delete_or_deactivate(user)
1605 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1607 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1608 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1609 when is_list(blocked_identifiers) do
1611 blocked_identifiers,
1612 fn blocked_identifier ->
1613 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1614 {:ok, _block} <- CommonAPI.block(blocker, blocked) do
1618 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1625 def perform(:follow_import, %User{} = follower, followed_identifiers)
1626 when is_list(followed_identifiers) do
1628 followed_identifiers,
1629 fn followed_identifier ->
1630 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1631 {:ok, follower} <- maybe_direct_follow(follower, followed),
1632 {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do
1636 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1643 @spec external_users_query() :: Ecto.Query.t()
1644 def external_users_query do
1652 @spec external_users(keyword()) :: [User.t()]
1653 def external_users(opts \\ []) do
1655 external_users_query()
1656 |> select([u], struct(u, [:id, :ap_id]))
1660 do: where(query, [u], u.id > ^opts[:max_id]),
1665 do: limit(query, ^opts[:limit]),
1671 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1672 BackgroundWorker.enqueue("blocks_import", %{
1673 "blocker_id" => blocker.id,
1674 "blocked_identifiers" => blocked_identifiers
1678 def follow_import(%User{} = follower, followed_identifiers)
1679 when is_list(followed_identifiers) do
1680 BackgroundWorker.enqueue("follow_import", %{
1681 "follower_id" => follower.id,
1682 "followed_identifiers" => followed_identifiers
1686 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1688 |> join(:inner, [n], activity in assoc(n, :activity))
1689 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1690 |> Repo.delete_all()
1693 def delete_user_activities(%User{ap_id: ap_id} = user) do
1695 |> Activity.Queries.by_actor()
1696 |> RepoStreamer.chunk_stream(50)
1697 |> Stream.each(fn activities ->
1698 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1703 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1704 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1705 {:ok, delete_data, _} <- Builder.delete(user, object) do
1706 Pipeline.common_pipeline(delete_data, local: user.local)
1708 {:find_object, nil} ->
1709 # We have the create activity, but not the object, it was probably pruned.
1710 # Insert a tombstone and try again
1711 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1712 {:ok, _tombstone} <- Object.create(tombstone_data) do
1713 delete_activity(activity, user)
1717 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1718 Logger.error("Error: #{inspect(e)}")
1722 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1723 when type in ["Like", "Announce"] do
1724 {:ok, undo, _} = Builder.undo(user, activity)
1725 Pipeline.common_pipeline(undo, local: user.local)
1728 defp delete_activity(_activity, _user), do: "Doing nothing"
1730 defp delete_outgoing_pending_follow_requests(user) do
1732 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1733 |> Repo.delete_all()
1736 def html_filter_policy(%User{no_rich_text: true}) do
1737 Pleroma.HTML.Scrubber.TwitterText
1740 def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
1742 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1744 def get_or_fetch_by_ap_id(ap_id) do
1745 cached_user = get_cached_by_ap_id(ap_id)
1747 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
1749 case {cached_user, maybe_fetched_user} do
1750 {_, {:ok, %User{} = user}} ->
1753 {%User{} = user, _} ->
1757 {:error, :not_found}
1762 Creates an internal service actor by URI if missing.
1763 Optionally takes nickname for addressing.
1765 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1766 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1768 case get_cached_by_ap_id(uri) do
1770 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1771 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1775 %User{invisible: false} = user ->
1785 @spec set_invisible(User.t()) :: {:ok, User.t()}
1786 defp set_invisible(user) do
1788 |> change(%{invisible: true})
1789 |> update_and_set_cache()
1792 @spec create_service_actor(String.t(), String.t()) ::
1793 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1794 defp create_service_actor(uri, nickname) do
1800 follower_address: uri <> "/followers"
1803 |> unique_constraint(:nickname)
1808 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1811 |> :public_key.pem_decode()
1813 |> :public_key.pem_entry_decode()
1818 def public_key(_), do: {:error, "key not found"}
1820 def get_public_key_for_ap_id(ap_id) do
1821 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1822 {:ok, public_key} <- public_key(user) do
1829 def ap_enabled?(%User{local: true}), do: true
1830 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1831 def ap_enabled?(_), do: false
1833 @doc "Gets or fetch a user by uri or nickname."
1834 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1835 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1836 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1838 # wait a period of time and return newest version of the User structs
1839 # this is because we have synchronous follow APIs and need to simulate them
1840 # with an async handshake
1841 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1842 with %User{} = a <- get_cached_by_id(a.id),
1843 %User{} = b <- get_cached_by_id(b.id) do
1850 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1851 with :ok <- :timer.sleep(timeout),
1852 %User{} = a <- get_cached_by_id(a.id),
1853 %User{} = b <- get_cached_by_id(b.id) do
1860 def parse_bio(bio) when is_binary(bio) and bio != "" do
1862 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1866 def parse_bio(_), do: ""
1868 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1869 # TODO: get profile URLs other than user.ap_id
1870 profile_urls = [user.ap_id]
1873 |> CommonUtils.format_input("text/plain",
1874 mentions_format: :full,
1875 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1880 def parse_bio(_, _), do: ""
1882 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1883 Repo.transaction(fn ->
1884 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1888 def tag(nickname, tags) when is_binary(nickname),
1889 do: tag(get_by_nickname(nickname), tags)
1891 def tag(%User{} = user, tags),
1892 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1894 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1895 Repo.transaction(fn ->
1896 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1900 def untag(nickname, tags) when is_binary(nickname),
1901 do: untag(get_by_nickname(nickname), tags)
1903 def untag(%User{} = user, tags),
1904 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1906 defp update_tags(%User{} = user, new_tags) do
1907 {:ok, updated_user} =
1909 |> change(%{tags: new_tags})
1910 |> update_and_set_cache()
1915 defp normalize_tags(tags) do
1918 |> Enum.map(&String.downcase/1)
1921 defp local_nickname_regex do
1922 if Config.get([:instance, :extended_nickname_format]) do
1923 @extended_local_nickname_regex
1925 @strict_local_nickname_regex
1929 def local_nickname(nickname_or_mention) do
1932 |> String.split("@")
1936 def full_nickname(nickname_or_mention),
1937 do: String.trim_leading(nickname_or_mention, "@")
1939 def error_user(ap_id) do
1943 nickname: "erroruser@example.com",
1944 inserted_at: NaiveDateTime.utc_now()
1948 @spec all_superusers() :: [User.t()]
1949 def all_superusers do
1950 User.Query.build(%{super_users: true, local: true, deactivated: false})
1954 def muting_reblogs?(%User{} = user, %User{} = target) do
1955 UserRelationship.reblog_mute_exists?(user, target)
1958 def showing_reblogs?(%User{} = user, %User{} = target) do
1959 not muting_reblogs?(user, target)
1963 The function returns a query to get users with no activity for given interval of days.
1964 Inactive users are those who didn't read any notification, or had any activity where
1965 the user is the activity's actor, during `inactivity_threshold` days.
1966 Deactivated users will not appear in this list.
1970 iex> Pleroma.User.list_inactive_users()
1973 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1974 def list_inactive_users_query(inactivity_threshold \\ 7) do
1975 negative_inactivity_threshold = -inactivity_threshold
1976 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1977 # Subqueries are not supported in `where` clauses, join gets too complicated.
1978 has_read_notifications =
1979 from(n in Pleroma.Notification,
1980 where: n.seen == true,
1982 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1985 |> Pleroma.Repo.all()
1987 from(u in Pleroma.User,
1988 left_join: a in Pleroma.Activity,
1989 on: u.ap_id == a.actor,
1990 where: not is_nil(u.nickname),
1991 where: u.deactivated != ^true,
1992 where: u.id not in ^has_read_notifications,
1995 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1996 is_nil(max(a.inserted_at))
2001 Enable or disable email notifications for user
2005 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
2006 Pleroma.User{email_notifications: %{"digest" => true}}
2008 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
2009 Pleroma.User{email_notifications: %{"digest" => false}}
2011 @spec switch_email_notifications(t(), String.t(), boolean()) ::
2012 {:ok, t()} | {:error, Ecto.Changeset.t()}
2013 def switch_email_notifications(user, type, status) do
2014 User.update_email_notifications(user, %{type => status})
2018 Set `last_digest_emailed_at` value for the user to current time
2020 @spec touch_last_digest_emailed_at(t()) :: t()
2021 def touch_last_digest_emailed_at(user) do
2022 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2024 {:ok, updated_user} =
2026 |> change(%{last_digest_emailed_at: now})
2027 |> update_and_set_cache()
2032 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
2033 def toggle_confirmation(%User{} = user) do
2035 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
2036 |> update_and_set_cache()
2039 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
2040 def toggle_confirmation(users) do
2041 Enum.map(users, &toggle_confirmation/1)
2044 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
2048 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
2049 # use instance-default
2050 config = Config.get([:assets, :mascots])
2051 default_mascot = Config.get([:assets, :default_mascot])
2052 mascot = Keyword.get(config, default_mascot)
2055 "id" => "default-mascot",
2056 "url" => mascot[:url],
2057 "preview_url" => mascot[:url],
2059 "mime_type" => mascot[:mime_type]
2064 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
2066 def ensure_keys_present(%User{} = user) do
2067 with {:ok, pem} <- Keys.generate_rsa_pem() do
2069 |> cast(%{keys: pem}, [:keys])
2070 |> validate_required([:keys])
2071 |> update_and_set_cache()
2075 def get_ap_ids_by_nicknames(nicknames) do
2077 where: u.nickname in ^nicknames,
2083 defdelegate search(query, opts \\ []), to: User.Search
2085 defp put_password_hash(
2086 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2088 change(changeset, password_hash: Pbkdf2.hash_pwd_salt(password))
2091 defp put_password_hash(changeset), do: changeset
2093 def is_internal_user?(%User{nickname: nil}), do: true
2094 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2095 def is_internal_user?(_), do: false
2097 # A hack because user delete activities have a fake id for whatever reason
2098 # TODO: Get rid of this
2099 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2101 def get_delivered_users_by_object_id(object_id) do
2103 inner_join: delivery in assoc(u, :deliveries),
2104 where: delivery.object_id == ^object_id
2109 def change_email(user, email) do
2111 |> cast(%{email: email}, [:email])
2112 |> validate_required([:email])
2113 |> unique_constraint(:email)
2114 |> validate_format(:email, @email_regex)
2115 |> update_and_set_cache()
2118 # Internal function; public one is `deactivate/2`
2119 defp set_activation_status(user, deactivated) do
2121 |> cast(%{deactivated: deactivated}, [:deactivated])
2122 |> update_and_set_cache()
2125 def update_banner(user, banner) do
2127 |> cast(%{banner: banner}, [:banner])
2128 |> update_and_set_cache()
2131 def update_background(user, background) do
2133 |> cast(%{background: background}, [:background])
2134 |> update_and_set_cache()
2137 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
2140 moderator: is_moderator
2144 def validate_fields(changeset, remote? \\ false) do
2145 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2146 limit = Config.get([:instance, limit_name], 0)
2149 |> validate_length(:fields, max: limit)
2150 |> validate_change(:fields, fn :fields, fields ->
2151 if Enum.all?(fields, &valid_field?/1) do
2159 defp valid_field?(%{"name" => name, "value" => value}) do
2160 name_limit = Config.get([:instance, :account_field_name_length], 255)
2161 value_limit = Config.get([:instance, :account_field_value_length], 255)
2163 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2164 String.length(value) <= value_limit
2167 defp valid_field?(_), do: false
2169 defp truncate_field(%{"name" => name, "value" => value}) do
2171 String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
2174 String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
2176 %{"name" => name, "value" => value}
2179 def admin_api_update(user, params) do
2186 |> update_and_set_cache()
2189 @doc "Signs user out of all applications"
2190 def global_sign_out(user) do
2191 OAuth.Authorization.delete_user_authorizations(user)
2192 OAuth.Token.delete_user_tokens(user)
2195 def mascot_update(user, url) do
2197 |> cast(%{mascot: url}, [:mascot])
2198 |> validate_required([:mascot])
2199 |> update_and_set_cache()
2202 def mastodon_settings_update(user, settings) do
2204 |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
2205 |> validate_required([:mastofe_settings])
2206 |> update_and_set_cache()
2209 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2210 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2212 if need_confirmation? do
2214 confirmation_pending: true,
2215 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2219 confirmation_pending: false,
2220 confirmation_token: nil
2224 cast(user, params, [:confirmation_pending, :confirmation_token])
2227 @spec approval_changeset(User.t(), keyword()) :: Changeset.t()
2228 def approval_changeset(user, need_approval: need_approval?) do
2229 params = if need_approval?, do: %{approval_pending: true}, else: %{approval_pending: false}
2230 cast(user, params, [:approval_pending])
2233 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2234 if id not in user.pinned_activities do
2235 max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
2236 params = %{pinned_activities: user.pinned_activities ++ [id]}
2239 |> cast(params, [:pinned_activities])
2240 |> validate_length(:pinned_activities,
2241 max: max_pinned_statuses,
2242 message: "You have already pinned the maximum number of statuses"
2247 |> update_and_set_cache()
2250 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2251 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2254 |> cast(params, [:pinned_activities])
2255 |> update_and_set_cache()
2258 def update_email_notifications(user, settings) do
2259 email_notifications =
2260 user.email_notifications
2261 |> Map.merge(settings)
2262 |> Map.take(["digest"])
2264 params = %{email_notifications: email_notifications}
2265 fields = [:email_notifications]
2268 |> cast(params, fields)
2269 |> validate_required(fields)
2270 |> update_and_set_cache()
2273 defp set_domain_blocks(user, domain_blocks) do
2274 params = %{domain_blocks: domain_blocks}
2277 |> cast(params, [:domain_blocks])
2278 |> validate_required([:domain_blocks])
2279 |> update_and_set_cache()
2282 def block_domain(user, domain_blocked) do
2283 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2286 def unblock_domain(user, domain_blocked) do
2287 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2290 @spec add_to_block(User.t(), User.t()) ::
2291 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2292 defp add_to_block(%User{} = user, %User{} = blocked) do
2293 UserRelationship.create_block(user, blocked)
2296 @spec add_to_block(User.t(), User.t()) ::
2297 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2298 defp remove_from_block(%User{} = user, %User{} = blocked) do
2299 UserRelationship.delete_block(user, blocked)
2302 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2303 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2304 {:ok, user_notification_mute} <-
2305 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2307 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2311 defp remove_from_mutes(user, %User{} = muted_user) do
2312 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2313 {:ok, user_notification_mute} <-
2314 UserRelationship.delete_notification_mute(user, muted_user) do
2315 {:ok, [user_mute, user_notification_mute]}
2319 def set_invisible(user, invisible) do
2320 params = %{invisible: invisible}
2323 |> cast(params, [:invisible])
2324 |> validate_required([:invisible])
2325 |> update_and_set_cache()
2328 def sanitize_html(%User{} = user) do
2329 sanitize_html(user, nil)
2332 # User data that mastodon isn't filtering (treated as plaintext):
2335 def sanitize_html(%User{} = user, filter) do
2337 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2340 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2345 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2346 |> Map.put(:fields, fields)