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]
14 alias Pleroma.Activity
16 alias Pleroma.Conversation.Participation
17 alias Pleroma.Delivery
19 alias Pleroma.FollowingRelationship
20 alias Pleroma.Formatter
23 alias Pleroma.Notification
25 alias Pleroma.Registration
27 alias Pleroma.RepoStreamer
29 alias Pleroma.UserRelationship
31 alias Pleroma.Web.ActivityPub.ActivityPub
32 alias Pleroma.Web.ActivityPub.ObjectValidators.Types
33 alias Pleroma.Web.ActivityPub.Utils
34 alias Pleroma.Web.CommonAPI
35 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
36 alias Pleroma.Web.OAuth
37 alias Pleroma.Web.RelMe
38 alias Pleroma.Workers.BackgroundWorker
42 @type t :: %__MODULE__{}
43 @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
44 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
46 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
47 @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])?)*$/
49 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
50 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
52 # AP ID user relationships (blocks, mutes etc.)
53 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
54 @user_relationships_config [
56 blocker_blocks: :blocked_users,
57 blockee_blocks: :blocker_users
60 muter_mutes: :muted_users,
61 mutee_mutes: :muter_users
64 reblog_muter_mutes: :reblog_muted_users,
65 reblog_mutee_mutes: :reblog_muter_users
68 notification_muter_mutes: :notification_muted_users,
69 notification_mutee_mutes: :notification_muter_users
71 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
72 inverse_subscription: [
73 subscribee_subscriptions: :subscriber_users,
74 subscriber_subscriptions: :subscribee_users
80 field(:email, :string)
82 field(:nickname, :string)
83 field(:password_hash, :string)
84 field(:password, :string, virtual: true)
85 field(:password_confirmation, :string, virtual: true)
87 field(:public_key, :string)
88 field(:ap_id, :string)
90 field(:local, :boolean, default: true)
91 field(:follower_address, :string)
92 field(:following_address, :string)
93 field(:search_rank, :float, virtual: true)
94 field(:search_type, :integer, virtual: true)
95 field(:tags, {:array, :string}, default: [])
96 field(:last_refreshed_at, :naive_datetime_usec)
97 field(:last_digest_emailed_at, :naive_datetime)
98 field(:banner, :map, default: %{})
99 field(:background, :map, default: %{})
100 field(:note_count, :integer, default: 0)
101 field(:follower_count, :integer, default: 0)
102 field(:following_count, :integer, default: 0)
103 field(:locked, :boolean, default: false)
104 field(:confirmation_pending, :boolean, default: false)
105 field(:password_reset_pending, :boolean, default: false)
106 field(:confirmation_token, :string, default: nil)
107 field(:default_scope, :string, default: "public")
108 field(:domain_blocks, {:array, :string}, default: [])
109 field(:deactivated, :boolean, default: false)
110 field(:no_rich_text, :boolean, default: false)
111 field(:ap_enabled, :boolean, default: false)
112 field(:is_moderator, :boolean, default: false)
113 field(:is_admin, :boolean, default: false)
114 field(:show_role, :boolean, default: true)
115 field(:settings, :map, default: nil)
116 field(:magic_key, :string, default: nil)
117 field(:uri, Types.Uri, default: nil)
118 field(:hide_followers_count, :boolean, default: false)
119 field(:hide_follows_count, :boolean, default: false)
120 field(:hide_followers, :boolean, default: false)
121 field(:hide_follows, :boolean, default: false)
122 field(:hide_favorites, :boolean, default: true)
123 field(:unread_conversation_count, :integer, default: 0)
124 field(:pinned_activities, {:array, :string}, default: [])
125 field(:email_notifications, :map, default: %{"digest" => false})
126 field(:mascot, :map, default: nil)
127 field(:emoji, :map, default: %{})
128 field(:pleroma_settings_store, :map, default: %{})
129 field(:fields, {:array, :map}, default: [])
130 field(:raw_fields, {:array, :map}, default: [])
131 field(:discoverable, :boolean, default: false)
132 field(:invisible, :boolean, default: false)
133 field(:allow_following_move, :boolean, default: true)
134 field(:skip_thread_containment, :boolean, default: false)
135 field(:actor_type, :string, default: "Person")
136 field(:also_known_as, {:array, :string}, default: [])
137 field(:inbox, :string)
138 field(:shared_inbox, :string)
141 :notification_settings,
142 Pleroma.User.NotificationSetting,
146 has_many(:notifications, Notification)
147 has_many(:registrations, Registration)
148 has_many(:deliveries, Delivery)
150 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
151 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
153 for {relationship_type,
155 {outgoing_relation, outgoing_relation_target},
156 {incoming_relation, incoming_relation_source}
157 ]} <- @user_relationships_config do
158 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
159 # :notification_muter_mutes, :subscribee_subscriptions
160 has_many(outgoing_relation, UserRelationship,
161 foreign_key: :source_id,
162 where: [relationship_type: relationship_type]
165 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
166 # :notification_mutee_mutes, :subscriber_subscriptions
167 has_many(incoming_relation, UserRelationship,
168 foreign_key: :target_id,
169 where: [relationship_type: relationship_type]
172 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
173 # :notification_muted_users, :subscriber_users
174 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
176 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
177 # :notification_muter_users, :subscribee_users
178 has_many(incoming_relation_source, through: [incoming_relation, :source])
181 # `:blocks` is deprecated (replaced with `blocked_users` relation)
182 field(:blocks, {:array, :string}, default: [])
183 # `:mutes` is deprecated (replaced with `muted_users` relation)
184 field(:mutes, {:array, :string}, default: [])
185 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
186 field(:muted_reblogs, {:array, :string}, default: [])
187 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
188 field(:muted_notifications, {:array, :string}, default: [])
189 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
190 field(:subscribers, {:array, :string}, default: [])
195 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
196 @user_relationships_config do
197 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
198 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
199 # `def subscriber_users/2`
200 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
201 target_users_query = assoc(user, unquote(outgoing_relation_target))
203 if restrict_deactivated? do
204 restrict_deactivated(target_users_query)
210 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
211 # `def notification_muted_users/2`, `def subscriber_users/2`
212 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
214 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
216 restrict_deactivated?
221 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
222 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
223 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
225 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
227 restrict_deactivated?
229 |> select([u], u.ap_id)
235 Dumps Flake Id to SQL-compatible format (16-byte UUID).
236 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
238 def binary_id(source_id) when is_binary(source_id) do
239 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
246 def binary_id(source_ids) when is_list(source_ids) do
247 Enum.map(source_ids, &binary_id/1)
250 def binary_id(%User{} = user), do: binary_id(user.id)
252 @doc "Returns status account"
253 @spec account_status(User.t()) :: account_status()
254 def account_status(%User{deactivated: true}), do: :deactivated
255 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
257 def account_status(%User{confirmation_pending: true}) do
258 case Config.get([:instance, :account_activation_required]) do
259 true -> :confirmation_pending
264 def account_status(%User{}), do: :active
266 @spec visible_for?(User.t(), User.t() | nil) :: boolean()
267 def visible_for?(user, for_user \\ nil)
269 def visible_for?(%User{invisible: true}, _), do: false
271 def visible_for?(%User{id: user_id}, %User{id: user_id}), do: true
273 def visible_for?(%User{local: local} = user, nil) do
279 if Config.get([:restrict_unauthenticated, :profiles, cfg_key]),
281 else: account_status(user) == :active
284 def visible_for?(%User{} = user, for_user) do
285 account_status(user) == :active || superuser?(for_user)
288 def visible_for?(_, _), do: false
290 @spec superuser?(User.t()) :: boolean()
291 def superuser?(%User{local: true, is_admin: true}), do: true
292 def superuser?(%User{local: true, is_moderator: true}), do: true
293 def superuser?(_), do: false
295 @spec invisible?(User.t()) :: boolean()
296 def invisible?(%User{invisible: true}), do: true
297 def invisible?(_), do: false
299 def avatar_url(user, options \\ []) do
301 %{"url" => [%{"href" => href} | _]} -> href
302 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
306 def banner_url(user, options \\ []) do
308 %{"url" => [%{"href" => href} | _]} -> href
309 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
313 # Should probably be renamed or removed
314 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
316 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
317 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
319 @spec ap_following(User.t()) :: String.t()
320 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
321 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
323 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
324 def restrict_deactivated(query) do
325 from(u in query, where: u.deactivated != ^true)
328 defdelegate following_count(user), to: FollowingRelationship
330 defp truncate_fields_param(params) do
331 if Map.has_key?(params, :fields) do
332 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
338 defp truncate_if_exists(params, key, max_length) do
339 if Map.has_key?(params, key) and is_binary(params[key]) do
340 {value, _chopped} = String.split_at(params[key], max_length)
341 Map.put(params, key, value)
347 defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
349 defp fix_follower_address(%{nickname: nickname} = params),
350 do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
352 defp fix_follower_address(params), do: params
354 def remote_user_changeset(struct \\ %User{local: false}, params) do
355 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
356 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
359 case params[:name] do
360 name when is_binary(name) and byte_size(name) > 0 -> name
361 _ -> params[:nickname]
366 |> Map.put(:name, name)
367 |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
368 |> truncate_if_exists(:name, name_limit)
369 |> truncate_if_exists(:bio, bio_limit)
370 |> truncate_fields_param()
371 |> fix_follower_address()
396 :hide_followers_count,
407 |> validate_required([:name, :ap_id])
408 |> unique_constraint(:nickname)
409 |> validate_format(:nickname, @email_regex)
410 |> validate_length(:bio, max: bio_limit)
411 |> validate_length(:name, max: name_limit)
412 |> validate_fields(true)
415 def update_changeset(struct, params \\ %{}) do
416 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
417 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
436 :hide_followers_count,
439 :allow_following_move,
442 :skip_thread_containment,
445 :pleroma_settings_store,
451 |> unique_constraint(:nickname)
452 |> validate_format(:nickname, local_nickname_regex())
453 |> validate_length(:bio, max: bio_limit)
454 |> validate_length(:name, min: 1, max: name_limit)
457 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
458 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
459 |> put_change_if_present(:banner, &put_upload(&1, :banner))
460 |> put_change_if_present(:background, &put_upload(&1, :background))
461 |> put_change_if_present(
462 :pleroma_settings_store,
463 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
465 |> validate_fields(false)
468 defp put_fields(changeset) do
469 if raw_fields = get_change(changeset, :raw_fields) do
472 |> Enum.filter(fn %{"name" => n} -> n != "" end)
476 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
479 |> put_change(:raw_fields, raw_fields)
480 |> put_change(:fields, fields)
486 defp parse_fields(value) do
488 |> Formatter.linkify(mentions_format: :full)
492 defp put_emoji(changeset) do
493 bio = get_change(changeset, :bio)
494 name = get_change(changeset, :name)
497 emoji = Map.merge(Emoji.Formatter.get_emoji_map(bio), Emoji.Formatter.get_emoji_map(name))
498 put_change(changeset, :emoji, emoji)
504 defp put_change_if_present(changeset, map_field, value_function) do
505 if value = get_change(changeset, map_field) do
506 with {:ok, new_value} <- value_function.(value) do
507 put_change(changeset, map_field, new_value)
516 defp put_upload(value, type) do
517 with %Plug.Upload{} <- value,
518 {:ok, object} <- ActivityPub.upload(value, type: type) do
523 def update_as_admin_changeset(struct, params) do
525 |> update_changeset(params)
526 |> cast(params, [:email])
527 |> delete_change(:also_known_as)
528 |> unique_constraint(:email)
529 |> validate_format(:email, @email_regex)
532 @spec update_as_admin(%User{}, map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
533 def update_as_admin(user, params) do
534 params = Map.put(params, "password_confirmation", params["password"])
535 changeset = update_as_admin_changeset(user, params)
537 if params["password"] do
538 reset_password(user, changeset, params)
540 User.update_and_set_cache(changeset)
544 def password_update_changeset(struct, params) do
546 |> cast(params, [:password, :password_confirmation])
547 |> validate_required([:password, :password_confirmation])
548 |> validate_confirmation(:password)
549 |> put_password_hash()
550 |> put_change(:password_reset_pending, false)
553 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
554 def reset_password(%User{} = user, params) do
555 reset_password(user, user, params)
558 def reset_password(%User{id: user_id} = user, struct, params) do
561 |> Multi.update(:user, password_update_changeset(struct, params))
562 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
563 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
565 case Repo.transaction(multi) do
566 {:ok, %{user: user} = _} -> set_cache(user)
567 {:error, _, changeset, _} -> {:error, changeset}
571 def update_password_reset_pending(user, value) do
574 |> put_change(:password_reset_pending, value)
575 |> update_and_set_cache()
578 def force_password_reset_async(user) do
579 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
582 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
583 def force_password_reset(user), do: update_password_reset_pending(user, true)
585 def register_changeset(struct, params \\ %{}, opts \\ []) do
586 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
587 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
590 if is_nil(opts[:need_confirmation]) do
591 Pleroma.Config.get([:instance, :account_activation_required])
593 opts[:need_confirmation]
597 |> confirmation_changeset(need_confirmation: need_confirmation?)
598 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation, :emoji])
599 |> validate_required([:name, :nickname, :password, :password_confirmation])
600 |> validate_confirmation(:password)
601 |> unique_constraint(:email)
602 |> unique_constraint(:nickname)
603 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
604 |> validate_format(:nickname, local_nickname_regex())
605 |> validate_format(:email, @email_regex)
606 |> validate_length(:bio, max: bio_limit)
607 |> validate_length(:name, min: 1, max: name_limit)
608 |> maybe_validate_required_email(opts[:external])
611 |> unique_constraint(:ap_id)
612 |> put_following_and_follower_address()
615 def maybe_validate_required_email(changeset, true), do: changeset
617 def maybe_validate_required_email(changeset, _) do
618 if Pleroma.Config.get([:instance, :account_activation_required]) do
619 validate_required(changeset, [:email])
625 defp put_ap_id(changeset) do
626 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
627 put_change(changeset, :ap_id, ap_id)
630 defp put_following_and_follower_address(changeset) do
631 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
634 |> put_change(:follower_address, followers)
637 defp autofollow_users(user) do
638 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
641 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
644 follow_all(user, autofollowed_users)
647 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
648 def register(%Ecto.Changeset{} = changeset) do
649 with {:ok, user} <- Repo.insert(changeset) do
650 post_register_action(user)
654 def post_register_action(%User{} = user) do
655 with {:ok, user} <- autofollow_users(user),
656 {:ok, user} <- set_cache(user),
657 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
658 {:ok, _} <- try_send_confirmation_email(user) do
663 def try_send_confirmation_email(%User{} = user) do
664 if user.confirmation_pending &&
665 Pleroma.Config.get([:instance, :account_activation_required]) do
667 |> Pleroma.Emails.UserEmail.account_confirmation_email()
668 |> Pleroma.Emails.Mailer.deliver_async()
676 def try_send_confirmation_email(users) do
677 Enum.each(users, &try_send_confirmation_email/1)
680 def needs_update?(%User{local: true}), do: false
682 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
684 def needs_update?(%User{local: false} = user) do
685 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
688 def needs_update?(_), do: true
690 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
691 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
692 follow(follower, followed, :follow_pending)
695 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
696 follow(follower, followed)
699 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
700 if not ap_enabled?(followed) do
701 follow(follower, followed)
707 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
708 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
709 def follow_all(follower, followeds) do
711 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
712 |> Enum.each(&follow(follower, &1, :follow_accept))
717 defdelegate following(user), to: FollowingRelationship
719 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
720 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
723 followed.deactivated ->
724 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
726 deny_follow_blocked and blocks?(followed, follower) ->
727 {:error, "Could not follow user: #{followed.nickname} blocked you."}
730 FollowingRelationship.follow(follower, followed, state)
732 {:ok, _} = update_follower_count(followed)
735 |> update_following_count()
740 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
741 {:error, "Not subscribed!"}
744 def unfollow(%User{} = follower, %User{} = followed) do
745 case get_follow_state(follower, followed) do
746 state when state in [:follow_pending, :follow_accept] ->
747 FollowingRelationship.unfollow(follower, followed)
748 {:ok, followed} = update_follower_count(followed)
752 |> update_following_count()
755 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
758 {:error, "Not subscribed!"}
762 defdelegate following?(follower, followed), to: FollowingRelationship
764 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
765 def get_follow_state(%User{} = follower, %User{} = following) do
766 following_relationship = FollowingRelationship.get(follower, following)
767 get_follow_state(follower, following, following_relationship)
770 def get_follow_state(
773 following_relationship
775 case {following_relationship, following.local} do
777 case Utils.fetch_latest_follow(follower, following) do
778 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
779 FollowingRelationship.state_to_enum(state)
785 {%{state: state}, _} ->
793 def locked?(%User{} = user) do
798 Repo.get_by(User, id: id)
801 def get_by_ap_id(ap_id) do
802 Repo.get_by(User, ap_id: ap_id)
805 def get_all_by_ap_id(ap_ids) do
806 from(u in __MODULE__,
807 where: u.ap_id in ^ap_ids
812 def get_all_by_ids(ids) do
813 from(u in __MODULE__, where: u.id in ^ids)
817 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
818 # of the ap_id and the domain and tries to get that user
819 def get_by_guessed_nickname(ap_id) do
820 domain = URI.parse(ap_id).host
821 name = List.last(String.split(ap_id, "/"))
822 nickname = "#{name}@#{domain}"
824 get_cached_by_nickname(nickname)
827 def set_cache({:ok, user}), do: set_cache(user)
828 def set_cache({:error, err}), do: {:error, err}
830 def set_cache(%User{} = user) do
831 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
832 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
836 def update_and_set_cache(struct, params) do
838 |> update_changeset(params)
839 |> update_and_set_cache()
842 def update_and_set_cache(changeset) do
843 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
848 def invalidate_cache(user) do
849 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
850 Cachex.del(:user_cache, "nickname:#{user.nickname}")
853 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
854 def get_cached_by_ap_id(ap_id) do
855 key = "ap_id:#{ap_id}"
857 with {:ok, nil} <- Cachex.get(:user_cache, key),
858 user when not is_nil(user) <- get_by_ap_id(ap_id),
859 {:ok, true} <- Cachex.put(:user_cache, key, user) do
867 def get_cached_by_id(id) do
871 Cachex.fetch!(:user_cache, key, fn _ ->
875 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
876 {:commit, user.ap_id}
882 get_cached_by_ap_id(ap_id)
885 def get_cached_by_nickname(nickname) do
886 key = "nickname:#{nickname}"
888 Cachex.fetch!(:user_cache, key, fn ->
889 case get_or_fetch_by_nickname(nickname) do
890 {:ok, user} -> {:commit, user}
891 {:error, _error} -> {:ignore, nil}
896 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
897 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
900 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
901 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
903 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
904 get_cached_by_nickname(nickname_or_id)
906 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
907 get_cached_by_nickname(nickname_or_id)
914 def get_by_nickname(nickname) do
915 Repo.get_by(User, nickname: nickname) ||
916 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
917 Repo.get_by(User, nickname: local_nickname(nickname))
921 def get_by_email(email), do: Repo.get_by(User, email: email)
923 def get_by_nickname_or_email(nickname_or_email) do
924 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
927 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
929 def get_or_fetch_by_nickname(nickname) do
930 with %User{} = user <- get_by_nickname(nickname) do
934 with [_nick, _domain] <- String.split(nickname, "@"),
935 {:ok, user} <- fetch_by_nickname(nickname) do
938 _e -> {:error, "not found " <> nickname}
943 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
944 def get_followers_query(%User{} = user, nil) do
945 User.Query.build(%{followers: user, deactivated: false})
948 def get_followers_query(user, page) do
950 |> get_followers_query(nil)
951 |> User.Query.paginate(page, 20)
954 @spec get_followers_query(User.t()) :: Ecto.Query.t()
955 def get_followers_query(user), do: get_followers_query(user, nil)
957 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
958 def get_followers(user, page \\ nil) do
960 |> get_followers_query(page)
964 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
965 def get_external_followers(user, page \\ nil) do
967 |> get_followers_query(page)
968 |> User.Query.build(%{external: true})
972 def get_followers_ids(user, page \\ nil) do
974 |> get_followers_query(page)
979 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
980 def get_friends_query(%User{} = user, nil) do
981 User.Query.build(%{friends: user, deactivated: false})
984 def get_friends_query(user, page) do
986 |> get_friends_query(nil)
987 |> User.Query.paginate(page, 20)
990 @spec get_friends_query(User.t()) :: Ecto.Query.t()
991 def get_friends_query(user), do: get_friends_query(user, nil)
993 def get_friends(user, page \\ nil) do
995 |> get_friends_query(page)
999 def get_friends_ap_ids(user) do
1001 |> get_friends_query(nil)
1002 |> select([u], u.ap_id)
1006 def get_friends_ids(user, page \\ nil) do
1008 |> get_friends_query(page)
1009 |> select([u], u.id)
1013 defdelegate get_follow_requests(user), to: FollowingRelationship
1015 def increase_note_count(%User{} = user) do
1017 |> where(id: ^user.id)
1018 |> update([u], inc: [note_count: 1])
1020 |> Repo.update_all([])
1022 {1, [user]} -> set_cache(user)
1027 def decrease_note_count(%User{} = user) do
1029 |> where(id: ^user.id)
1032 note_count: fragment("greatest(0, note_count - 1)")
1036 |> Repo.update_all([])
1038 {1, [user]} -> set_cache(user)
1043 def update_note_count(%User{} = user, note_count \\ nil) do
1048 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1054 |> cast(%{note_count: note_count}, [:note_count])
1055 |> update_and_set_cache()
1058 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1059 def maybe_fetch_follow_information(user) do
1060 with {:ok, user} <- fetch_follow_information(user) do
1064 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1070 def fetch_follow_information(user) do
1071 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1073 |> follow_information_changeset(info)
1074 |> update_and_set_cache()
1078 defp follow_information_changeset(user, params) do
1085 :hide_followers_count,
1090 def update_follower_count(%User{} = user) do
1091 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
1092 follower_count_query =
1093 User.Query.build(%{followers: user, deactivated: false})
1094 |> select([u], %{count: count(u.id)})
1097 |> where(id: ^user.id)
1098 |> join(:inner, [u], s in subquery(follower_count_query))
1100 set: [follower_count: s.count]
1103 |> Repo.update_all([])
1105 {1, [user]} -> set_cache(user)
1109 {:ok, maybe_fetch_follow_information(user)}
1113 @spec update_following_count(User.t()) :: User.t()
1114 def update_following_count(%User{local: false} = user) do
1115 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1116 maybe_fetch_follow_information(user)
1122 def update_following_count(%User{local: true} = user) do
1123 following_count = FollowingRelationship.following_count(user)
1126 |> follow_information_changeset(%{following_count: following_count})
1130 def set_unread_conversation_count(%User{local: true} = user) do
1131 unread_query = Participation.unread_conversation_count_for_user(user)
1134 |> join(:inner, [u], p in subquery(unread_query))
1136 set: [unread_conversation_count: p.count]
1138 |> where([u], u.id == ^user.id)
1140 |> Repo.update_all([])
1142 {1, [user]} -> set_cache(user)
1147 def set_unread_conversation_count(user), do: {:ok, user}
1149 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1151 Participation.unread_conversation_count_for_user(user)
1152 |> where([p], p.conversation_id == ^conversation.id)
1155 |> join(:inner, [u], p in subquery(unread_query))
1157 inc: [unread_conversation_count: 1]
1159 |> where([u], u.id == ^user.id)
1160 |> where([u, p], p.count == 0)
1162 |> Repo.update_all([])
1164 {1, [user]} -> set_cache(user)
1169 def increment_unread_conversation_count(_, user), do: {:ok, user}
1171 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1172 def get_users_from_set(ap_ids, local_only \\ true) do
1173 criteria = %{ap_id: ap_ids, deactivated: false}
1174 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1176 User.Query.build(criteria)
1180 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1181 def get_recipients_from_activity(%Activity{recipients: to}) do
1182 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1186 @spec mute(User.t(), User.t(), boolean()) ::
1187 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1188 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1189 add_to_mutes(muter, mutee, notifications?)
1192 def unmute(%User{} = muter, %User{} = mutee) do
1193 remove_from_mutes(muter, mutee)
1196 def subscribe(%User{} = subscriber, %User{} = target) do
1197 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1199 if blocks?(target, subscriber) and deny_follow_blocked do
1200 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1202 # Note: the relationship is inverse: subscriber acts as relationship target
1203 UserRelationship.create_inverse_subscription(target, subscriber)
1207 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1208 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1209 subscribe(subscriber, subscribee)
1213 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1214 # Note: the relationship is inverse: subscriber acts as relationship target
1215 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1218 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1219 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1220 unsubscribe(unsubscriber, user)
1224 def block(%User{} = blocker, %User{} = blocked) do
1225 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1227 if following?(blocker, blocked) do
1228 {:ok, blocker, _} = unfollow(blocker, blocked)
1234 # clear any requested follows as well
1236 case CommonAPI.reject_follow_request(blocked, blocker) do
1237 {:ok, %User{} = updated_blocked} -> updated_blocked
1241 unsubscribe(blocked, blocker)
1243 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1245 {:ok, blocker} = update_follower_count(blocker)
1246 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1247 add_to_block(blocker, blocked)
1250 # helper to handle the block given only an actor's AP id
1251 def block(%User{} = blocker, %{ap_id: ap_id}) do
1252 block(blocker, get_cached_by_ap_id(ap_id))
1255 def unblock(%User{} = blocker, %User{} = blocked) do
1256 remove_from_block(blocker, blocked)
1259 # helper to handle the block given only an actor's AP id
1260 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1261 unblock(blocker, get_cached_by_ap_id(ap_id))
1264 def mutes?(nil, _), do: false
1265 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1267 def mutes_user?(%User{} = user, %User{} = target) do
1268 UserRelationship.mute_exists?(user, target)
1271 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1272 def muted_notifications?(nil, _), do: false
1274 def muted_notifications?(%User{} = user, %User{} = target),
1275 do: UserRelationship.notification_mute_exists?(user, target)
1277 def blocks?(nil, _), do: false
1279 def blocks?(%User{} = user, %User{} = target) do
1280 blocks_user?(user, target) ||
1281 (blocks_domain?(user, target) and not User.following?(user, target))
1284 def blocks_user?(%User{} = user, %User{} = target) do
1285 UserRelationship.block_exists?(user, target)
1288 def blocks_user?(_, _), do: false
1290 def blocks_domain?(%User{} = user, %User{} = target) do
1291 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1292 %{host: host} = URI.parse(target.ap_id)
1293 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1296 def blocks_domain?(_, _), do: false
1298 def subscribed_to?(%User{} = user, %User{} = target) do
1299 # Note: the relationship is inverse: subscriber acts as relationship target
1300 UserRelationship.inverse_subscription_exists?(target, user)
1303 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1304 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1305 subscribed_to?(user, target)
1310 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1311 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1313 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1314 def outgoing_relationships_ap_ids(_user, []), do: %{}
1316 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1318 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1319 when is_list(relationship_types) do
1322 |> assoc(:outgoing_relationships)
1323 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1324 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1325 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1326 |> group_by([user_rel, u], user_rel.relationship_type)
1328 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1333 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1337 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1339 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1341 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1343 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1344 when is_list(relationship_types) do
1346 |> assoc(:incoming_relationships)
1347 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1348 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1349 |> maybe_filter_on_ap_id(ap_ids)
1350 |> select([user_rel, u], u.ap_id)
1355 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1356 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1359 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1361 def deactivate_async(user, status \\ true) do
1362 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1365 def deactivate(user, status \\ true)
1367 def deactivate(users, status) when is_list(users) do
1368 Repo.transaction(fn ->
1369 for user <- users, do: deactivate(user, status)
1373 def deactivate(%User{} = user, status) do
1374 with {:ok, user} <- set_activation_status(user, status) do
1377 |> Enum.filter(& &1.local)
1378 |> Enum.each(fn follower ->
1379 follower |> update_following_count() |> set_cache()
1382 # Only update local user counts, remote will be update during the next pull.
1385 |> Enum.filter(& &1.local)
1386 |> Enum.each(&update_follower_count/1)
1392 def update_notification_settings(%User{} = user, settings) do
1394 |> cast(%{notification_settings: settings}, [])
1395 |> cast_embed(:notification_settings)
1396 |> validate_required([:notification_settings])
1397 |> update_and_set_cache()
1400 def delete(users) when is_list(users) do
1401 for user <- users, do: delete(user)
1404 def delete(%User{} = user) do
1405 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1408 def perform(:force_password_reset, user), do: force_password_reset(user)
1410 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1411 def perform(:delete, %User{} = user) do
1412 {:ok, _user} = ActivityPub.delete(user)
1414 # Remove all relationships
1417 |> Enum.each(fn follower ->
1418 ActivityPub.unfollow(follower, user)
1419 unfollow(follower, user)
1424 |> Enum.each(fn followed ->
1425 ActivityPub.unfollow(user, followed)
1426 unfollow(user, followed)
1429 delete_user_activities(user)
1430 invalidate_cache(user)
1434 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1436 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1437 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1438 when is_list(blocked_identifiers) do
1440 blocked_identifiers,
1441 fn blocked_identifier ->
1442 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1443 {:ok, _user_block} <- block(blocker, blocked),
1444 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1448 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1455 def perform(:follow_import, %User{} = follower, followed_identifiers)
1456 when is_list(followed_identifiers) do
1458 followed_identifiers,
1459 fn followed_identifier ->
1460 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1461 {:ok, follower} <- maybe_direct_follow(follower, followed),
1462 {:ok, _} <- ActivityPub.follow(follower, followed) do
1466 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1473 @spec external_users_query() :: Ecto.Query.t()
1474 def external_users_query do
1482 @spec external_users(keyword()) :: [User.t()]
1483 def external_users(opts \\ []) do
1485 external_users_query()
1486 |> select([u], struct(u, [:id, :ap_id]))
1490 do: where(query, [u], u.id > ^opts[:max_id]),
1495 do: limit(query, ^opts[:limit]),
1501 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1502 BackgroundWorker.enqueue("blocks_import", %{
1503 "blocker_id" => blocker.id,
1504 "blocked_identifiers" => blocked_identifiers
1508 def follow_import(%User{} = follower, followed_identifiers)
1509 when is_list(followed_identifiers) do
1510 BackgroundWorker.enqueue("follow_import", %{
1511 "follower_id" => follower.id,
1512 "followed_identifiers" => followed_identifiers
1516 def delete_user_activities(%User{ap_id: ap_id}) do
1518 |> Activity.Queries.by_actor()
1519 |> RepoStreamer.chunk_stream(50)
1520 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1524 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1526 |> Object.normalize()
1527 |> ActivityPub.delete()
1530 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1531 object = Object.normalize(activity)
1534 |> get_cached_by_ap_id()
1535 |> ActivityPub.unlike(object)
1538 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1539 object = Object.normalize(activity)
1542 |> get_cached_by_ap_id()
1543 |> ActivityPub.unannounce(object)
1546 defp delete_activity(_activity), do: "Doing nothing"
1548 def html_filter_policy(%User{no_rich_text: true}) do
1549 Pleroma.HTML.Scrubber.TwitterText
1552 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1554 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1556 def get_or_fetch_by_ap_id(ap_id) do
1557 user = get_cached_by_ap_id(ap_id)
1559 if !is_nil(user) and !needs_update?(user) do
1562 fetch_by_ap_id(ap_id)
1567 Creates an internal service actor by URI if missing.
1568 Optionally takes nickname for addressing.
1570 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1571 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1573 case get_cached_by_ap_id(uri) do
1575 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1576 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1580 %User{invisible: false} = user ->
1590 @spec set_invisible(User.t()) :: {:ok, User.t()}
1591 defp set_invisible(user) do
1593 |> change(%{invisible: true})
1594 |> update_and_set_cache()
1597 @spec create_service_actor(String.t(), String.t()) ::
1598 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1599 defp create_service_actor(uri, nickname) do
1605 follower_address: uri <> "/followers"
1608 |> unique_constraint(:nickname)
1613 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1616 |> :public_key.pem_decode()
1618 |> :public_key.pem_entry_decode()
1623 def public_key(_), do: {:error, "key not found"}
1625 def get_public_key_for_ap_id(ap_id) do
1626 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1627 {:ok, public_key} <- public_key(user) do
1634 def ap_enabled?(%User{local: true}), do: true
1635 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1636 def ap_enabled?(_), do: false
1638 @doc "Gets or fetch a user by uri or nickname."
1639 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1640 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1641 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1643 # wait a period of time and return newest version of the User structs
1644 # this is because we have synchronous follow APIs and need to simulate them
1645 # with an async handshake
1646 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1647 with %User{} = a <- get_cached_by_id(a.id),
1648 %User{} = b <- get_cached_by_id(b.id) do
1655 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1656 with :ok <- :timer.sleep(timeout),
1657 %User{} = a <- get_cached_by_id(a.id),
1658 %User{} = b <- get_cached_by_id(b.id) do
1665 def parse_bio(bio) when is_binary(bio) and bio != "" do
1667 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1671 def parse_bio(_), do: ""
1673 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1674 # TODO: get profile URLs other than user.ap_id
1675 profile_urls = [user.ap_id]
1678 |> CommonUtils.format_input("text/plain",
1679 mentions_format: :full,
1680 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1685 def parse_bio(_, _), do: ""
1687 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1688 Repo.transaction(fn ->
1689 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1693 def tag(nickname, tags) when is_binary(nickname),
1694 do: tag(get_by_nickname(nickname), tags)
1696 def tag(%User{} = user, tags),
1697 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1699 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1700 Repo.transaction(fn ->
1701 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1705 def untag(nickname, tags) when is_binary(nickname),
1706 do: untag(get_by_nickname(nickname), tags)
1708 def untag(%User{} = user, tags),
1709 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1711 defp update_tags(%User{} = user, new_tags) do
1712 {:ok, updated_user} =
1714 |> change(%{tags: new_tags})
1715 |> update_and_set_cache()
1720 defp normalize_tags(tags) do
1723 |> Enum.map(&String.downcase/1)
1726 defp local_nickname_regex do
1727 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1728 @extended_local_nickname_regex
1730 @strict_local_nickname_regex
1734 def local_nickname(nickname_or_mention) do
1737 |> String.split("@")
1741 def full_nickname(nickname_or_mention),
1742 do: String.trim_leading(nickname_or_mention, "@")
1744 def error_user(ap_id) do
1748 nickname: "erroruser@example.com",
1749 inserted_at: NaiveDateTime.utc_now()
1753 @spec all_superusers() :: [User.t()]
1754 def all_superusers do
1755 User.Query.build(%{super_users: true, local: true, deactivated: false})
1759 def muting_reblogs?(%User{} = user, %User{} = target) do
1760 UserRelationship.reblog_mute_exists?(user, target)
1763 def showing_reblogs?(%User{} = user, %User{} = target) do
1764 not muting_reblogs?(user, target)
1768 The function returns a query to get users with no activity for given interval of days.
1769 Inactive users are those who didn't read any notification, or had any activity where
1770 the user is the activity's actor, during `inactivity_threshold` days.
1771 Deactivated users will not appear in this list.
1775 iex> Pleroma.User.list_inactive_users()
1778 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1779 def list_inactive_users_query(inactivity_threshold \\ 7) do
1780 negative_inactivity_threshold = -inactivity_threshold
1781 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1782 # Subqueries are not supported in `where` clauses, join gets too complicated.
1783 has_read_notifications =
1784 from(n in Pleroma.Notification,
1785 where: n.seen == true,
1787 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1790 |> Pleroma.Repo.all()
1792 from(u in Pleroma.User,
1793 left_join: a in Pleroma.Activity,
1794 on: u.ap_id == a.actor,
1795 where: not is_nil(u.nickname),
1796 where: u.deactivated != ^true,
1797 where: u.id not in ^has_read_notifications,
1800 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1801 is_nil(max(a.inserted_at))
1806 Enable or disable email notifications for user
1810 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1811 Pleroma.User{email_notifications: %{"digest" => true}}
1813 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1814 Pleroma.User{email_notifications: %{"digest" => false}}
1816 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1817 {:ok, t()} | {:error, Ecto.Changeset.t()}
1818 def switch_email_notifications(user, type, status) do
1819 User.update_email_notifications(user, %{type => status})
1823 Set `last_digest_emailed_at` value for the user to current time
1825 @spec touch_last_digest_emailed_at(t()) :: t()
1826 def touch_last_digest_emailed_at(user) do
1827 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1829 {:ok, updated_user} =
1831 |> change(%{last_digest_emailed_at: now})
1832 |> update_and_set_cache()
1837 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1838 def toggle_confirmation(%User{} = user) do
1840 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1841 |> update_and_set_cache()
1844 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1845 def toggle_confirmation(users) do
1846 Enum.map(users, &toggle_confirmation/1)
1849 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1853 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1854 # use instance-default
1855 config = Pleroma.Config.get([:assets, :mascots])
1856 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1857 mascot = Keyword.get(config, default_mascot)
1860 "id" => "default-mascot",
1861 "url" => mascot[:url],
1862 "preview_url" => mascot[:url],
1864 "mime_type" => mascot[:mime_type]
1869 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1871 def ensure_keys_present(%User{} = user) do
1872 with {:ok, pem} <- Keys.generate_rsa_pem() do
1874 |> cast(%{keys: pem}, [:keys])
1875 |> validate_required([:keys])
1876 |> update_and_set_cache()
1880 def get_ap_ids_by_nicknames(nicknames) do
1882 where: u.nickname in ^nicknames,
1888 defdelegate search(query, opts \\ []), to: User.Search
1890 defp put_password_hash(
1891 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1893 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1896 defp put_password_hash(changeset), do: changeset
1898 def is_internal_user?(%User{nickname: nil}), do: true
1899 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1900 def is_internal_user?(_), do: false
1902 # A hack because user delete activities have a fake id for whatever reason
1903 # TODO: Get rid of this
1904 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1906 def get_delivered_users_by_object_id(object_id) do
1908 inner_join: delivery in assoc(u, :deliveries),
1909 where: delivery.object_id == ^object_id
1914 def change_email(user, email) do
1916 |> cast(%{email: email}, [:email])
1917 |> validate_required([:email])
1918 |> unique_constraint(:email)
1919 |> validate_format(:email, @email_regex)
1920 |> update_and_set_cache()
1923 # Internal function; public one is `deactivate/2`
1924 defp set_activation_status(user, deactivated) do
1926 |> cast(%{deactivated: deactivated}, [:deactivated])
1927 |> update_and_set_cache()
1930 def update_banner(user, banner) do
1932 |> cast(%{banner: banner}, [:banner])
1933 |> update_and_set_cache()
1936 def update_background(user, background) do
1938 |> cast(%{background: background}, [:background])
1939 |> update_and_set_cache()
1942 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1945 moderator: is_moderator
1949 def validate_fields(changeset, remote? \\ false) do
1950 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1951 limit = Pleroma.Config.get([:instance, limit_name], 0)
1954 |> validate_length(:fields, max: limit)
1955 |> validate_change(:fields, fn :fields, fields ->
1956 if Enum.all?(fields, &valid_field?/1) do
1964 defp valid_field?(%{"name" => name, "value" => value}) do
1965 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1966 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1968 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1969 String.length(value) <= value_limit
1972 defp valid_field?(_), do: false
1974 defp truncate_field(%{"name" => name, "value" => value}) do
1976 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1979 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1981 %{"name" => name, "value" => value}
1984 def admin_api_update(user, params) do
1991 |> update_and_set_cache()
1994 @doc "Signs user out of all applications"
1995 def global_sign_out(user) do
1996 OAuth.Authorization.delete_user_authorizations(user)
1997 OAuth.Token.delete_user_tokens(user)
2000 def mascot_update(user, url) do
2002 |> cast(%{mascot: url}, [:mascot])
2003 |> validate_required([:mascot])
2004 |> update_and_set_cache()
2007 def mastodon_settings_update(user, settings) do
2009 |> cast(%{settings: settings}, [:settings])
2010 |> validate_required([:settings])
2011 |> update_and_set_cache()
2014 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2015 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2017 if need_confirmation? do
2019 confirmation_pending: true,
2020 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2024 confirmation_pending: false,
2025 confirmation_token: nil
2029 cast(user, params, [:confirmation_pending, :confirmation_token])
2032 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2033 if id not in user.pinned_activities do
2034 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
2035 params = %{pinned_activities: user.pinned_activities ++ [id]}
2038 |> cast(params, [:pinned_activities])
2039 |> validate_length(:pinned_activities,
2040 max: max_pinned_statuses,
2041 message: "You have already pinned the maximum number of statuses"
2046 |> update_and_set_cache()
2049 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2050 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2053 |> cast(params, [:pinned_activities])
2054 |> update_and_set_cache()
2057 def update_email_notifications(user, settings) do
2058 email_notifications =
2059 user.email_notifications
2060 |> Map.merge(settings)
2061 |> Map.take(["digest"])
2063 params = %{email_notifications: email_notifications}
2064 fields = [:email_notifications]
2067 |> cast(params, fields)
2068 |> validate_required(fields)
2069 |> update_and_set_cache()
2072 defp set_domain_blocks(user, domain_blocks) do
2073 params = %{domain_blocks: domain_blocks}
2076 |> cast(params, [:domain_blocks])
2077 |> validate_required([:domain_blocks])
2078 |> update_and_set_cache()
2081 def block_domain(user, domain_blocked) do
2082 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2085 def unblock_domain(user, domain_blocked) do
2086 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2089 @spec add_to_block(User.t(), User.t()) ::
2090 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2091 defp add_to_block(%User{} = user, %User{} = blocked) do
2092 UserRelationship.create_block(user, blocked)
2095 @spec add_to_block(User.t(), User.t()) ::
2096 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2097 defp remove_from_block(%User{} = user, %User{} = blocked) do
2098 UserRelationship.delete_block(user, blocked)
2101 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2102 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2103 {:ok, user_notification_mute} <-
2104 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2106 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2110 defp remove_from_mutes(user, %User{} = muted_user) do
2111 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2112 {:ok, user_notification_mute} <-
2113 UserRelationship.delete_notification_mute(user, muted_user) do
2114 {:ok, [user_mute, user_notification_mute]}
2118 def set_invisible(user, invisible) do
2119 params = %{invisible: invisible}
2122 |> cast(params, [:invisible])
2123 |> validate_required([:invisible])
2124 |> update_and_set_cache()
2127 def sanitize_html(%User{} = user) do
2128 sanitize_html(user, nil)
2131 # User data that mastodon isn't filtering (treated as plaintext):
2134 def sanitize_html(%User{} = user, filter) do
2136 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2139 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2144 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2145 |> Map.put(:fields, fields)