1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.User do
10 import Ecto, only: [assoc: 2]
13 alias Pleroma.Activity
15 alias Pleroma.Conversation.Participation
16 alias Pleroma.Delivery
17 alias Pleroma.EctoType.ActivityPub.ObjectValidators
19 alias Pleroma.FollowingRelationship
20 alias Pleroma.Formatter
24 alias Pleroma.Notification
26 alias Pleroma.Registration
28 alias Pleroma.RepoStreamer
30 alias Pleroma.UserRelationship
32 alias Pleroma.Web.ActivityPub.ActivityPub
33 alias Pleroma.Web.ActivityPub.Builder
34 alias Pleroma.Web.ActivityPub.Pipeline
35 alias Pleroma.Web.ActivityPub.Utils
36 alias Pleroma.Web.CommonAPI
37 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
38 alias Pleroma.Web.OAuth
39 alias Pleroma.Web.RelMe
40 alias Pleroma.Workers.BackgroundWorker
44 @type t :: %__MODULE__{}
45 @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
46 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
48 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
49 @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
51 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
52 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
54 # AP ID user relationships (blocks, mutes etc.)
55 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
56 @user_relationships_config [
58 blocker_blocks: :blocked_users,
59 blockee_blocks: :blocker_users
62 muter_mutes: :muted_users,
63 mutee_mutes: :muter_users
66 reblog_muter_mutes: :reblog_muted_users,
67 reblog_mutee_mutes: :reblog_muter_users
70 notification_muter_mutes: :notification_muted_users,
71 notification_mutee_mutes: :notification_muter_users
73 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
74 inverse_subscription: [
75 subscribee_subscriptions: :subscriber_users,
76 subscriber_subscriptions: :subscribee_users
82 field(:raw_bio, :string)
83 field(:email, :string)
85 field(:nickname, :string)
86 field(:password_hash, :string)
87 field(:password, :string, virtual: true)
88 field(:password_confirmation, :string, virtual: true)
90 field(:public_key, :string)
91 field(:ap_id, :string)
92 field(:avatar, :map, default: %{})
93 field(:local, :boolean, default: true)
94 field(:follower_address, :string)
95 field(:following_address, :string)
96 field(:search_rank, :float, virtual: true)
97 field(:search_type, :integer, virtual: true)
98 field(:tags, {:array, :string}, default: [])
99 field(:last_refreshed_at, :naive_datetime_usec)
100 field(:last_digest_emailed_at, :naive_datetime)
101 field(:banner, :map, default: %{})
102 field(:background, :map, default: %{})
103 field(:note_count, :integer, default: 0)
104 field(:follower_count, :integer, default: 0)
105 field(:following_count, :integer, default: 0)
106 field(:locked, :boolean, default: false)
107 field(:confirmation_pending, :boolean, default: false)
108 field(:password_reset_pending, :boolean, default: false)
109 field(:confirmation_token, :string, default: nil)
110 field(:default_scope, :string, default: "public")
111 field(:domain_blocks, {:array, :string}, default: [])
112 field(:deactivated, :boolean, default: false)
113 field(:no_rich_text, :boolean, default: false)
114 field(:ap_enabled, :boolean, default: false)
115 field(:is_moderator, :boolean, default: false)
116 field(:is_admin, :boolean, default: false)
117 field(:show_role, :boolean, default: true)
118 field(:mastofe_settings, :map, default: nil)
119 field(:uri, ObjectValidators.Uri, default: nil)
120 field(:hide_followers_count, :boolean, default: false)
121 field(:hide_follows_count, :boolean, default: false)
122 field(:hide_followers, :boolean, default: false)
123 field(:hide_follows, :boolean, default: false)
124 field(:hide_favorites, :boolean, default: true)
125 field(:unread_conversation_count, :integer, default: 0)
126 field(:pinned_activities, {:array, :string}, default: [])
127 field(:email_notifications, :map, default: %{"digest" => false})
128 field(:mascot, :map, default: nil)
129 field(:emoji, :map, default: %{})
130 field(:pleroma_settings_store, :map, default: %{})
131 field(:fields, {:array, :map}, default: [])
132 field(:raw_fields, {:array, :map}, default: [])
133 field(:discoverable, :boolean, default: false)
134 field(:invisible, :boolean, default: false)
135 field(:allow_following_move, :boolean, default: true)
136 field(:skip_thread_containment, :boolean, default: false)
137 field(:actor_type, :string, default: "Person")
138 field(:also_known_as, {:array, :string}, default: [])
139 field(:inbox, :string)
140 field(:shared_inbox, :string)
141 field(:accepts_chat_messages, :boolean, default: nil)
144 :notification_settings,
145 Pleroma.User.NotificationSetting,
149 has_many(:notifications, Notification)
150 has_many(:registrations, Registration)
151 has_many(:deliveries, Delivery)
153 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
154 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
156 for {relationship_type,
158 {outgoing_relation, outgoing_relation_target},
159 {incoming_relation, incoming_relation_source}
160 ]} <- @user_relationships_config do
161 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
162 # :notification_muter_mutes, :subscribee_subscriptions
163 has_many(outgoing_relation, UserRelationship,
164 foreign_key: :source_id,
165 where: [relationship_type: relationship_type]
168 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
169 # :notification_mutee_mutes, :subscriber_subscriptions
170 has_many(incoming_relation, UserRelationship,
171 foreign_key: :target_id,
172 where: [relationship_type: relationship_type]
175 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
176 # :notification_muted_users, :subscriber_users
177 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
179 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
180 # :notification_muter_users, :subscribee_users
181 has_many(incoming_relation_source, through: [incoming_relation, :source])
184 # `:blocks` is deprecated (replaced with `blocked_users` relation)
185 field(:blocks, {:array, :string}, default: [])
186 # `:mutes` is deprecated (replaced with `muted_users` relation)
187 field(:mutes, {:array, :string}, default: [])
188 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
189 field(:muted_reblogs, {:array, :string}, default: [])
190 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
191 field(:muted_notifications, {:array, :string}, default: [])
192 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
193 field(:subscribers, {:array, :string}, default: [])
196 :multi_factor_authentication_settings,
204 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
205 @user_relationships_config do
206 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
207 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
208 # `def subscriber_users/2`
209 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
210 target_users_query = assoc(user, unquote(outgoing_relation_target))
212 if restrict_deactivated? do
213 restrict_deactivated(target_users_query)
219 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
220 # `def notification_muted_users/2`, `def subscriber_users/2`
221 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
223 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
225 restrict_deactivated?
230 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
231 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
232 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
234 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
236 restrict_deactivated?
238 |> select([u], u.ap_id)
244 Dumps Flake Id to SQL-compatible format (16-byte UUID).
245 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
247 def binary_id(source_id) when is_binary(source_id) do
248 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
255 def binary_id(source_ids) when is_list(source_ids) do
256 Enum.map(source_ids, &binary_id/1)
259 def binary_id(%User{} = user), do: binary_id(user.id)
261 @doc "Returns status account"
262 @spec account_status(User.t()) :: account_status()
263 def account_status(%User{deactivated: true}), do: :deactivated
264 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
266 def account_status(%User{confirmation_pending: true}) do
267 if Config.get([:instance, :account_activation_required]) do
268 :confirmation_pending
274 def account_status(%User{}), do: :active
276 @spec visible_for(User.t(), User.t() | nil) ::
279 | :restricted_unauthenticated
281 | :confirmation_pending
282 def visible_for(user, for_user \\ nil)
284 def visible_for(%User{invisible: true}, _), do: :invisible
286 def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
288 def visible_for(%User{} = user, nil) do
289 if restrict_unauthenticated?(user) do
290 :restrict_unauthenticated
292 visible_account_status(user)
296 def visible_for(%User{} = user, for_user) do
297 if superuser?(for_user) do
300 visible_account_status(user)
304 def visible_for(_, _), do: :invisible
306 defp restrict_unauthenticated?(%User{local: local}) do
307 config_key = if local, do: :local, else: :remote
309 Config.get([:restrict_unauthenticated, :profiles, config_key], false)
312 defp visible_account_status(user) do
313 status = account_status(user)
315 if status in [:active, :password_reset_pending] do
322 @spec superuser?(User.t()) :: boolean()
323 def superuser?(%User{local: true, is_admin: true}), do: true
324 def superuser?(%User{local: true, is_moderator: true}), do: true
325 def superuser?(_), do: false
327 @spec invisible?(User.t()) :: boolean()
328 def invisible?(%User{invisible: true}), do: true
329 def invisible?(_), do: false
331 def avatar_url(user, options \\ []) do
333 %{"url" => [%{"href" => href} | _]} ->
337 unless options[:no_default] do
338 Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png")
343 def banner_url(user, options \\ []) do
345 %{"url" => [%{"href" => href} | _]} -> href
346 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
350 # Should probably be renamed or removed
351 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
353 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
354 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
356 @spec ap_following(User.t()) :: String.t()
357 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
358 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
360 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
361 def restrict_deactivated(query) do
362 from(u in query, where: u.deactivated != ^true)
365 defdelegate following_count(user), to: FollowingRelationship
367 defp truncate_fields_param(params) do
368 if Map.has_key?(params, :fields) do
369 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
375 defp truncate_if_exists(params, key, max_length) do
376 if Map.has_key?(params, key) and is_binary(params[key]) do
377 {value, _chopped} = String.split_at(params[key], max_length)
378 Map.put(params, key, value)
384 defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
386 defp fix_follower_address(%{nickname: nickname} = params),
387 do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
389 defp fix_follower_address(params), do: params
391 def remote_user_changeset(struct \\ %User{local: false}, params) do
392 bio_limit = Config.get([:instance, :user_bio_length], 5000)
393 name_limit = Config.get([:instance, :user_name_length], 100)
396 case params[:name] do
397 name when is_binary(name) and byte_size(name) > 0 -> name
398 _ -> params[:nickname]
403 |> Map.put(:name, name)
404 |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
405 |> truncate_if_exists(:name, name_limit)
406 |> truncate_if_exists(:bio, bio_limit)
407 |> truncate_fields_param()
408 |> fix_follower_address()
432 :hide_followers_count,
441 :accepts_chat_messages
444 |> validate_required([:name, :ap_id])
445 |> unique_constraint(:nickname)
446 |> validate_format(:nickname, @email_regex)
447 |> validate_length(:bio, max: bio_limit)
448 |> validate_length(:name, max: name_limit)
449 |> validate_fields(true)
452 def update_changeset(struct, params \\ %{}) do
453 bio_limit = Config.get([:instance, :user_bio_length], 5000)
454 name_limit = Config.get([:instance, :user_name_length], 100)
474 :hide_followers_count,
477 :allow_following_move,
480 :skip_thread_containment,
483 :pleroma_settings_store,
487 :accepts_chat_messages
490 |> unique_constraint(:nickname)
491 |> validate_format(:nickname, local_nickname_regex())
492 |> validate_length(:bio, max: bio_limit)
493 |> validate_length(:name, min: 1, max: name_limit)
494 |> validate_inclusion(:actor_type, ["Person", "Service"])
497 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
498 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
499 |> put_change_if_present(:banner, &put_upload(&1, :banner))
500 |> put_change_if_present(:background, &put_upload(&1, :background))
501 |> put_change_if_present(
502 :pleroma_settings_store,
503 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
505 |> validate_fields(false)
508 defp put_fields(changeset) do
509 if raw_fields = get_change(changeset, :raw_fields) do
512 |> Enum.filter(fn %{"name" => n} -> n != "" end)
516 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
519 |> put_change(:raw_fields, raw_fields)
520 |> put_change(:fields, fields)
526 defp parse_fields(value) do
528 |> Formatter.linkify(mentions_format: :full)
532 defp put_emoji(changeset) do
533 emojified_fields = [:bio, :name, :raw_fields]
535 if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
536 bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
537 name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
539 emoji = Map.merge(bio, name)
543 |> get_field(:raw_fields)
544 |> Enum.reduce(emoji, fn x, acc ->
545 Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
548 put_change(changeset, :emoji, emoji)
554 defp put_change_if_present(changeset, map_field, value_function) do
555 with {:ok, value} <- fetch_change(changeset, map_field),
556 {:ok, new_value} <- value_function.(value) do
557 put_change(changeset, map_field, new_value)
563 defp put_upload(value, type) do
564 with %Plug.Upload{} <- value,
565 {:ok, object} <- ActivityPub.upload(value, type: type) do
570 def update_as_admin_changeset(struct, params) do
572 |> update_changeset(params)
573 |> cast(params, [:email])
574 |> delete_change(:also_known_as)
575 |> unique_constraint(:email)
576 |> validate_format(:email, @email_regex)
577 |> validate_inclusion(:actor_type, ["Person", "Service"])
580 @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
581 def update_as_admin(user, params) do
582 params = Map.put(params, "password_confirmation", params["password"])
583 changeset = update_as_admin_changeset(user, params)
585 if params["password"] do
586 reset_password(user, changeset, params)
588 User.update_and_set_cache(changeset)
592 def password_update_changeset(struct, params) do
594 |> cast(params, [:password, :password_confirmation])
595 |> validate_required([:password, :password_confirmation])
596 |> validate_confirmation(:password)
597 |> put_password_hash()
598 |> put_change(:password_reset_pending, false)
601 @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
602 def reset_password(%User{} = user, params) do
603 reset_password(user, user, params)
606 def reset_password(%User{id: user_id} = user, struct, params) do
609 |> Multi.update(:user, password_update_changeset(struct, params))
610 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
611 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
613 case Repo.transaction(multi) do
614 {:ok, %{user: user} = _} -> set_cache(user)
615 {:error, _, changeset, _} -> {:error, changeset}
619 def update_password_reset_pending(user, value) do
622 |> put_change(:password_reset_pending, value)
623 |> update_and_set_cache()
626 def force_password_reset_async(user) do
627 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
630 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
631 def force_password_reset(user), do: update_password_reset_pending(user, true)
633 def register_changeset(struct, params \\ %{}, opts \\ []) do
634 bio_limit = Config.get([:instance, :user_bio_length], 5000)
635 name_limit = Config.get([:instance, :user_name_length], 100)
636 params = Map.put_new(params, :accepts_chat_messages, true)
639 if is_nil(opts[:need_confirmation]) do
640 Config.get([:instance, :account_activation_required])
642 opts[:need_confirmation]
646 |> confirmation_changeset(need_confirmation: need_confirmation?)
654 :password_confirmation,
656 :accepts_chat_messages
658 |> validate_required([:name, :nickname, :password, :password_confirmation])
659 |> validate_confirmation(:password)
660 |> unique_constraint(:email)
661 |> unique_constraint(:nickname)
662 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
663 |> validate_format(:nickname, local_nickname_regex())
664 |> validate_format(:email, @email_regex)
665 |> validate_length(:bio, max: bio_limit)
666 |> validate_length(:name, min: 1, max: name_limit)
667 |> maybe_validate_required_email(opts[:external])
670 |> unique_constraint(:ap_id)
671 |> put_following_and_follower_address()
674 def maybe_validate_required_email(changeset, true), do: changeset
676 def maybe_validate_required_email(changeset, _) do
677 if Config.get([:instance, :account_activation_required]) do
678 validate_required(changeset, [:email])
684 defp put_ap_id(changeset) do
685 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
686 put_change(changeset, :ap_id, ap_id)
689 defp put_following_and_follower_address(changeset) do
690 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
693 |> put_change(:follower_address, followers)
696 defp autofollow_users(user) do
697 candidates = Config.get([:instance, :autofollowed_nicknames])
700 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
703 follow_all(user, autofollowed_users)
706 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
707 def register(%Ecto.Changeset{} = changeset) do
708 with {:ok, user} <- Repo.insert(changeset) do
709 post_register_action(user)
713 def post_register_action(%User{} = user) do
714 with {:ok, user} <- autofollow_users(user),
715 {:ok, user} <- set_cache(user),
716 {:ok, _} <- send_welcome_email(user),
717 {:ok, _} <- send_welcome_message(user),
718 {:ok, _} <- try_send_confirmation_email(user) do
723 def send_welcome_message(user) do
724 if User.WelcomeMessage.enabled?() do
725 User.WelcomeMessage.post_message(user)
732 def send_welcome_email(%User{email: email} = user) when is_binary(email) do
733 if User.WelcomeEmail.enabled?() do
734 User.WelcomeEmail.send_email(user)
741 def send_welcome_email(_), do: {:ok, :noop}
743 def try_send_confirmation_email(%User{} = user) do
744 if user.confirmation_pending &&
745 Config.get([:instance, :account_activation_required]) do
747 |> Pleroma.Emails.UserEmail.account_confirmation_email()
748 |> Pleroma.Emails.Mailer.deliver_async()
756 def try_send_confirmation_email(users) do
757 Enum.each(users, &try_send_confirmation_email/1)
760 def needs_update?(%User{local: true}), do: false
762 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
764 def needs_update?(%User{local: false} = user) do
765 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
768 def needs_update?(_), do: true
770 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
772 # "Locked" (self-locked) users demand explicit authorization of follow requests
773 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
774 follow(follower, followed, :follow_pending)
777 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
778 follow(follower, followed)
781 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
782 if not ap_enabled?(followed) do
783 follow(follower, followed)
789 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
790 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
791 def follow_all(follower, followeds) do
793 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
794 |> Enum.each(&follow(follower, &1, :follow_accept))
799 defdelegate following(user), to: FollowingRelationship
801 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
802 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
805 followed.deactivated ->
806 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
808 deny_follow_blocked and blocks?(followed, follower) ->
809 {:error, "Could not follow user: #{followed.nickname} blocked you."}
812 FollowingRelationship.follow(follower, followed, state)
814 {:ok, _} = update_follower_count(followed)
817 |> update_following_count()
821 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
822 {:error, "Not subscribed!"}
825 @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
826 def unfollow(%User{} = follower, %User{} = followed) do
827 case do_unfollow(follower, followed) do
828 {:ok, follower, followed} ->
829 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
836 @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
837 defp do_unfollow(%User{} = follower, %User{} = followed) do
838 case get_follow_state(follower, followed) do
839 state when state in [:follow_pending, :follow_accept] ->
840 FollowingRelationship.unfollow(follower, followed)
841 {:ok, followed} = update_follower_count(followed)
845 |> update_following_count()
847 {:ok, follower, followed}
850 {:error, "Not subscribed!"}
854 defdelegate following?(follower, followed), to: FollowingRelationship
856 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
857 def get_follow_state(%User{} = follower, %User{} = following) do
858 following_relationship = FollowingRelationship.get(follower, following)
859 get_follow_state(follower, following, following_relationship)
862 def get_follow_state(
865 following_relationship
867 case {following_relationship, following.local} do
869 case Utils.fetch_latest_follow(follower, following) do
870 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
871 FollowingRelationship.state_to_enum(state)
877 {%{state: state}, _} ->
885 def locked?(%User{} = user) do
890 Repo.get_by(User, id: id)
893 def get_by_ap_id(ap_id) do
894 Repo.get_by(User, ap_id: ap_id)
897 def get_all_by_ap_id(ap_ids) do
898 from(u in __MODULE__,
899 where: u.ap_id in ^ap_ids
904 def get_all_by_ids(ids) do
905 from(u in __MODULE__, where: u.id in ^ids)
909 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
910 # of the ap_id and the domain and tries to get that user
911 def get_by_guessed_nickname(ap_id) do
912 domain = URI.parse(ap_id).host
913 name = List.last(String.split(ap_id, "/"))
914 nickname = "#{name}@#{domain}"
916 get_cached_by_nickname(nickname)
919 def set_cache({:ok, user}), do: set_cache(user)
920 def set_cache({:error, err}), do: {:error, err}
922 def set_cache(%User{} = user) do
923 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
924 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
925 Cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
929 def update_and_set_cache(struct, params) do
931 |> update_changeset(params)
932 |> update_and_set_cache()
935 def update_and_set_cache(changeset) do
936 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
941 def get_user_friends_ap_ids(user) do
942 from(u in User.get_friends_query(user), select: u.ap_id)
946 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
947 def get_cached_user_friends_ap_ids(user) do
948 Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
949 get_user_friends_ap_ids(user)
953 def invalidate_cache(user) do
954 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
955 Cachex.del(:user_cache, "nickname:#{user.nickname}")
956 Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
959 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
960 def get_cached_by_ap_id(ap_id) do
961 key = "ap_id:#{ap_id}"
963 with {:ok, nil} <- Cachex.get(:user_cache, key),
964 user when not is_nil(user) <- get_by_ap_id(ap_id),
965 {:ok, true} <- Cachex.put(:user_cache, key, user) do
973 def get_cached_by_id(id) do
977 Cachex.fetch!(:user_cache, key, fn _ ->
981 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
982 {:commit, user.ap_id}
988 get_cached_by_ap_id(ap_id)
991 def get_cached_by_nickname(nickname) do
992 key = "nickname:#{nickname}"
994 Cachex.fetch!(:user_cache, key, fn ->
995 case get_or_fetch_by_nickname(nickname) do
996 {:ok, user} -> {:commit, user}
997 {:error, _error} -> {:ignore, nil}
1002 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
1003 restrict_to_local = Config.get([:instance, :limit_to_local_content])
1006 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
1007 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
1009 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
1010 get_cached_by_nickname(nickname_or_id)
1012 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
1013 get_cached_by_nickname(nickname_or_id)
1020 @spec get_by_nickname(String.t()) :: User.t() | nil
1021 def get_by_nickname(nickname) do
1022 Repo.get_by(User, nickname: nickname) ||
1023 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
1024 Repo.get_by(User, nickname: local_nickname(nickname))
1028 def get_by_email(email), do: Repo.get_by(User, email: email)
1030 def get_by_nickname_or_email(nickname_or_email) do
1031 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
1034 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
1036 def get_or_fetch_by_nickname(nickname) do
1037 with %User{} = user <- get_by_nickname(nickname) do
1041 with [_nick, _domain] <- String.split(nickname, "@"),
1042 {:ok, user} <- fetch_by_nickname(nickname) do
1045 _e -> {:error, "not found " <> nickname}
1050 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1051 def get_followers_query(%User{} = user, nil) do
1052 User.Query.build(%{followers: user, deactivated: false})
1055 def get_followers_query(user, page) do
1057 |> get_followers_query(nil)
1058 |> User.Query.paginate(page, 20)
1061 @spec get_followers_query(User.t()) :: Ecto.Query.t()
1062 def get_followers_query(user), do: get_followers_query(user, nil)
1064 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1065 def get_followers(user, page \\ nil) do
1067 |> get_followers_query(page)
1071 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1072 def get_external_followers(user, page \\ nil) do
1074 |> get_followers_query(page)
1075 |> User.Query.build(%{external: true})
1079 def get_followers_ids(user, page \\ nil) do
1081 |> get_followers_query(page)
1082 |> select([u], u.id)
1086 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1087 def get_friends_query(%User{} = user, nil) do
1088 User.Query.build(%{friends: user, deactivated: false})
1091 def get_friends_query(user, page) do
1093 |> get_friends_query(nil)
1094 |> User.Query.paginate(page, 20)
1097 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1098 def get_friends_query(user), do: get_friends_query(user, nil)
1100 def get_friends(user, page \\ nil) do
1102 |> get_friends_query(page)
1106 def get_friends_ap_ids(user) do
1108 |> get_friends_query(nil)
1109 |> select([u], u.ap_id)
1113 def get_friends_ids(user, page \\ nil) do
1115 |> get_friends_query(page)
1116 |> select([u], u.id)
1120 defdelegate get_follow_requests(user), to: FollowingRelationship
1122 def increase_note_count(%User{} = user) do
1124 |> where(id: ^user.id)
1125 |> update([u], inc: [note_count: 1])
1127 |> Repo.update_all([])
1129 {1, [user]} -> set_cache(user)
1134 def decrease_note_count(%User{} = user) do
1136 |> where(id: ^user.id)
1139 note_count: fragment("greatest(0, note_count - 1)")
1143 |> Repo.update_all([])
1145 {1, [user]} -> set_cache(user)
1150 def update_note_count(%User{} = user, note_count \\ nil) do
1155 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1161 |> cast(%{note_count: note_count}, [:note_count])
1162 |> update_and_set_cache()
1165 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1166 def maybe_fetch_follow_information(user) do
1167 with {:ok, user} <- fetch_follow_information(user) do
1171 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1177 def fetch_follow_information(user) do
1178 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1180 |> follow_information_changeset(info)
1181 |> update_and_set_cache()
1185 defp follow_information_changeset(user, params) do
1192 :hide_followers_count,
1197 @spec update_follower_count(User.t()) :: {:ok, User.t()}
1198 def update_follower_count(%User{} = user) do
1199 if user.local or !Config.get([:instance, :external_user_synchronization]) do
1200 follower_count = FollowingRelationship.follower_count(user)
1203 |> follow_information_changeset(%{follower_count: follower_count})
1204 |> update_and_set_cache
1206 {:ok, maybe_fetch_follow_information(user)}
1210 @spec update_following_count(User.t()) :: {:ok, User.t()}
1211 def update_following_count(%User{local: false} = user) do
1212 if Config.get([:instance, :external_user_synchronization]) do
1213 {:ok, maybe_fetch_follow_information(user)}
1219 def update_following_count(%User{local: true} = user) do
1220 following_count = FollowingRelationship.following_count(user)
1223 |> follow_information_changeset(%{following_count: following_count})
1224 |> update_and_set_cache()
1227 def set_unread_conversation_count(%User{local: true} = user) do
1228 unread_query = Participation.unread_conversation_count_for_user(user)
1231 |> join(:inner, [u], p in subquery(unread_query))
1233 set: [unread_conversation_count: p.count]
1235 |> where([u], u.id == ^user.id)
1237 |> Repo.update_all([])
1239 {1, [user]} -> set_cache(user)
1244 def set_unread_conversation_count(user), do: {:ok, user}
1246 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1248 Participation.unread_conversation_count_for_user(user)
1249 |> where([p], p.conversation_id == ^conversation.id)
1252 |> join(:inner, [u], p in subquery(unread_query))
1254 inc: [unread_conversation_count: 1]
1256 |> where([u], u.id == ^user.id)
1257 |> where([u, p], p.count == 0)
1259 |> Repo.update_all([])
1261 {1, [user]} -> set_cache(user)
1266 def increment_unread_conversation_count(_, user), do: {:ok, user}
1268 @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
1269 def get_users_from_set(ap_ids, opts \\ []) do
1270 local_only = Keyword.get(opts, :local_only, true)
1271 criteria = %{ap_id: ap_ids, deactivated: false}
1272 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1274 User.Query.build(criteria)
1278 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1279 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1282 query = User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1288 @spec mute(User.t(), User.t(), boolean()) ::
1289 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1290 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1291 add_to_mutes(muter, mutee, notifications?)
1294 def unmute(%User{} = muter, %User{} = mutee) do
1295 remove_from_mutes(muter, mutee)
1298 def subscribe(%User{} = subscriber, %User{} = target) do
1299 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
1301 if blocks?(target, subscriber) and deny_follow_blocked do
1302 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1304 # Note: the relationship is inverse: subscriber acts as relationship target
1305 UserRelationship.create_inverse_subscription(target, subscriber)
1309 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1310 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1311 subscribe(subscriber, subscribee)
1315 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1316 # Note: the relationship is inverse: subscriber acts as relationship target
1317 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1320 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1321 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1322 unsubscribe(unsubscriber, user)
1326 def block(%User{} = blocker, %User{} = blocked) do
1327 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1329 if following?(blocker, blocked) do
1330 {:ok, blocker, _} = unfollow(blocker, blocked)
1336 # clear any requested follows as well
1338 case CommonAPI.reject_follow_request(blocked, blocker) do
1339 {:ok, %User{} = updated_blocked} -> updated_blocked
1343 unsubscribe(blocked, blocker)
1345 unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
1346 if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
1348 {:ok, blocker} = update_follower_count(blocker)
1349 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1350 add_to_block(blocker, blocked)
1353 # helper to handle the block given only an actor's AP id
1354 def block(%User{} = blocker, %{ap_id: ap_id}) do
1355 block(blocker, get_cached_by_ap_id(ap_id))
1358 def unblock(%User{} = blocker, %User{} = blocked) do
1359 remove_from_block(blocker, blocked)
1362 # helper to handle the block given only an actor's AP id
1363 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1364 unblock(blocker, get_cached_by_ap_id(ap_id))
1367 def mutes?(nil, _), do: false
1368 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1370 def mutes_user?(%User{} = user, %User{} = target) do
1371 UserRelationship.mute_exists?(user, target)
1374 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1375 def muted_notifications?(nil, _), do: false
1377 def muted_notifications?(%User{} = user, %User{} = target),
1378 do: UserRelationship.notification_mute_exists?(user, target)
1380 def blocks?(nil, _), do: false
1382 def blocks?(%User{} = user, %User{} = target) do
1383 blocks_user?(user, target) ||
1384 (blocks_domain?(user, target) and not User.following?(user, target))
1387 def blocks_user?(%User{} = user, %User{} = target) do
1388 UserRelationship.block_exists?(user, target)
1391 def blocks_user?(_, _), do: false
1393 def blocks_domain?(%User{} = user, %User{} = target) do
1394 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1395 %{host: host} = URI.parse(target.ap_id)
1396 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1399 def blocks_domain?(_, _), do: false
1401 def subscribed_to?(%User{} = user, %User{} = target) do
1402 # Note: the relationship is inverse: subscriber acts as relationship target
1403 UserRelationship.inverse_subscription_exists?(target, user)
1406 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1407 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1408 subscribed_to?(user, target)
1413 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1414 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1416 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1417 def outgoing_relationships_ap_ids(_user, []), do: %{}
1419 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1421 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1422 when is_list(relationship_types) do
1425 |> assoc(:outgoing_relationships)
1426 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1427 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1428 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1429 |> group_by([user_rel, u], user_rel.relationship_type)
1431 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1436 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1440 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1442 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1444 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1446 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1447 when is_list(relationship_types) do
1449 |> assoc(:incoming_relationships)
1450 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1451 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1452 |> maybe_filter_on_ap_id(ap_ids)
1453 |> select([user_rel, u], u.ap_id)
1458 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1459 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1462 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1464 def deactivate_async(user, status \\ true) do
1465 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1468 def deactivate(user, status \\ true)
1470 def deactivate(users, status) when is_list(users) do
1471 Repo.transaction(fn ->
1472 for user <- users, do: deactivate(user, status)
1476 def deactivate(%User{} = user, status) do
1477 with {:ok, user} <- set_activation_status(user, status) do
1480 |> Enum.filter(& &1.local)
1481 |> Enum.each(&set_cache(update_following_count(&1)))
1483 # Only update local user counts, remote will be update during the next pull.
1486 |> Enum.filter(& &1.local)
1487 |> Enum.each(&do_unfollow(user, &1))
1493 def update_notification_settings(%User{} = user, settings) do
1495 |> cast(%{notification_settings: settings}, [])
1496 |> cast_embed(:notification_settings)
1497 |> validate_required([:notification_settings])
1498 |> update_and_set_cache()
1501 def delete(users) when is_list(users) do
1502 for user <- users, do: delete(user)
1505 def delete(%User{} = user) do
1506 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1509 defp delete_and_invalidate_cache(%User{} = user) do
1510 invalidate_cache(user)
1514 defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user)
1516 defp delete_or_deactivate(%User{local: true} = user) do
1517 status = account_status(user)
1519 if status == :confirmation_pending do
1520 delete_and_invalidate_cache(user)
1523 |> change(%{deactivated: true, email: nil})
1524 |> update_and_set_cache()
1528 def perform(:force_password_reset, user), do: force_password_reset(user)
1530 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1531 def perform(:delete, %User{} = user) do
1532 # Remove all relationships
1535 |> Enum.each(fn follower ->
1536 ActivityPub.unfollow(follower, user)
1537 unfollow(follower, user)
1542 |> Enum.each(fn followed ->
1543 ActivityPub.unfollow(user, followed)
1544 unfollow(user, followed)
1547 delete_user_activities(user)
1548 delete_notifications_from_user_activities(user)
1550 delete_outgoing_pending_follow_requests(user)
1552 delete_or_deactivate(user)
1555 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1557 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1558 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1559 when is_list(blocked_identifiers) do
1561 blocked_identifiers,
1562 fn blocked_identifier ->
1563 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1564 {:ok, _block} <- CommonAPI.block(blocker, blocked) do
1568 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1575 def perform(:follow_import, %User{} = follower, followed_identifiers)
1576 when is_list(followed_identifiers) do
1578 followed_identifiers,
1579 fn followed_identifier ->
1580 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1581 {:ok, follower} <- maybe_direct_follow(follower, followed),
1582 {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do
1586 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1593 @spec external_users_query() :: Ecto.Query.t()
1594 def external_users_query do
1602 @spec external_users(keyword()) :: [User.t()]
1603 def external_users(opts \\ []) do
1605 external_users_query()
1606 |> select([u], struct(u, [:id, :ap_id]))
1610 do: where(query, [u], u.id > ^opts[:max_id]),
1615 do: limit(query, ^opts[:limit]),
1621 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1622 BackgroundWorker.enqueue("blocks_import", %{
1623 "blocker_id" => blocker.id,
1624 "blocked_identifiers" => blocked_identifiers
1628 def follow_import(%User{} = follower, followed_identifiers)
1629 when is_list(followed_identifiers) do
1630 BackgroundWorker.enqueue("follow_import", %{
1631 "follower_id" => follower.id,
1632 "followed_identifiers" => followed_identifiers
1636 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1638 |> join(:inner, [n], activity in assoc(n, :activity))
1639 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1640 |> Repo.delete_all()
1643 def delete_user_activities(%User{ap_id: ap_id} = user) do
1645 |> Activity.Queries.by_actor()
1646 |> RepoStreamer.chunk_stream(50)
1647 |> Stream.each(fn activities ->
1648 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1653 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1654 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1655 {:ok, delete_data, _} <- Builder.delete(user, object) do
1656 Pipeline.common_pipeline(delete_data, local: user.local)
1658 {:find_object, nil} ->
1659 # We have the create activity, but not the object, it was probably pruned.
1660 # Insert a tombstone and try again
1661 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1662 {:ok, _tombstone} <- Object.create(tombstone_data) do
1663 delete_activity(activity, user)
1667 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1668 Logger.error("Error: #{inspect(e)}")
1672 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1673 when type in ["Like", "Announce"] do
1674 {:ok, undo, _} = Builder.undo(user, activity)
1675 Pipeline.common_pipeline(undo, local: user.local)
1678 defp delete_activity(_activity, _user), do: "Doing nothing"
1680 defp delete_outgoing_pending_follow_requests(user) do
1682 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1683 |> Repo.delete_all()
1686 def html_filter_policy(%User{no_rich_text: true}) do
1687 Pleroma.HTML.Scrubber.TwitterText
1690 def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
1692 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1694 def get_or_fetch_by_ap_id(ap_id) do
1695 cached_user = get_cached_by_ap_id(ap_id)
1697 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
1699 case {cached_user, maybe_fetched_user} do
1700 {_, {:ok, %User{} = user}} ->
1703 {%User{} = user, _} ->
1707 {:error, :not_found}
1712 Creates an internal service actor by URI if missing.
1713 Optionally takes nickname for addressing.
1715 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1716 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1718 case get_cached_by_ap_id(uri) do
1720 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1721 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1725 %User{invisible: false} = user ->
1735 @spec set_invisible(User.t()) :: {:ok, User.t()}
1736 defp set_invisible(user) do
1738 |> change(%{invisible: true})
1739 |> update_and_set_cache()
1742 @spec create_service_actor(String.t(), String.t()) ::
1743 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1744 defp create_service_actor(uri, nickname) do
1750 follower_address: uri <> "/followers"
1753 |> unique_constraint(:nickname)
1758 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1761 |> :public_key.pem_decode()
1763 |> :public_key.pem_entry_decode()
1768 def public_key(_), do: {:error, "key not found"}
1770 def get_public_key_for_ap_id(ap_id) do
1771 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1772 {:ok, public_key} <- public_key(user) do
1779 def ap_enabled?(%User{local: true}), do: true
1780 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1781 def ap_enabled?(_), do: false
1783 @doc "Gets or fetch a user by uri or nickname."
1784 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1785 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1786 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1788 # wait a period of time and return newest version of the User structs
1789 # this is because we have synchronous follow APIs and need to simulate them
1790 # with an async handshake
1791 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1792 with %User{} = a <- get_cached_by_id(a.id),
1793 %User{} = b <- get_cached_by_id(b.id) do
1800 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1801 with :ok <- :timer.sleep(timeout),
1802 %User{} = a <- get_cached_by_id(a.id),
1803 %User{} = b <- get_cached_by_id(b.id) do
1810 def parse_bio(bio) when is_binary(bio) and bio != "" do
1812 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1816 def parse_bio(_), do: ""
1818 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1819 # TODO: get profile URLs other than user.ap_id
1820 profile_urls = [user.ap_id]
1823 |> CommonUtils.format_input("text/plain",
1824 mentions_format: :full,
1825 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1830 def parse_bio(_, _), do: ""
1832 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1833 Repo.transaction(fn ->
1834 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1838 def tag(nickname, tags) when is_binary(nickname),
1839 do: tag(get_by_nickname(nickname), tags)
1841 def tag(%User{} = user, tags),
1842 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1844 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1845 Repo.transaction(fn ->
1846 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1850 def untag(nickname, tags) when is_binary(nickname),
1851 do: untag(get_by_nickname(nickname), tags)
1853 def untag(%User{} = user, tags),
1854 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1856 defp update_tags(%User{} = user, new_tags) do
1857 {:ok, updated_user} =
1859 |> change(%{tags: new_tags})
1860 |> update_and_set_cache()
1865 defp normalize_tags(tags) do
1868 |> Enum.map(&String.downcase/1)
1871 defp local_nickname_regex do
1872 if Config.get([:instance, :extended_nickname_format]) do
1873 @extended_local_nickname_regex
1875 @strict_local_nickname_regex
1879 def local_nickname(nickname_or_mention) do
1882 |> String.split("@")
1886 def full_nickname(nickname_or_mention),
1887 do: String.trim_leading(nickname_or_mention, "@")
1889 def error_user(ap_id) do
1893 nickname: "erroruser@example.com",
1894 inserted_at: NaiveDateTime.utc_now()
1898 @spec all_superusers() :: [User.t()]
1899 def all_superusers do
1900 User.Query.build(%{super_users: true, local: true, deactivated: false})
1904 def muting_reblogs?(%User{} = user, %User{} = target) do
1905 UserRelationship.reblog_mute_exists?(user, target)
1908 def showing_reblogs?(%User{} = user, %User{} = target) do
1909 not muting_reblogs?(user, target)
1913 The function returns a query to get users with no activity for given interval of days.
1914 Inactive users are those who didn't read any notification, or had any activity where
1915 the user is the activity's actor, during `inactivity_threshold` days.
1916 Deactivated users will not appear in this list.
1920 iex> Pleroma.User.list_inactive_users()
1923 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1924 def list_inactive_users_query(inactivity_threshold \\ 7) do
1925 negative_inactivity_threshold = -inactivity_threshold
1926 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1927 # Subqueries are not supported in `where` clauses, join gets too complicated.
1928 has_read_notifications =
1929 from(n in Pleroma.Notification,
1930 where: n.seen == true,
1932 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1935 |> Pleroma.Repo.all()
1937 from(u in Pleroma.User,
1938 left_join: a in Pleroma.Activity,
1939 on: u.ap_id == a.actor,
1940 where: not is_nil(u.nickname),
1941 where: u.deactivated != ^true,
1942 where: u.id not in ^has_read_notifications,
1945 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1946 is_nil(max(a.inserted_at))
1951 Enable or disable email notifications for user
1955 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1956 Pleroma.User{email_notifications: %{"digest" => true}}
1958 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1959 Pleroma.User{email_notifications: %{"digest" => false}}
1961 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1962 {:ok, t()} | {:error, Ecto.Changeset.t()}
1963 def switch_email_notifications(user, type, status) do
1964 User.update_email_notifications(user, %{type => status})
1968 Set `last_digest_emailed_at` value for the user to current time
1970 @spec touch_last_digest_emailed_at(t()) :: t()
1971 def touch_last_digest_emailed_at(user) do
1972 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1974 {:ok, updated_user} =
1976 |> change(%{last_digest_emailed_at: now})
1977 |> update_and_set_cache()
1982 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1983 def toggle_confirmation(%User{} = user) do
1985 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1986 |> update_and_set_cache()
1989 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1990 def toggle_confirmation(users) do
1991 Enum.map(users, &toggle_confirmation/1)
1994 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1998 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1999 # use instance-default
2000 config = Config.get([:assets, :mascots])
2001 default_mascot = Config.get([:assets, :default_mascot])
2002 mascot = Keyword.get(config, default_mascot)
2005 "id" => "default-mascot",
2006 "url" => mascot[:url],
2007 "preview_url" => mascot[:url],
2009 "mime_type" => mascot[:mime_type]
2014 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
2016 def ensure_keys_present(%User{} = user) do
2017 with {:ok, pem} <- Keys.generate_rsa_pem() do
2019 |> cast(%{keys: pem}, [:keys])
2020 |> validate_required([:keys])
2021 |> update_and_set_cache()
2025 def get_ap_ids_by_nicknames(nicknames) do
2027 where: u.nickname in ^nicknames,
2033 defdelegate search(query, opts \\ []), to: User.Search
2035 defp put_password_hash(
2036 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2038 change(changeset, password_hash: Pbkdf2.hash_pwd_salt(password))
2041 defp put_password_hash(changeset), do: changeset
2043 def is_internal_user?(%User{nickname: nil}), do: true
2044 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2045 def is_internal_user?(_), do: false
2047 # A hack because user delete activities have a fake id for whatever reason
2048 # TODO: Get rid of this
2049 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2051 def get_delivered_users_by_object_id(object_id) do
2053 inner_join: delivery in assoc(u, :deliveries),
2054 where: delivery.object_id == ^object_id
2059 def change_email(user, email) do
2061 |> cast(%{email: email}, [:email])
2062 |> validate_required([:email])
2063 |> unique_constraint(:email)
2064 |> validate_format(:email, @email_regex)
2065 |> update_and_set_cache()
2068 # Internal function; public one is `deactivate/2`
2069 defp set_activation_status(user, deactivated) do
2071 |> cast(%{deactivated: deactivated}, [:deactivated])
2072 |> update_and_set_cache()
2075 def update_banner(user, banner) do
2077 |> cast(%{banner: banner}, [:banner])
2078 |> update_and_set_cache()
2081 def update_background(user, background) do
2083 |> cast(%{background: background}, [:background])
2084 |> update_and_set_cache()
2087 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
2090 moderator: is_moderator
2094 def validate_fields(changeset, remote? \\ false) do
2095 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2096 limit = Config.get([:instance, limit_name], 0)
2099 |> validate_length(:fields, max: limit)
2100 |> validate_change(:fields, fn :fields, fields ->
2101 if Enum.all?(fields, &valid_field?/1) do
2109 defp valid_field?(%{"name" => name, "value" => value}) do
2110 name_limit = Config.get([:instance, :account_field_name_length], 255)
2111 value_limit = Config.get([:instance, :account_field_value_length], 255)
2113 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2114 String.length(value) <= value_limit
2117 defp valid_field?(_), do: false
2119 defp truncate_field(%{"name" => name, "value" => value}) do
2121 String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
2124 String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
2126 %{"name" => name, "value" => value}
2129 def admin_api_update(user, params) do
2136 |> update_and_set_cache()
2139 @doc "Signs user out of all applications"
2140 def global_sign_out(user) do
2141 OAuth.Authorization.delete_user_authorizations(user)
2142 OAuth.Token.delete_user_tokens(user)
2145 def mascot_update(user, url) do
2147 |> cast(%{mascot: url}, [:mascot])
2148 |> validate_required([:mascot])
2149 |> update_and_set_cache()
2152 def mastodon_settings_update(user, settings) do
2154 |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
2155 |> validate_required([:mastofe_settings])
2156 |> update_and_set_cache()
2159 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2160 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2162 if need_confirmation? do
2164 confirmation_pending: true,
2165 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2169 confirmation_pending: false,
2170 confirmation_token: nil
2174 cast(user, params, [:confirmation_pending, :confirmation_token])
2177 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2178 if id not in user.pinned_activities do
2179 max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
2180 params = %{pinned_activities: user.pinned_activities ++ [id]}
2183 |> cast(params, [:pinned_activities])
2184 |> validate_length(:pinned_activities,
2185 max: max_pinned_statuses,
2186 message: "You have already pinned the maximum number of statuses"
2191 |> update_and_set_cache()
2194 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2195 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2198 |> cast(params, [:pinned_activities])
2199 |> update_and_set_cache()
2202 def update_email_notifications(user, settings) do
2203 email_notifications =
2204 user.email_notifications
2205 |> Map.merge(settings)
2206 |> Map.take(["digest"])
2208 params = %{email_notifications: email_notifications}
2209 fields = [:email_notifications]
2212 |> cast(params, fields)
2213 |> validate_required(fields)
2214 |> update_and_set_cache()
2217 defp set_domain_blocks(user, domain_blocks) do
2218 params = %{domain_blocks: domain_blocks}
2221 |> cast(params, [:domain_blocks])
2222 |> validate_required([:domain_blocks])
2223 |> update_and_set_cache()
2226 def block_domain(user, domain_blocked) do
2227 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2230 def unblock_domain(user, domain_blocked) do
2231 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2234 @spec add_to_block(User.t(), User.t()) ::
2235 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2236 defp add_to_block(%User{} = user, %User{} = blocked) do
2237 UserRelationship.create_block(user, blocked)
2240 @spec add_to_block(User.t(), User.t()) ::
2241 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2242 defp remove_from_block(%User{} = user, %User{} = blocked) do
2243 UserRelationship.delete_block(user, blocked)
2246 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2247 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2248 {:ok, user_notification_mute} <-
2249 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2251 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2255 defp remove_from_mutes(user, %User{} = muted_user) do
2256 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2257 {:ok, user_notification_mute} <-
2258 UserRelationship.delete_notification_mute(user, muted_user) do
2259 {:ok, [user_mute, user_notification_mute]}
2263 def set_invisible(user, invisible) do
2264 params = %{invisible: invisible}
2267 |> cast(params, [:invisible])
2268 |> validate_required([:invisible])
2269 |> update_and_set_cache()
2272 def sanitize_html(%User{} = user) do
2273 sanitize_html(user, nil)
2276 # User data that mastodon isn't filtering (treated as plaintext):
2279 def sanitize_html(%User{} = user, filter) do
2281 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2284 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2289 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2290 |> Map.put(:fields, fields)