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 params = Map.put_new(params, :accepts_chat_messages, true)
647 if is_nil(opts[:need_confirmation]) do
648 Config.get([:instance, :account_activation_required])
650 opts[:need_confirmation]
654 if is_nil(opts[:need_approval]) do
655 Config.get([:instance, :account_approval_required])
661 |> confirmation_changeset(need_confirmation: need_confirmation?)
662 |> approval_changeset(need_approval: need_approval?)
670 :password_confirmation,
672 :accepts_chat_messages,
675 |> validate_required([:name, :nickname, :password, :password_confirmation])
676 |> validate_confirmation(:password)
677 |> unique_constraint(:email)
678 |> unique_constraint(:nickname)
679 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
680 |> validate_format(:nickname, local_nickname_regex())
681 |> validate_format(:email, @email_regex)
682 |> validate_length(:bio, max: bio_limit)
683 |> validate_length(:name, min: 1, max: name_limit)
684 |> maybe_validate_required_email(opts[:external])
687 |> unique_constraint(:ap_id)
688 |> put_following_and_follower_address()
691 def maybe_validate_required_email(changeset, true), do: changeset
693 def maybe_validate_required_email(changeset, _) do
694 if Config.get([:instance, :account_activation_required]) do
695 validate_required(changeset, [:email])
701 defp put_ap_id(changeset) do
702 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
703 put_change(changeset, :ap_id, ap_id)
706 defp put_following_and_follower_address(changeset) do
707 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
710 |> put_change(:follower_address, followers)
713 defp autofollow_users(user) do
714 candidates = Config.get([:instance, :autofollowed_nicknames])
717 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
720 follow_all(user, autofollowed_users)
723 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
724 def register(%Ecto.Changeset{} = changeset) do
725 with {:ok, user} <- Repo.insert(changeset) do
726 post_register_action(user)
730 def post_register_action(%User{} = user) do
731 with {:ok, user} <- autofollow_users(user),
732 {:ok, user} <- set_cache(user),
733 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
734 {:ok, _} <- try_send_confirmation_email(user) do
739 @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
740 def try_send_confirmation_email(%User{confirmation_pending: true} = user) do
741 if Config.get([:instance, :account_activation_required]) do
742 send_confirmation_email(user)
749 def try_send_confirmation_email(_), do: {:ok, :noop}
751 @spec send_confirmation_email(Uset.t()) :: User.t()
752 def send_confirmation_email(%User{} = user) do
754 |> Pleroma.Emails.UserEmail.account_confirmation_email()
755 |> Pleroma.Emails.Mailer.deliver_async()
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 approve(users) when is_list(users) do
1494 Repo.transaction(fn ->
1495 Enum.map(users, fn user ->
1496 with {:ok, user} <- approve(user), do: user
1501 def approve(%User{} = user) do
1502 change(user, approval_pending: false)
1503 |> update_and_set_cache()
1506 def update_notification_settings(%User{} = user, settings) do
1508 |> cast(%{notification_settings: settings}, [])
1509 |> cast_embed(:notification_settings)
1510 |> validate_required([:notification_settings])
1511 |> update_and_set_cache()
1514 def delete(users) when is_list(users) do
1515 for user <- users, do: delete(user)
1518 def delete(%User{} = user) do
1519 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1522 defp delete_and_invalidate_cache(%User{} = user) do
1523 invalidate_cache(user)
1527 defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user)
1529 defp delete_or_deactivate(%User{local: true} = user) do
1530 status = account_status(user)
1533 :confirmation_pending -> delete_and_invalidate_cache(user)
1534 :approval_pending -> delete_and_invalidate_cache(user)
1537 |> change(%{deactivated: true, email: nil})
1538 |> update_and_set_cache()
1542 def perform(:force_password_reset, user), do: force_password_reset(user)
1544 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1545 def perform(:delete, %User{} = user) do
1546 # Remove all relationships
1549 |> Enum.each(fn follower ->
1550 ActivityPub.unfollow(follower, user)
1551 unfollow(follower, user)
1556 |> Enum.each(fn followed ->
1557 ActivityPub.unfollow(user, followed)
1558 unfollow(user, followed)
1561 delete_user_activities(user)
1562 delete_notifications_from_user_activities(user)
1564 delete_outgoing_pending_follow_requests(user)
1566 delete_or_deactivate(user)
1569 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1571 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1572 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1573 when is_list(blocked_identifiers) do
1575 blocked_identifiers,
1576 fn blocked_identifier ->
1577 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1578 {:ok, _block} <- CommonAPI.block(blocker, blocked) do
1582 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1589 def perform(:follow_import, %User{} = follower, followed_identifiers)
1590 when is_list(followed_identifiers) do
1592 followed_identifiers,
1593 fn followed_identifier ->
1594 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1595 {:ok, follower} <- maybe_direct_follow(follower, followed),
1596 {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do
1600 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1607 @spec external_users_query() :: Ecto.Query.t()
1608 def external_users_query do
1616 @spec external_users(keyword()) :: [User.t()]
1617 def external_users(opts \\ []) do
1619 external_users_query()
1620 |> select([u], struct(u, [:id, :ap_id]))
1624 do: where(query, [u], u.id > ^opts[:max_id]),
1629 do: limit(query, ^opts[:limit]),
1635 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1636 BackgroundWorker.enqueue("blocks_import", %{
1637 "blocker_id" => blocker.id,
1638 "blocked_identifiers" => blocked_identifiers
1642 def follow_import(%User{} = follower, followed_identifiers)
1643 when is_list(followed_identifiers) do
1644 BackgroundWorker.enqueue("follow_import", %{
1645 "follower_id" => follower.id,
1646 "followed_identifiers" => followed_identifiers
1650 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1652 |> join(:inner, [n], activity in assoc(n, :activity))
1653 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1654 |> Repo.delete_all()
1657 def delete_user_activities(%User{ap_id: ap_id} = user) do
1659 |> Activity.Queries.by_actor()
1660 |> RepoStreamer.chunk_stream(50)
1661 |> Stream.each(fn activities ->
1662 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1667 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1668 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1669 {:ok, delete_data, _} <- Builder.delete(user, object) do
1670 Pipeline.common_pipeline(delete_data, local: user.local)
1672 {:find_object, nil} ->
1673 # We have the create activity, but not the object, it was probably pruned.
1674 # Insert a tombstone and try again
1675 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1676 {:ok, _tombstone} <- Object.create(tombstone_data) do
1677 delete_activity(activity, user)
1681 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1682 Logger.error("Error: #{inspect(e)}")
1686 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1687 when type in ["Like", "Announce"] do
1688 {:ok, undo, _} = Builder.undo(user, activity)
1689 Pipeline.common_pipeline(undo, local: user.local)
1692 defp delete_activity(_activity, _user), do: "Doing nothing"
1694 defp delete_outgoing_pending_follow_requests(user) do
1696 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1697 |> Repo.delete_all()
1700 def html_filter_policy(%User{no_rich_text: true}) do
1701 Pleroma.HTML.Scrubber.TwitterText
1704 def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
1706 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1708 def get_or_fetch_by_ap_id(ap_id) do
1709 cached_user = get_cached_by_ap_id(ap_id)
1711 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
1713 case {cached_user, maybe_fetched_user} do
1714 {_, {:ok, %User{} = user}} ->
1717 {%User{} = user, _} ->
1721 {:error, :not_found}
1726 Creates an internal service actor by URI if missing.
1727 Optionally takes nickname for addressing.
1729 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1730 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1732 case get_cached_by_ap_id(uri) do
1734 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1735 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1739 %User{invisible: false} = user ->
1749 @spec set_invisible(User.t()) :: {:ok, User.t()}
1750 defp set_invisible(user) do
1752 |> change(%{invisible: true})
1753 |> update_and_set_cache()
1756 @spec create_service_actor(String.t(), String.t()) ::
1757 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1758 defp create_service_actor(uri, nickname) do
1764 follower_address: uri <> "/followers"
1767 |> unique_constraint(:nickname)
1772 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1775 |> :public_key.pem_decode()
1777 |> :public_key.pem_entry_decode()
1782 def public_key(_), do: {:error, "key not found"}
1784 def get_public_key_for_ap_id(ap_id) do
1785 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1786 {:ok, public_key} <- public_key(user) do
1793 def ap_enabled?(%User{local: true}), do: true
1794 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1795 def ap_enabled?(_), do: false
1797 @doc "Gets or fetch a user by uri or nickname."
1798 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1799 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1800 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1802 # wait a period of time and return newest version of the User structs
1803 # this is because we have synchronous follow APIs and need to simulate them
1804 # with an async handshake
1805 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1806 with %User{} = a <- get_cached_by_id(a.id),
1807 %User{} = b <- get_cached_by_id(b.id) do
1814 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1815 with :ok <- :timer.sleep(timeout),
1816 %User{} = a <- get_cached_by_id(a.id),
1817 %User{} = b <- get_cached_by_id(b.id) do
1824 def parse_bio(bio) when is_binary(bio) and bio != "" do
1826 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1830 def parse_bio(_), do: ""
1832 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1833 # TODO: get profile URLs other than user.ap_id
1834 profile_urls = [user.ap_id]
1837 |> CommonUtils.format_input("text/plain",
1838 mentions_format: :full,
1839 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1844 def parse_bio(_, _), do: ""
1846 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1847 Repo.transaction(fn ->
1848 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1852 def tag(nickname, tags) when is_binary(nickname),
1853 do: tag(get_by_nickname(nickname), tags)
1855 def tag(%User{} = user, tags),
1856 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1858 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1859 Repo.transaction(fn ->
1860 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1864 def untag(nickname, tags) when is_binary(nickname),
1865 do: untag(get_by_nickname(nickname), tags)
1867 def untag(%User{} = user, tags),
1868 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1870 defp update_tags(%User{} = user, new_tags) do
1871 {:ok, updated_user} =
1873 |> change(%{tags: new_tags})
1874 |> update_and_set_cache()
1879 defp normalize_tags(tags) do
1882 |> Enum.map(&String.downcase/1)
1885 defp local_nickname_regex do
1886 if Config.get([:instance, :extended_nickname_format]) do
1887 @extended_local_nickname_regex
1889 @strict_local_nickname_regex
1893 def local_nickname(nickname_or_mention) do
1896 |> String.split("@")
1900 def full_nickname(nickname_or_mention),
1901 do: String.trim_leading(nickname_or_mention, "@")
1903 def error_user(ap_id) do
1907 nickname: "erroruser@example.com",
1908 inserted_at: NaiveDateTime.utc_now()
1912 @spec all_superusers() :: [User.t()]
1913 def all_superusers do
1914 User.Query.build(%{super_users: true, local: true, deactivated: false})
1918 def muting_reblogs?(%User{} = user, %User{} = target) do
1919 UserRelationship.reblog_mute_exists?(user, target)
1922 def showing_reblogs?(%User{} = user, %User{} = target) do
1923 not muting_reblogs?(user, target)
1927 The function returns a query to get users with no activity for given interval of days.
1928 Inactive users are those who didn't read any notification, or had any activity where
1929 the user is the activity's actor, during `inactivity_threshold` days.
1930 Deactivated users will not appear in this list.
1934 iex> Pleroma.User.list_inactive_users()
1937 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1938 def list_inactive_users_query(inactivity_threshold \\ 7) do
1939 negative_inactivity_threshold = -inactivity_threshold
1940 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1941 # Subqueries are not supported in `where` clauses, join gets too complicated.
1942 has_read_notifications =
1943 from(n in Pleroma.Notification,
1944 where: n.seen == true,
1946 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1949 |> Pleroma.Repo.all()
1951 from(u in Pleroma.User,
1952 left_join: a in Pleroma.Activity,
1953 on: u.ap_id == a.actor,
1954 where: not is_nil(u.nickname),
1955 where: u.deactivated != ^true,
1956 where: u.id not in ^has_read_notifications,
1959 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1960 is_nil(max(a.inserted_at))
1965 Enable or disable email notifications for user
1969 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1970 Pleroma.User{email_notifications: %{"digest" => true}}
1972 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1973 Pleroma.User{email_notifications: %{"digest" => false}}
1975 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1976 {:ok, t()} | {:error, Ecto.Changeset.t()}
1977 def switch_email_notifications(user, type, status) do
1978 User.update_email_notifications(user, %{type => status})
1982 Set `last_digest_emailed_at` value for the user to current time
1984 @spec touch_last_digest_emailed_at(t()) :: t()
1985 def touch_last_digest_emailed_at(user) do
1986 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1988 {:ok, updated_user} =
1990 |> change(%{last_digest_emailed_at: now})
1991 |> update_and_set_cache()
1996 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1997 def toggle_confirmation(%User{} = user) do
1999 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
2000 |> update_and_set_cache()
2003 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
2004 def toggle_confirmation(users) do
2005 Enum.map(users, &toggle_confirmation/1)
2008 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
2012 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
2013 # use instance-default
2014 config = Config.get([:assets, :mascots])
2015 default_mascot = Config.get([:assets, :default_mascot])
2016 mascot = Keyword.get(config, default_mascot)
2019 "id" => "default-mascot",
2020 "url" => mascot[:url],
2021 "preview_url" => mascot[:url],
2023 "mime_type" => mascot[:mime_type]
2028 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
2030 def ensure_keys_present(%User{} = user) do
2031 with {:ok, pem} <- Keys.generate_rsa_pem() do
2033 |> cast(%{keys: pem}, [:keys])
2034 |> validate_required([:keys])
2035 |> update_and_set_cache()
2039 def get_ap_ids_by_nicknames(nicknames) do
2041 where: u.nickname in ^nicknames,
2047 defdelegate search(query, opts \\ []), to: User.Search
2049 defp put_password_hash(
2050 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2052 change(changeset, password_hash: Pbkdf2.hash_pwd_salt(password))
2055 defp put_password_hash(changeset), do: changeset
2057 def is_internal_user?(%User{nickname: nil}), do: true
2058 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2059 def is_internal_user?(_), do: false
2061 # A hack because user delete activities have a fake id for whatever reason
2062 # TODO: Get rid of this
2063 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2065 def get_delivered_users_by_object_id(object_id) do
2067 inner_join: delivery in assoc(u, :deliveries),
2068 where: delivery.object_id == ^object_id
2073 def change_email(user, email) do
2075 |> cast(%{email: email}, [:email])
2076 |> validate_required([:email])
2077 |> unique_constraint(:email)
2078 |> validate_format(:email, @email_regex)
2079 |> update_and_set_cache()
2082 # Internal function; public one is `deactivate/2`
2083 defp set_activation_status(user, deactivated) do
2085 |> cast(%{deactivated: deactivated}, [:deactivated])
2086 |> update_and_set_cache()
2089 def update_banner(user, banner) do
2091 |> cast(%{banner: banner}, [:banner])
2092 |> update_and_set_cache()
2095 def update_background(user, background) do
2097 |> cast(%{background: background}, [:background])
2098 |> update_and_set_cache()
2101 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
2104 moderator: is_moderator
2108 def validate_fields(changeset, remote? \\ false) do
2109 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2110 limit = Config.get([:instance, limit_name], 0)
2113 |> validate_length(:fields, max: limit)
2114 |> validate_change(:fields, fn :fields, fields ->
2115 if Enum.all?(fields, &valid_field?/1) do
2123 defp valid_field?(%{"name" => name, "value" => value}) do
2124 name_limit = Config.get([:instance, :account_field_name_length], 255)
2125 value_limit = Config.get([:instance, :account_field_value_length], 255)
2127 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2128 String.length(value) <= value_limit
2131 defp valid_field?(_), do: false
2133 defp truncate_field(%{"name" => name, "value" => value}) do
2135 String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
2138 String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
2140 %{"name" => name, "value" => value}
2143 def admin_api_update(user, params) do
2150 |> update_and_set_cache()
2153 @doc "Signs user out of all applications"
2154 def global_sign_out(user) do
2155 OAuth.Authorization.delete_user_authorizations(user)
2156 OAuth.Token.delete_user_tokens(user)
2159 def mascot_update(user, url) do
2161 |> cast(%{mascot: url}, [:mascot])
2162 |> validate_required([:mascot])
2163 |> update_and_set_cache()
2166 def mastodon_settings_update(user, settings) do
2168 |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
2169 |> validate_required([:mastofe_settings])
2170 |> update_and_set_cache()
2173 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2174 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2176 if need_confirmation? do
2178 confirmation_pending: true,
2179 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2183 confirmation_pending: false,
2184 confirmation_token: nil
2188 cast(user, params, [:confirmation_pending, :confirmation_token])
2191 @spec approval_changeset(User.t(), keyword()) :: Changeset.t()
2192 def approval_changeset(user, need_approval: need_approval?) do
2193 params = if need_approval?, do: %{approval_pending: true}, else: %{approval_pending: false}
2194 cast(user, params, [:approval_pending])
2197 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2198 if id not in user.pinned_activities do
2199 max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
2200 params = %{pinned_activities: user.pinned_activities ++ [id]}
2203 |> cast(params, [:pinned_activities])
2204 |> validate_length(:pinned_activities,
2205 max: max_pinned_statuses,
2206 message: "You have already pinned the maximum number of statuses"
2211 |> update_and_set_cache()
2214 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2215 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2218 |> cast(params, [:pinned_activities])
2219 |> update_and_set_cache()
2222 def update_email_notifications(user, settings) do
2223 email_notifications =
2224 user.email_notifications
2225 |> Map.merge(settings)
2226 |> Map.take(["digest"])
2228 params = %{email_notifications: email_notifications}
2229 fields = [:email_notifications]
2232 |> cast(params, fields)
2233 |> validate_required(fields)
2234 |> update_and_set_cache()
2237 defp set_domain_blocks(user, domain_blocks) do
2238 params = %{domain_blocks: domain_blocks}
2241 |> cast(params, [:domain_blocks])
2242 |> validate_required([:domain_blocks])
2243 |> update_and_set_cache()
2246 def block_domain(user, domain_blocked) do
2247 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2250 def unblock_domain(user, domain_blocked) do
2251 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2254 @spec add_to_block(User.t(), User.t()) ::
2255 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2256 defp add_to_block(%User{} = user, %User{} = blocked) do
2257 UserRelationship.create_block(user, blocked)
2260 @spec add_to_block(User.t(), User.t()) ::
2261 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2262 defp remove_from_block(%User{} = user, %User{} = blocked) do
2263 UserRelationship.delete_block(user, blocked)
2266 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2267 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2268 {:ok, user_notification_mute} <-
2269 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2271 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2275 defp remove_from_mutes(user, %User{} = muted_user) do
2276 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2277 {:ok, user_notification_mute} <-
2278 UserRelationship.delete_notification_mute(user, muted_user) do
2279 {:ok, [user_mute, user_notification_mute]}
2283 def set_invisible(user, invisible) do
2284 params = %{invisible: invisible}
2287 |> cast(params, [:invisible])
2288 |> validate_required([:invisible])
2289 |> update_and_set_cache()
2292 def sanitize_html(%User{} = user) do
2293 sanitize_html(user, nil)
2296 # User data that mastodon isn't filtering (treated as plaintext):
2299 def sanitize_html(%User{} = user, filter) do
2301 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2304 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2309 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2310 |> Map.put(:fields, fields)