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)
93 field(:local, :boolean, default: true)
94 field(:follower_address, :string)
95 field(:following_address, :string)
96 field(:search_rank, :float, virtual: true)
97 field(:search_type, :integer, virtual: true)
98 field(:tags, {:array, :string}, default: [])
99 field(:last_refreshed_at, :naive_datetime_usec)
100 field(:last_digest_emailed_at, :naive_datetime)
101 field(:banner, :map, default: %{})
102 field(:background, :map, default: %{})
103 field(:note_count, :integer, default: 0)
104 field(:follower_count, :integer, default: 0)
105 field(:following_count, :integer, default: 0)
106 field(:locked, :boolean, default: false)
107 field(:confirmation_pending, :boolean, default: false)
108 field(:password_reset_pending, :boolean, default: false)
109 field(:confirmation_token, :string, default: nil)
110 field(:default_scope, :string, default: "public")
111 field(:domain_blocks, {:array, :string}, default: [])
112 field(:deactivated, :boolean, default: false)
113 field(:no_rich_text, :boolean, default: false)
114 field(:ap_enabled, :boolean, default: false)
115 field(:is_moderator, :boolean, default: false)
116 field(:is_admin, :boolean, default: false)
117 field(:show_role, :boolean, default: true)
118 field(:settings, :map, default: nil)
119 field(:uri, 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)
143 :notification_settings,
144 Pleroma.User.NotificationSetting,
148 has_many(:notifications, Notification)
149 has_many(:registrations, Registration)
150 has_many(:deliveries, Delivery)
152 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
153 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
155 for {relationship_type,
157 {outgoing_relation, outgoing_relation_target},
158 {incoming_relation, incoming_relation_source}
159 ]} <- @user_relationships_config do
160 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
161 # :notification_muter_mutes, :subscribee_subscriptions
162 has_many(outgoing_relation, UserRelationship,
163 foreign_key: :source_id,
164 where: [relationship_type: relationship_type]
167 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
168 # :notification_mutee_mutes, :subscriber_subscriptions
169 has_many(incoming_relation, UserRelationship,
170 foreign_key: :target_id,
171 where: [relationship_type: relationship_type]
174 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
175 # :notification_muted_users, :subscriber_users
176 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
178 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
179 # :notification_muter_users, :subscribee_users
180 has_many(incoming_relation_source, through: [incoming_relation, :source])
183 # `:blocks` is deprecated (replaced with `blocked_users` relation)
184 field(:blocks, {:array, :string}, default: [])
185 # `:mutes` is deprecated (replaced with `muted_users` relation)
186 field(:mutes, {:array, :string}, default: [])
187 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
188 field(:muted_reblogs, {:array, :string}, default: [])
189 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
190 field(:muted_notifications, {:array, :string}, default: [])
191 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
192 field(:subscribers, {:array, :string}, default: [])
195 :multi_factor_authentication_settings,
203 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
204 @user_relationships_config do
205 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
206 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
207 # `def subscriber_users/2`
208 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
209 target_users_query = assoc(user, unquote(outgoing_relation_target))
211 if restrict_deactivated? do
212 restrict_deactivated(target_users_query)
218 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
219 # `def notification_muted_users/2`, `def subscriber_users/2`
220 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
222 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
224 restrict_deactivated?
229 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
230 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
231 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
233 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
235 restrict_deactivated?
237 |> select([u], u.ap_id)
243 Dumps Flake Id to SQL-compatible format (16-byte UUID).
244 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
246 def binary_id(source_id) when is_binary(source_id) do
247 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
254 def binary_id(source_ids) when is_list(source_ids) do
255 Enum.map(source_ids, &binary_id/1)
258 def binary_id(%User{} = user), do: binary_id(user.id)
260 @doc "Returns status account"
261 @spec account_status(User.t()) :: account_status()
262 def account_status(%User{deactivated: true}), do: :deactivated
263 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
265 def account_status(%User{confirmation_pending: true}) do
266 case Config.get([:instance, :account_activation_required]) do
267 true -> :confirmation_pending
272 def account_status(%User{}), do: :active
274 @spec visible_for?(User.t(), User.t() | nil) :: boolean()
275 def visible_for?(user, for_user \\ nil)
277 def visible_for?(%User{invisible: true}, _), do: false
279 def visible_for?(%User{id: user_id}, %User{id: user_id}), do: true
281 def visible_for?(%User{local: local} = user, nil) do
287 if Config.get([:restrict_unauthenticated, :profiles, cfg_key]),
289 else: account_status(user) == :active
292 def visible_for?(%User{} = user, for_user) do
293 account_status(user) == :active || superuser?(for_user)
296 def visible_for?(_, _), do: false
298 @spec superuser?(User.t()) :: boolean()
299 def superuser?(%User{local: true, is_admin: true}), do: true
300 def superuser?(%User{local: true, is_moderator: true}), do: true
301 def superuser?(_), do: false
303 @spec invisible?(User.t()) :: boolean()
304 def invisible?(%User{invisible: true}), do: true
305 def invisible?(_), do: false
307 def avatar_url(user, options \\ []) do
309 %{"url" => [%{"href" => href} | _]} ->
313 unless options[:no_default] do
314 Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png")
319 def banner_url(user, options \\ []) do
321 %{"url" => [%{"href" => href} | _]} -> href
322 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
326 # Should probably be renamed or removed
327 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
329 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
330 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
332 @spec ap_following(User.t()) :: String.t()
333 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
334 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
336 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
337 def restrict_deactivated(query) do
338 from(u in query, where: u.deactivated != ^true)
341 defdelegate following_count(user), to: FollowingRelationship
343 defp truncate_fields_param(params) do
344 if Map.has_key?(params, :fields) do
345 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
351 defp truncate_if_exists(params, key, max_length) do
352 if Map.has_key?(params, key) and is_binary(params[key]) do
353 {value, _chopped} = String.split_at(params[key], max_length)
354 Map.put(params, key, value)
360 defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
362 defp fix_follower_address(%{nickname: nickname} = params),
363 do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
365 defp fix_follower_address(params), do: params
367 def remote_user_changeset(struct \\ %User{local: false}, params) do
368 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
369 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
372 case params[:name] do
373 name when is_binary(name) and byte_size(name) > 0 -> name
374 _ -> params[:nickname]
379 |> Map.put(:name, name)
380 |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
381 |> truncate_if_exists(:name, name_limit)
382 |> truncate_if_exists(:bio, bio_limit)
383 |> truncate_fields_param()
384 |> fix_follower_address()
408 :hide_followers_count,
419 |> validate_required([:name, :ap_id])
420 |> unique_constraint(:nickname)
421 |> validate_format(:nickname, @email_regex)
422 |> validate_length(:bio, max: bio_limit)
423 |> validate_length(:name, max: name_limit)
424 |> validate_fields(true)
427 def update_changeset(struct, params \\ %{}) do
428 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
429 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
449 :hide_followers_count,
452 :allow_following_move,
455 :skip_thread_containment,
458 :pleroma_settings_store,
464 |> unique_constraint(:nickname)
465 |> validate_format(:nickname, local_nickname_regex())
466 |> validate_length(:bio, max: bio_limit)
467 |> validate_length(:name, min: 1, max: name_limit)
470 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
471 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
472 |> put_change_if_present(:banner, &put_upload(&1, :banner))
473 |> put_change_if_present(:background, &put_upload(&1, :background))
474 |> put_change_if_present(
475 :pleroma_settings_store,
476 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
478 |> validate_fields(false)
481 defp put_fields(changeset) do
482 if raw_fields = get_change(changeset, :raw_fields) do
485 |> Enum.filter(fn %{"name" => n} -> n != "" end)
489 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
492 |> put_change(:raw_fields, raw_fields)
493 |> put_change(:fields, fields)
499 defp parse_fields(value) do
501 |> Formatter.linkify(mentions_format: :full)
505 defp put_emoji(changeset) do
506 bio = get_change(changeset, :bio)
507 name = get_change(changeset, :name)
510 emoji = Map.merge(Emoji.Formatter.get_emoji_map(bio), Emoji.Formatter.get_emoji_map(name))
511 put_change(changeset, :emoji, emoji)
517 defp put_change_if_present(changeset, map_field, value_function) do
518 if value = get_change(changeset, map_field) do
519 with {:ok, new_value} <- value_function.(value) do
520 put_change(changeset, map_field, new_value)
529 defp put_upload(value, type) do
530 with %Plug.Upload{} <- value,
531 {:ok, object} <- ActivityPub.upload(value, type: type) do
536 def update_as_admin_changeset(struct, params) do
538 |> update_changeset(params)
539 |> cast(params, [:email])
540 |> delete_change(:also_known_as)
541 |> unique_constraint(:email)
542 |> validate_format(:email, @email_regex)
543 |> validate_inclusion(:actor_type, ["Person", "Service"])
546 @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
547 def update_as_admin(user, params) do
548 params = Map.put(params, "password_confirmation", params["password"])
549 changeset = update_as_admin_changeset(user, params)
551 if params["password"] do
552 reset_password(user, changeset, params)
554 User.update_and_set_cache(changeset)
558 def password_update_changeset(struct, params) do
560 |> cast(params, [:password, :password_confirmation])
561 |> validate_required([:password, :password_confirmation])
562 |> validate_confirmation(:password)
563 |> put_password_hash()
564 |> put_change(:password_reset_pending, false)
567 @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
568 def reset_password(%User{} = user, params) do
569 reset_password(user, user, params)
572 def reset_password(%User{id: user_id} = user, struct, params) do
575 |> Multi.update(:user, password_update_changeset(struct, params))
576 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
577 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
579 case Repo.transaction(multi) do
580 {:ok, %{user: user} = _} -> set_cache(user)
581 {:error, _, changeset, _} -> {:error, changeset}
585 def update_password_reset_pending(user, value) do
588 |> put_change(:password_reset_pending, value)
589 |> update_and_set_cache()
592 def force_password_reset_async(user) do
593 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
596 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
597 def force_password_reset(user), do: update_password_reset_pending(user, true)
599 def register_changeset(struct, params \\ %{}, opts \\ []) do
600 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
601 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
604 if is_nil(opts[:need_confirmation]) do
605 Pleroma.Config.get([:instance, :account_activation_required])
607 opts[:need_confirmation]
611 |> confirmation_changeset(need_confirmation: need_confirmation?)
619 :password_confirmation,
622 |> validate_required([:name, :nickname, :password, :password_confirmation])
623 |> validate_confirmation(:password)
624 |> unique_constraint(:email)
625 |> unique_constraint(:nickname)
626 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
627 |> validate_format(:nickname, local_nickname_regex())
628 |> validate_format(:email, @email_regex)
629 |> validate_length(:bio, max: bio_limit)
630 |> validate_length(:name, min: 1, max: name_limit)
631 |> maybe_validate_required_email(opts[:external])
634 |> unique_constraint(:ap_id)
635 |> put_following_and_follower_address()
638 def maybe_validate_required_email(changeset, true), do: changeset
640 def maybe_validate_required_email(changeset, _) do
641 if Pleroma.Config.get([:instance, :account_activation_required]) do
642 validate_required(changeset, [:email])
648 defp put_ap_id(changeset) do
649 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
650 put_change(changeset, :ap_id, ap_id)
653 defp put_following_and_follower_address(changeset) do
654 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
657 |> put_change(:follower_address, followers)
660 defp autofollow_users(user) do
661 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
664 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
667 follow_all(user, autofollowed_users)
670 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
671 def register(%Ecto.Changeset{} = changeset) do
672 with {:ok, user} <- Repo.insert(changeset) do
673 post_register_action(user)
677 def post_register_action(%User{} = user) do
678 with {:ok, user} <- autofollow_users(user),
679 {:ok, user} <- set_cache(user),
680 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
681 {:ok, _} <- try_send_confirmation_email(user) do
686 def try_send_confirmation_email(%User{} = user) do
687 if user.confirmation_pending &&
688 Pleroma.Config.get([:instance, :account_activation_required]) do
690 |> Pleroma.Emails.UserEmail.account_confirmation_email()
691 |> Pleroma.Emails.Mailer.deliver_async()
699 def try_send_confirmation_email(users) do
700 Enum.each(users, &try_send_confirmation_email/1)
703 def needs_update?(%User{local: true}), do: false
705 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
707 def needs_update?(%User{local: false} = user) do
708 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
711 def needs_update?(_), do: true
713 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
715 # "Locked" (self-locked) users demand explicit authorization of follow requests
716 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
717 follow(follower, followed, :follow_pending)
720 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
721 follow(follower, followed)
724 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
725 if not ap_enabled?(followed) do
726 follow(follower, followed)
732 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
733 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
734 def follow_all(follower, followeds) do
736 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
737 |> Enum.each(&follow(follower, &1, :follow_accept))
742 defdelegate following(user), to: FollowingRelationship
744 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
745 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
748 followed.deactivated ->
749 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
751 deny_follow_blocked and blocks?(followed, follower) ->
752 {:error, "Could not follow user: #{followed.nickname} blocked you."}
755 FollowingRelationship.follow(follower, followed, state)
757 {:ok, _} = update_follower_count(followed)
760 |> update_following_count()
764 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
765 {:error, "Not subscribed!"}
768 @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
769 def unfollow(%User{} = follower, %User{} = followed) do
770 case do_unfollow(follower, followed) do
771 {:ok, follower, followed} ->
772 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
779 @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
780 defp do_unfollow(%User{} = follower, %User{} = followed) do
781 case get_follow_state(follower, followed) do
782 state when state in [:follow_pending, :follow_accept] ->
783 FollowingRelationship.unfollow(follower, followed)
784 {:ok, followed} = update_follower_count(followed)
788 |> update_following_count()
790 {:ok, follower, followed}
793 {:error, "Not subscribed!"}
797 defdelegate following?(follower, followed), to: FollowingRelationship
799 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
800 def get_follow_state(%User{} = follower, %User{} = following) do
801 following_relationship = FollowingRelationship.get(follower, following)
802 get_follow_state(follower, following, following_relationship)
805 def get_follow_state(
808 following_relationship
810 case {following_relationship, following.local} do
812 case Utils.fetch_latest_follow(follower, following) do
813 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
814 FollowingRelationship.state_to_enum(state)
820 {%{state: state}, _} ->
828 def locked?(%User{} = user) do
833 Repo.get_by(User, id: id)
836 def get_by_ap_id(ap_id) do
837 Repo.get_by(User, ap_id: ap_id)
840 def get_all_by_ap_id(ap_ids) do
841 from(u in __MODULE__,
842 where: u.ap_id in ^ap_ids
847 def get_all_by_ids(ids) do
848 from(u in __MODULE__, where: u.id in ^ids)
852 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
853 # of the ap_id and the domain and tries to get that user
854 def get_by_guessed_nickname(ap_id) do
855 domain = URI.parse(ap_id).host
856 name = List.last(String.split(ap_id, "/"))
857 nickname = "#{name}@#{domain}"
859 get_cached_by_nickname(nickname)
862 def set_cache({:ok, user}), do: set_cache(user)
863 def set_cache({:error, err}), do: {:error, err}
865 def set_cache(%User{} = user) do
866 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
867 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
868 Cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
872 def update_and_set_cache(struct, params) do
874 |> update_changeset(params)
875 |> update_and_set_cache()
878 def update_and_set_cache(changeset) do
879 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
884 def get_user_friends_ap_ids(user) do
885 from(u in User.get_friends_query(user), select: u.ap_id)
889 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
890 def get_cached_user_friends_ap_ids(user) do
891 Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
892 get_user_friends_ap_ids(user)
896 def invalidate_cache(user) do
897 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
898 Cachex.del(:user_cache, "nickname:#{user.nickname}")
899 Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
902 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
903 def get_cached_by_ap_id(ap_id) do
904 key = "ap_id:#{ap_id}"
906 with {:ok, nil} <- Cachex.get(:user_cache, key),
907 user when not is_nil(user) <- get_by_ap_id(ap_id),
908 {:ok, true} <- Cachex.put(:user_cache, key, user) do
916 def get_cached_by_id(id) do
920 Cachex.fetch!(:user_cache, key, fn _ ->
924 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
925 {:commit, user.ap_id}
931 get_cached_by_ap_id(ap_id)
934 def get_cached_by_nickname(nickname) do
935 key = "nickname:#{nickname}"
937 Cachex.fetch!(:user_cache, key, fn ->
938 case get_or_fetch_by_nickname(nickname) do
939 {:ok, user} -> {:commit, user}
940 {:error, _error} -> {:ignore, nil}
945 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
946 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
949 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
950 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
952 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
953 get_cached_by_nickname(nickname_or_id)
955 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
956 get_cached_by_nickname(nickname_or_id)
963 @spec get_by_nickname(String.t()) :: User.t() | nil
964 def get_by_nickname(nickname) do
965 Repo.get_by(User, nickname: nickname) ||
966 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
967 Repo.get_by(User, nickname: local_nickname(nickname))
971 def get_by_email(email), do: Repo.get_by(User, email: email)
973 def get_by_nickname_or_email(nickname_or_email) do
974 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
977 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
979 def get_or_fetch_by_nickname(nickname) do
980 with %User{} = user <- get_by_nickname(nickname) do
984 with [_nick, _domain] <- String.split(nickname, "@"),
985 {:ok, user} <- fetch_by_nickname(nickname) do
988 _e -> {:error, "not found " <> nickname}
993 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
994 def get_followers_query(%User{} = user, nil) do
995 User.Query.build(%{followers: user, deactivated: false})
998 def get_followers_query(user, page) do
1000 |> get_followers_query(nil)
1001 |> User.Query.paginate(page, 20)
1004 @spec get_followers_query(User.t()) :: Ecto.Query.t()
1005 def get_followers_query(user), do: get_followers_query(user, nil)
1007 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1008 def get_followers(user, page \\ nil) do
1010 |> get_followers_query(page)
1014 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1015 def get_external_followers(user, page \\ nil) do
1017 |> get_followers_query(page)
1018 |> User.Query.build(%{external: true})
1022 def get_followers_ids(user, page \\ nil) do
1024 |> get_followers_query(page)
1025 |> select([u], u.id)
1029 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1030 def get_friends_query(%User{} = user, nil) do
1031 User.Query.build(%{friends: user, deactivated: false})
1034 def get_friends_query(user, page) do
1036 |> get_friends_query(nil)
1037 |> User.Query.paginate(page, 20)
1040 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1041 def get_friends_query(user), do: get_friends_query(user, nil)
1043 def get_friends(user, page \\ nil) do
1045 |> get_friends_query(page)
1049 def get_friends_ap_ids(user) do
1051 |> get_friends_query(nil)
1052 |> select([u], u.ap_id)
1056 def get_friends_ids(user, page \\ nil) do
1058 |> get_friends_query(page)
1059 |> select([u], u.id)
1063 defdelegate get_follow_requests(user), to: FollowingRelationship
1065 def increase_note_count(%User{} = user) do
1067 |> where(id: ^user.id)
1068 |> update([u], inc: [note_count: 1])
1070 |> Repo.update_all([])
1072 {1, [user]} -> set_cache(user)
1077 def decrease_note_count(%User{} = user) do
1079 |> where(id: ^user.id)
1082 note_count: fragment("greatest(0, note_count - 1)")
1086 |> Repo.update_all([])
1088 {1, [user]} -> set_cache(user)
1093 def update_note_count(%User{} = user, note_count \\ nil) do
1098 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1104 |> cast(%{note_count: note_count}, [:note_count])
1105 |> update_and_set_cache()
1108 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1109 def maybe_fetch_follow_information(user) do
1110 with {:ok, user} <- fetch_follow_information(user) do
1114 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1120 def fetch_follow_information(user) do
1121 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1123 |> follow_information_changeset(info)
1124 |> update_and_set_cache()
1128 defp follow_information_changeset(user, params) do
1135 :hide_followers_count,
1140 @spec update_follower_count(User.t()) :: {:ok, User.t()}
1141 def update_follower_count(%User{} = user) do
1142 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
1143 follower_count = FollowingRelationship.follower_count(user)
1146 |> follow_information_changeset(%{follower_count: follower_count})
1147 |> update_and_set_cache
1149 {:ok, maybe_fetch_follow_information(user)}
1153 @spec update_following_count(User.t()) :: {:ok, User.t()}
1154 def update_following_count(%User{local: false} = user) do
1155 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1156 {:ok, maybe_fetch_follow_information(user)}
1162 def update_following_count(%User{local: true} = user) do
1163 following_count = FollowingRelationship.following_count(user)
1166 |> follow_information_changeset(%{following_count: following_count})
1167 |> update_and_set_cache()
1170 def set_unread_conversation_count(%User{local: true} = user) do
1171 unread_query = Participation.unread_conversation_count_for_user(user)
1174 |> join(:inner, [u], p in subquery(unread_query))
1176 set: [unread_conversation_count: p.count]
1178 |> where([u], u.id == ^user.id)
1180 |> Repo.update_all([])
1182 {1, [user]} -> set_cache(user)
1187 def set_unread_conversation_count(user), do: {:ok, user}
1189 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1191 Participation.unread_conversation_count_for_user(user)
1192 |> where([p], p.conversation_id == ^conversation.id)
1195 |> join(:inner, [u], p in subquery(unread_query))
1197 inc: [unread_conversation_count: 1]
1199 |> where([u], u.id == ^user.id)
1200 |> where([u, p], p.count == 0)
1202 |> Repo.update_all([])
1204 {1, [user]} -> set_cache(user)
1209 def increment_unread_conversation_count(_, user), do: {:ok, user}
1211 @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
1212 def get_users_from_set(ap_ids, opts \\ []) do
1213 local_only = Keyword.get(opts, :local_only, true)
1214 criteria = %{ap_id: ap_ids, deactivated: false}
1215 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1217 User.Query.build(criteria)
1221 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1222 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1225 query = User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1231 @spec mute(User.t(), User.t(), boolean()) ::
1232 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1233 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1234 add_to_mutes(muter, mutee, notifications?)
1237 def unmute(%User{} = muter, %User{} = mutee) do
1238 remove_from_mutes(muter, mutee)
1241 def subscribe(%User{} = subscriber, %User{} = target) do
1242 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1244 if blocks?(target, subscriber) and deny_follow_blocked do
1245 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1247 # Note: the relationship is inverse: subscriber acts as relationship target
1248 UserRelationship.create_inverse_subscription(target, subscriber)
1252 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1253 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1254 subscribe(subscriber, subscribee)
1258 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1259 # Note: the relationship is inverse: subscriber acts as relationship target
1260 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1263 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1264 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1265 unsubscribe(unsubscriber, user)
1269 def block(%User{} = blocker, %User{} = blocked) do
1270 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1272 if following?(blocker, blocked) do
1273 {:ok, blocker, _} = unfollow(blocker, blocked)
1279 # clear any requested follows as well
1281 case CommonAPI.reject_follow_request(blocked, blocker) do
1282 {:ok, %User{} = updated_blocked} -> updated_blocked
1286 unsubscribe(blocked, blocker)
1288 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1290 {:ok, blocker} = update_follower_count(blocker)
1291 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1292 add_to_block(blocker, blocked)
1295 # helper to handle the block given only an actor's AP id
1296 def block(%User{} = blocker, %{ap_id: ap_id}) do
1297 block(blocker, get_cached_by_ap_id(ap_id))
1300 def unblock(%User{} = blocker, %User{} = blocked) do
1301 remove_from_block(blocker, blocked)
1304 # helper to handle the block given only an actor's AP id
1305 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1306 unblock(blocker, get_cached_by_ap_id(ap_id))
1309 def mutes?(nil, _), do: false
1310 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1312 def mutes_user?(%User{} = user, %User{} = target) do
1313 UserRelationship.mute_exists?(user, target)
1316 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1317 def muted_notifications?(nil, _), do: false
1319 def muted_notifications?(%User{} = user, %User{} = target),
1320 do: UserRelationship.notification_mute_exists?(user, target)
1322 def blocks?(nil, _), do: false
1324 def blocks?(%User{} = user, %User{} = target) do
1325 blocks_user?(user, target) ||
1326 (blocks_domain?(user, target) and not User.following?(user, target))
1329 def blocks_user?(%User{} = user, %User{} = target) do
1330 UserRelationship.block_exists?(user, target)
1333 def blocks_user?(_, _), do: false
1335 def blocks_domain?(%User{} = user, %User{} = target) do
1336 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1337 %{host: host} = URI.parse(target.ap_id)
1338 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1341 def blocks_domain?(_, _), do: false
1343 def subscribed_to?(%User{} = user, %User{} = target) do
1344 # Note: the relationship is inverse: subscriber acts as relationship target
1345 UserRelationship.inverse_subscription_exists?(target, user)
1348 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1349 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1350 subscribed_to?(user, target)
1355 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1356 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1358 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1359 def outgoing_relationships_ap_ids(_user, []), do: %{}
1361 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1363 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1364 when is_list(relationship_types) do
1367 |> assoc(:outgoing_relationships)
1368 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1369 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1370 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1371 |> group_by([user_rel, u], user_rel.relationship_type)
1373 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1378 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1382 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1384 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1386 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1388 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1389 when is_list(relationship_types) do
1391 |> assoc(:incoming_relationships)
1392 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1393 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1394 |> maybe_filter_on_ap_id(ap_ids)
1395 |> select([user_rel, u], u.ap_id)
1400 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1401 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1404 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1406 def deactivate_async(user, status \\ true) do
1407 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1410 def deactivate(user, status \\ true)
1412 def deactivate(users, status) when is_list(users) do
1413 Repo.transaction(fn ->
1414 for user <- users, do: deactivate(user, status)
1418 def deactivate(%User{} = user, status) do
1419 with {:ok, user} <- set_activation_status(user, status) do
1422 |> Enum.filter(& &1.local)
1423 |> Enum.each(&set_cache(update_following_count(&1)))
1425 # Only update local user counts, remote will be update during the next pull.
1428 |> Enum.filter(& &1.local)
1429 |> Enum.each(&do_unfollow(user, &1))
1435 def update_notification_settings(%User{} = user, settings) do
1437 |> cast(%{notification_settings: settings}, [])
1438 |> cast_embed(:notification_settings)
1439 |> validate_required([:notification_settings])
1440 |> update_and_set_cache()
1443 def delete(users) when is_list(users) do
1444 for user <- users, do: delete(user)
1447 def delete(%User{} = user) do
1448 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1451 defp delete_and_invalidate_cache(%User{} = user) do
1452 invalidate_cache(user)
1456 defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user)
1458 defp delete_or_deactivate(%User{local: true} = user) do
1459 status = account_status(user)
1461 if status == :confirmation_pending do
1462 delete_and_invalidate_cache(user)
1465 |> change(%{deactivated: true, email: nil})
1466 |> update_and_set_cache()
1470 def perform(:force_password_reset, user), do: force_password_reset(user)
1472 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1473 def perform(:delete, %User{} = user) do
1474 # Remove all relationships
1477 |> Enum.each(fn follower ->
1478 ActivityPub.unfollow(follower, user)
1479 unfollow(follower, user)
1484 |> Enum.each(fn followed ->
1485 ActivityPub.unfollow(user, followed)
1486 unfollow(user, followed)
1489 delete_user_activities(user)
1490 delete_notifications_from_user_activities(user)
1492 delete_outgoing_pending_follow_requests(user)
1494 delete_or_deactivate(user)
1497 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1499 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1500 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1501 when is_list(blocked_identifiers) do
1503 blocked_identifiers,
1504 fn blocked_identifier ->
1505 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1506 {:ok, _user_block} <- block(blocker, blocked),
1507 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1511 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1518 def perform(:follow_import, %User{} = follower, followed_identifiers)
1519 when is_list(followed_identifiers) do
1521 followed_identifiers,
1522 fn followed_identifier ->
1523 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1524 {:ok, follower} <- maybe_direct_follow(follower, followed),
1525 {:ok, _} <- ActivityPub.follow(follower, followed) do
1529 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1536 @spec external_users_query() :: Ecto.Query.t()
1537 def external_users_query do
1545 @spec external_users(keyword()) :: [User.t()]
1546 def external_users(opts \\ []) do
1548 external_users_query()
1549 |> select([u], struct(u, [:id, :ap_id]))
1553 do: where(query, [u], u.id > ^opts[:max_id]),
1558 do: limit(query, ^opts[:limit]),
1564 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1565 BackgroundWorker.enqueue("blocks_import", %{
1566 "blocker_id" => blocker.id,
1567 "blocked_identifiers" => blocked_identifiers
1571 def follow_import(%User{} = follower, followed_identifiers)
1572 when is_list(followed_identifiers) do
1573 BackgroundWorker.enqueue("follow_import", %{
1574 "follower_id" => follower.id,
1575 "followed_identifiers" => followed_identifiers
1579 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1581 |> join(:inner, [n], activity in assoc(n, :activity))
1582 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1583 |> Repo.delete_all()
1586 def delete_user_activities(%User{ap_id: ap_id} = user) do
1588 |> Activity.Queries.by_actor()
1589 |> RepoStreamer.chunk_stream(50)
1590 |> Stream.each(fn activities ->
1591 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1596 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1597 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1598 {:ok, delete_data, _} <- Builder.delete(user, object) do
1599 Pipeline.common_pipeline(delete_data, local: user.local)
1601 {:find_object, nil} ->
1602 # We have the create activity, but not the object, it was probably pruned.
1603 # Insert a tombstone and try again
1604 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1605 {:ok, _tombstone} <- Object.create(tombstone_data) do
1606 delete_activity(activity, user)
1610 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1611 Logger.error("Error: #{inspect(e)}")
1615 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1616 when type in ["Like", "Announce"] do
1617 {:ok, undo, _} = Builder.undo(user, activity)
1618 Pipeline.common_pipeline(undo, local: user.local)
1621 defp delete_activity(_activity, _user), do: "Doing nothing"
1623 defp delete_outgoing_pending_follow_requests(user) do
1625 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1626 |> Repo.delete_all()
1629 def html_filter_policy(%User{no_rich_text: true}) do
1630 Pleroma.HTML.Scrubber.TwitterText
1633 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1635 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1637 def get_or_fetch_by_ap_id(ap_id) do
1638 cached_user = get_cached_by_ap_id(ap_id)
1640 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
1642 case {cached_user, maybe_fetched_user} do
1643 {_, {:ok, %User{} = user}} ->
1646 {%User{} = user, _} ->
1650 {:error, :not_found}
1655 Creates an internal service actor by URI if missing.
1656 Optionally takes nickname for addressing.
1658 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1659 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1661 case get_cached_by_ap_id(uri) do
1663 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1664 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1668 %User{invisible: false} = user ->
1678 @spec set_invisible(User.t()) :: {:ok, User.t()}
1679 defp set_invisible(user) do
1681 |> change(%{invisible: true})
1682 |> update_and_set_cache()
1685 @spec create_service_actor(String.t(), String.t()) ::
1686 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1687 defp create_service_actor(uri, nickname) do
1693 follower_address: uri <> "/followers"
1696 |> unique_constraint(:nickname)
1701 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1704 |> :public_key.pem_decode()
1706 |> :public_key.pem_entry_decode()
1711 def public_key(_), do: {:error, "key not found"}
1713 def get_public_key_for_ap_id(ap_id) do
1714 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1715 {:ok, public_key} <- public_key(user) do
1722 def ap_enabled?(%User{local: true}), do: true
1723 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1724 def ap_enabled?(_), do: false
1726 @doc "Gets or fetch a user by uri or nickname."
1727 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1728 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1729 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1731 # wait a period of time and return newest version of the User structs
1732 # this is because we have synchronous follow APIs and need to simulate them
1733 # with an async handshake
1734 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1735 with %User{} = a <- get_cached_by_id(a.id),
1736 %User{} = b <- get_cached_by_id(b.id) do
1743 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1744 with :ok <- :timer.sleep(timeout),
1745 %User{} = a <- get_cached_by_id(a.id),
1746 %User{} = b <- get_cached_by_id(b.id) do
1753 def parse_bio(bio) when is_binary(bio) and bio != "" do
1755 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1759 def parse_bio(_), do: ""
1761 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1762 # TODO: get profile URLs other than user.ap_id
1763 profile_urls = [user.ap_id]
1766 |> CommonUtils.format_input("text/plain",
1767 mentions_format: :full,
1768 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1773 def parse_bio(_, _), do: ""
1775 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1776 Repo.transaction(fn ->
1777 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1781 def tag(nickname, tags) when is_binary(nickname),
1782 do: tag(get_by_nickname(nickname), tags)
1784 def tag(%User{} = user, tags),
1785 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1787 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1788 Repo.transaction(fn ->
1789 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1793 def untag(nickname, tags) when is_binary(nickname),
1794 do: untag(get_by_nickname(nickname), tags)
1796 def untag(%User{} = user, tags),
1797 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1799 defp update_tags(%User{} = user, new_tags) do
1800 {:ok, updated_user} =
1802 |> change(%{tags: new_tags})
1803 |> update_and_set_cache()
1808 defp normalize_tags(tags) do
1811 |> Enum.map(&String.downcase/1)
1814 defp local_nickname_regex do
1815 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1816 @extended_local_nickname_regex
1818 @strict_local_nickname_regex
1822 def local_nickname(nickname_or_mention) do
1825 |> String.split("@")
1829 def full_nickname(nickname_or_mention),
1830 do: String.trim_leading(nickname_or_mention, "@")
1832 def error_user(ap_id) do
1836 nickname: "erroruser@example.com",
1837 inserted_at: NaiveDateTime.utc_now()
1841 @spec all_superusers() :: [User.t()]
1842 def all_superusers do
1843 User.Query.build(%{super_users: true, local: true, deactivated: false})
1847 def muting_reblogs?(%User{} = user, %User{} = target) do
1848 UserRelationship.reblog_mute_exists?(user, target)
1851 def showing_reblogs?(%User{} = user, %User{} = target) do
1852 not muting_reblogs?(user, target)
1856 The function returns a query to get users with no activity for given interval of days.
1857 Inactive users are those who didn't read any notification, or had any activity where
1858 the user is the activity's actor, during `inactivity_threshold` days.
1859 Deactivated users will not appear in this list.
1863 iex> Pleroma.User.list_inactive_users()
1866 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1867 def list_inactive_users_query(inactivity_threshold \\ 7) do
1868 negative_inactivity_threshold = -inactivity_threshold
1869 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1870 # Subqueries are not supported in `where` clauses, join gets too complicated.
1871 has_read_notifications =
1872 from(n in Pleroma.Notification,
1873 where: n.seen == true,
1875 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1878 |> Pleroma.Repo.all()
1880 from(u in Pleroma.User,
1881 left_join: a in Pleroma.Activity,
1882 on: u.ap_id == a.actor,
1883 where: not is_nil(u.nickname),
1884 where: u.deactivated != ^true,
1885 where: u.id not in ^has_read_notifications,
1888 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1889 is_nil(max(a.inserted_at))
1894 Enable or disable email notifications for user
1898 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1899 Pleroma.User{email_notifications: %{"digest" => true}}
1901 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1902 Pleroma.User{email_notifications: %{"digest" => false}}
1904 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1905 {:ok, t()} | {:error, Ecto.Changeset.t()}
1906 def switch_email_notifications(user, type, status) do
1907 User.update_email_notifications(user, %{type => status})
1911 Set `last_digest_emailed_at` value for the user to current time
1913 @spec touch_last_digest_emailed_at(t()) :: t()
1914 def touch_last_digest_emailed_at(user) do
1915 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1917 {:ok, updated_user} =
1919 |> change(%{last_digest_emailed_at: now})
1920 |> update_and_set_cache()
1925 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1926 def toggle_confirmation(%User{} = user) do
1928 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1929 |> update_and_set_cache()
1932 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1933 def toggle_confirmation(users) do
1934 Enum.map(users, &toggle_confirmation/1)
1937 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1941 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1942 # use instance-default
1943 config = Pleroma.Config.get([:assets, :mascots])
1944 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1945 mascot = Keyword.get(config, default_mascot)
1948 "id" => "default-mascot",
1949 "url" => mascot[:url],
1950 "preview_url" => mascot[:url],
1952 "mime_type" => mascot[:mime_type]
1957 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1959 def ensure_keys_present(%User{} = user) do
1960 with {:ok, pem} <- Keys.generate_rsa_pem() do
1962 |> cast(%{keys: pem}, [:keys])
1963 |> validate_required([:keys])
1964 |> update_and_set_cache()
1968 def get_ap_ids_by_nicknames(nicknames) do
1970 where: u.nickname in ^nicknames,
1976 defdelegate search(query, opts \\ []), to: User.Search
1978 defp put_password_hash(
1979 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1981 change(changeset, password_hash: Pbkdf2.hash_pwd_salt(password))
1984 defp put_password_hash(changeset), do: changeset
1986 def is_internal_user?(%User{nickname: nil}), do: true
1987 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1988 def is_internal_user?(_), do: false
1990 # A hack because user delete activities have a fake id for whatever reason
1991 # TODO: Get rid of this
1992 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1994 def get_delivered_users_by_object_id(object_id) do
1996 inner_join: delivery in assoc(u, :deliveries),
1997 where: delivery.object_id == ^object_id
2002 def change_email(user, email) do
2004 |> cast(%{email: email}, [:email])
2005 |> validate_required([:email])
2006 |> unique_constraint(:email)
2007 |> validate_format(:email, @email_regex)
2008 |> update_and_set_cache()
2011 # Internal function; public one is `deactivate/2`
2012 defp set_activation_status(user, deactivated) do
2014 |> cast(%{deactivated: deactivated}, [:deactivated])
2015 |> update_and_set_cache()
2018 def update_banner(user, banner) do
2020 |> cast(%{banner: banner}, [:banner])
2021 |> update_and_set_cache()
2024 def update_background(user, background) do
2026 |> cast(%{background: background}, [:background])
2027 |> update_and_set_cache()
2030 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
2033 moderator: is_moderator
2037 def validate_fields(changeset, remote? \\ false) do
2038 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2039 limit = Pleroma.Config.get([:instance, limit_name], 0)
2042 |> validate_length(:fields, max: limit)
2043 |> validate_change(:fields, fn :fields, fields ->
2044 if Enum.all?(fields, &valid_field?/1) do
2052 defp valid_field?(%{"name" => name, "value" => value}) do
2053 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
2054 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
2056 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2057 String.length(value) <= value_limit
2060 defp valid_field?(_), do: false
2062 defp truncate_field(%{"name" => name, "value" => value}) do
2064 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
2067 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
2069 %{"name" => name, "value" => value}
2072 def admin_api_update(user, params) do
2079 |> update_and_set_cache()
2082 @doc "Signs user out of all applications"
2083 def global_sign_out(user) do
2084 OAuth.Authorization.delete_user_authorizations(user)
2085 OAuth.Token.delete_user_tokens(user)
2088 def mascot_update(user, url) do
2090 |> cast(%{mascot: url}, [:mascot])
2091 |> validate_required([:mascot])
2092 |> update_and_set_cache()
2095 def mastodon_settings_update(user, settings) do
2097 |> cast(%{settings: settings}, [:settings])
2098 |> validate_required([:settings])
2099 |> update_and_set_cache()
2102 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2103 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2105 if need_confirmation? do
2107 confirmation_pending: true,
2108 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2112 confirmation_pending: false,
2113 confirmation_token: nil
2117 cast(user, params, [:confirmation_pending, :confirmation_token])
2120 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2121 if id not in user.pinned_activities do
2122 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
2123 params = %{pinned_activities: user.pinned_activities ++ [id]}
2126 |> cast(params, [:pinned_activities])
2127 |> validate_length(:pinned_activities,
2128 max: max_pinned_statuses,
2129 message: "You have already pinned the maximum number of statuses"
2134 |> update_and_set_cache()
2137 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2138 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2141 |> cast(params, [:pinned_activities])
2142 |> update_and_set_cache()
2145 def update_email_notifications(user, settings) do
2146 email_notifications =
2147 user.email_notifications
2148 |> Map.merge(settings)
2149 |> Map.take(["digest"])
2151 params = %{email_notifications: email_notifications}
2152 fields = [:email_notifications]
2155 |> cast(params, fields)
2156 |> validate_required(fields)
2157 |> update_and_set_cache()
2160 defp set_domain_blocks(user, domain_blocks) do
2161 params = %{domain_blocks: domain_blocks}
2164 |> cast(params, [:domain_blocks])
2165 |> validate_required([:domain_blocks])
2166 |> update_and_set_cache()
2169 def block_domain(user, domain_blocked) do
2170 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2173 def unblock_domain(user, domain_blocked) do
2174 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2177 @spec add_to_block(User.t(), User.t()) ::
2178 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2179 defp add_to_block(%User{} = user, %User{} = blocked) do
2180 UserRelationship.create_block(user, blocked)
2183 @spec add_to_block(User.t(), User.t()) ::
2184 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2185 defp remove_from_block(%User{} = user, %User{} = blocked) do
2186 UserRelationship.delete_block(user, blocked)
2189 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2190 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2191 {:ok, user_notification_mute} <-
2192 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2194 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2198 defp remove_from_mutes(user, %User{} = muted_user) do
2199 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2200 {:ok, user_notification_mute} <-
2201 UserRelationship.delete_notification_mute(user, muted_user) do
2202 {:ok, [user_mute, user_notification_mute]}
2206 def set_invisible(user, invisible) do
2207 params = %{invisible: invisible}
2210 |> cast(params, [:invisible])
2211 |> validate_required([:invisible])
2212 |> update_and_set_cache()
2215 def sanitize_html(%User{} = user) do
2216 sanitize_html(user, nil)
2219 # User data that mastodon isn't filtering (treated as plaintext):
2222 def sanitize_html(%User{} = user, filter) do
2224 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2227 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2232 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2233 |> Map.put(:fields, fields)