1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 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
29 alias Pleroma.UserRelationship
30 alias Pleroma.Web.ActivityPub.ActivityPub
31 alias Pleroma.Web.ActivityPub.Builder
32 alias Pleroma.Web.ActivityPub.Pipeline
33 alias Pleroma.Web.ActivityPub.Utils
34 alias Pleroma.Web.CommonAPI
35 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
36 alias Pleroma.Web.Endpoint
37 alias Pleroma.Web.OAuth
38 alias Pleroma.Web.RelMe
39 alias Pleroma.Workers.BackgroundWorker
43 @type t :: %__MODULE__{}
44 @type account_status ::
47 | :password_reset_pending
48 | :confirmation_pending
50 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
52 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
53 @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])?)*$/
55 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
56 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
58 # AP ID user relationships (blocks, mutes etc.)
59 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
60 @user_relationships_config [
62 blocker_blocks: :blocked_users,
63 blockee_blocks: :blocker_users
66 muter_mutes: :muted_users,
67 mutee_mutes: :muter_users
70 reblog_muter_mutes: :reblog_muted_users,
71 reblog_mutee_mutes: :reblog_muter_users
74 notification_muter_mutes: :notification_muted_users,
75 notification_mutee_mutes: :notification_muter_users
77 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
78 inverse_subscription: [
79 subscribee_subscriptions: :subscriber_users,
80 subscriber_subscriptions: :subscribee_users
83 endorser_endorsements: :endorsed_users,
84 endorsee_endorsements: :endorser_users
88 @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
91 field(:bio, :string, default: "")
92 field(:raw_bio, :string)
93 field(:email, :string)
95 field(:nickname, :string)
96 field(:password_hash, :string)
97 field(:password, :string, virtual: true)
98 field(:password_confirmation, :string, virtual: true)
100 field(:public_key, :string)
101 field(:ap_id, :string)
102 field(:avatar, :map, default: %{})
103 field(:local, :boolean, default: true)
104 field(:follower_address, :string)
105 field(:following_address, :string)
106 field(:featured_address, :string)
107 field(:search_rank, :float, virtual: true)
108 field(:search_type, :integer, virtual: true)
109 field(:tags, {:array, :string}, default: [])
110 field(:last_refreshed_at, :naive_datetime_usec)
111 field(:last_digest_emailed_at, :naive_datetime)
112 field(:banner, :map, default: %{})
113 field(:background, :map, default: %{})
114 field(:note_count, :integer, default: 0)
115 field(:follower_count, :integer, default: 0)
116 field(:following_count, :integer, default: 0)
117 field(:is_locked, :boolean, default: false)
118 field(:is_confirmed, :boolean, default: true)
119 field(:password_reset_pending, :boolean, default: false)
120 field(:is_approved, :boolean, default: true)
121 field(:registration_reason, :string, default: nil)
122 field(:confirmation_token, :string, default: nil)
123 field(:default_scope, :string, default: "public")
124 field(:domain_blocks, {:array, :string}, default: [])
125 field(:is_active, :boolean, default: true)
126 field(:no_rich_text, :boolean, default: false)
127 field(:ap_enabled, :boolean, default: false)
128 field(:is_moderator, :boolean, default: false)
129 field(:is_admin, :boolean, default: false)
130 field(:show_role, :boolean, default: true)
131 field(:uri, ObjectValidators.Uri, default: nil)
132 field(:hide_followers_count, :boolean, default: false)
133 field(:hide_follows_count, :boolean, default: false)
134 field(:hide_followers, :boolean, default: false)
135 field(:hide_follows, :boolean, default: false)
136 field(:hide_favorites, :boolean, default: true)
137 field(:email_notifications, :map, default: %{"digest" => false})
138 field(:mascot, :map, default: nil)
139 field(:emoji, :map, default: %{})
140 field(:pleroma_settings_store, :map, default: %{})
141 field(:fields, {:array, :map}, default: [])
142 field(:raw_fields, {:array, :map}, default: [])
143 field(:is_discoverable, :boolean, default: false)
144 field(:invisible, :boolean, default: false)
145 field(:allow_following_move, :boolean, default: true)
146 field(:skip_thread_containment, :boolean, default: false)
147 field(:actor_type, :string, default: "Person")
148 field(:also_known_as, {:array, ObjectValidators.ObjectID}, default: [])
149 field(:inbox, :string)
150 field(:shared_inbox, :string)
151 field(:accepts_chat_messages, :boolean, default: nil)
152 field(:last_active_at, :naive_datetime)
153 field(:disclose_client, :boolean, default: true)
154 field(:pinned_objects, :map, default: %{})
155 field(:is_suggested, :boolean, default: false)
156 field(:last_status_at, :naive_datetime)
157 field(:birthday, :date)
158 field(:show_birthday, :boolean, default: false)
159 field(:language, :string)
162 :notification_settings,
163 Pleroma.User.NotificationSetting,
167 has_many(:notifications, Notification)
168 has_many(:registrations, Registration)
169 has_many(:deliveries, Delivery)
171 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
172 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
174 for {relationship_type,
176 {outgoing_relation, outgoing_relation_target},
177 {incoming_relation, incoming_relation_source}
178 ]} <- @user_relationships_config do
179 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
180 # :notification_muter_mutes, :subscribee_subscriptions, :endorser_endorsements
181 has_many(outgoing_relation, UserRelationship,
182 foreign_key: :source_id,
183 where: [relationship_type: relationship_type]
186 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
187 # :notification_mutee_mutes, :subscriber_subscriptions, :endorsee_endorsements
188 has_many(incoming_relation, UserRelationship,
189 foreign_key: :target_id,
190 where: [relationship_type: relationship_type]
193 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
194 # :notification_muted_users, :subscriber_users, :endorsed_users
195 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
197 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
198 # :notification_muter_users, :subscribee_users, :endorser_users
199 has_many(incoming_relation_source, through: [incoming_relation, :source])
202 # `:blocks` is deprecated (replaced with `blocked_users` relation)
203 field(:blocks, {:array, :string}, default: [])
204 # `:mutes` is deprecated (replaced with `muted_users` relation)
205 field(:mutes, {:array, :string}, default: [])
206 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
207 field(:muted_reblogs, {:array, :string}, default: [])
208 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
209 field(:muted_notifications, {:array, :string}, default: [])
210 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
211 field(:subscribers, {:array, :string}, default: [])
214 :multi_factor_authentication_settings,
222 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
223 @user_relationships_config do
224 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
225 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
226 # `def subscriber_users/2`, `def endorsed_users_relation/2`
227 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
228 target_users_query = assoc(user, unquote(outgoing_relation_target))
230 if restrict_deactivated? do
232 |> User.Query.build(%{deactivated: false})
238 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
239 # `def notification_muted_users/2`, `def subscriber_users/2`, `def endorsed_users/2`
240 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
242 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
244 restrict_deactivated?
249 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
250 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`,
251 # `def endorsed_users_ap_ids/2`
252 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
254 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
256 restrict_deactivated?
258 |> select([u], u.ap_id)
263 def cached_blocked_users_ap_ids(user) do
264 @cachex.fetch!(:user_cache, "blocked_users_ap_ids:#{user.ap_id}", fn _ ->
265 blocked_users_ap_ids(user)
269 def cached_muted_users_ap_ids(user) do
270 @cachex.fetch!(:user_cache, "muted_users_ap_ids:#{user.ap_id}", fn _ ->
271 muted_users_ap_ids(user)
275 defdelegate following_count(user), to: FollowingRelationship
276 defdelegate following(user), to: FollowingRelationship
277 defdelegate following?(follower, followed), to: FollowingRelationship
278 defdelegate following_ap_ids(user), to: FollowingRelationship
279 defdelegate get_follow_requests(user), to: FollowingRelationship
280 defdelegate search(query, opts \\ []), to: User.Search
283 Dumps Flake Id to SQL-compatible format (16-byte UUID).
284 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
286 def binary_id(source_id) when is_binary(source_id) do
287 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
294 def binary_id(source_ids) when is_list(source_ids) do
295 Enum.map(source_ids, &binary_id/1)
298 def binary_id(%User{} = user), do: binary_id(user.id)
300 @doc "Returns status account"
301 @spec account_status(User.t()) :: account_status()
302 def account_status(%User{is_active: false}), do: :deactivated
303 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
304 def account_status(%User{local: true, is_approved: false}), do: :approval_pending
305 def account_status(%User{local: true, is_confirmed: false}), do: :confirmation_pending
306 def account_status(%User{}), do: :active
308 @spec visible_for(User.t(), User.t() | nil) ::
311 | :restricted_unauthenticated
313 | :confirmation_pending
314 def visible_for(user, for_user \\ nil)
316 def visible_for(%User{invisible: true}, _), do: :invisible
318 def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
320 def visible_for(%User{} = user, nil) do
321 if restrict_unauthenticated?(user) do
322 :restrict_unauthenticated
324 visible_account_status(user)
328 def visible_for(%User{} = user, for_user) do
329 if privileged?(for_user, :users_manage_activation_state) do
332 visible_account_status(user)
336 def visible_for(_, _), do: :invisible
338 defp restrict_unauthenticated?(%User{local: true}) do
339 Config.restrict_unauthenticated_access?(:profiles, :local)
342 defp restrict_unauthenticated?(%User{local: _}) do
343 Config.restrict_unauthenticated_access?(:profiles, :remote)
346 defp visible_account_status(user) do
347 status = account_status(user)
349 if status in [:active, :password_reset_pending] do
356 @spec privileged?(User.t(), atom()) :: boolean()
357 def privileged?(%User{is_admin: false, is_moderator: false}, _), do: false
360 %User{local: true, is_admin: is_admin, is_moderator: is_moderator},
364 privileged_for?(privilege_tag, is_admin, :admin_privileges) or
365 privileged_for?(privilege_tag, is_moderator, :moderator_privileges)
367 def privileged?(_, _), do: false
369 defp privileged_for?(privilege_tag, true, config_role_key),
370 do: privilege_tag in Config.get([:instance, config_role_key])
372 defp privileged_for?(_, _, _), do: false
374 @spec privileges(User.t()) :: [atom()]
375 def privileges(%User{local: false}) do
379 def privileges(%User{is_moderator: false, is_admin: false}) do
383 def privileges(%User{local: true, is_moderator: true, is_admin: true}) do
384 (Config.get([:instance, :moderator_privileges]) ++ Config.get([:instance, :admin_privileges]))
388 def privileges(%User{local: true, is_moderator: true, is_admin: false}) do
389 Config.get([:instance, :moderator_privileges])
392 def privileges(%User{local: true, is_moderator: false, is_admin: true}) do
393 Config.get([:instance, :admin_privileges])
396 @spec invisible?(User.t()) :: boolean()
397 def invisible?(%User{invisible: true}), do: true
398 def invisible?(_), do: false
400 def avatar_url(user, options \\ []) do
402 %{"url" => [%{"href" => href} | _]} ->
406 unless options[:no_default] do
407 Config.get([:assets, :default_user_avatar], "#{Endpoint.url()}/images/avi.png")
412 def banner_url(user, options \\ []) do
414 %{"url" => [%{"href" => href} | _]} -> href
415 _ -> !options[:no_default] && "#{Endpoint.url()}/images/banner.png"
419 # Should probably be renamed or removed
420 @spec ap_id(User.t()) :: String.t()
421 def ap_id(%User{nickname: nickname}), do: "#{Endpoint.url()}/users/#{nickname}"
423 @spec ap_followers(User.t()) :: String.t()
424 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
425 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
427 @spec ap_following(User.t()) :: String.t()
428 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
429 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
431 @spec ap_featured_collection(User.t()) :: String.t()
432 def ap_featured_collection(%User{featured_address: fa}) when is_binary(fa), do: fa
434 def ap_featured_collection(%User{} = user), do: "#{ap_id(user)}/collections/featured"
436 defp truncate_fields_param(params) do
437 if Map.has_key?(params, :fields) do
438 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
444 defp truncate_if_exists(params, key, max_length) do
445 if Map.has_key?(params, key) and is_binary(params[key]) do
446 {value, _chopped} = String.split_at(params[key], max_length)
447 Map.put(params, key, value)
453 defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
455 defp fix_follower_address(%{nickname: nickname} = params),
456 do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
458 defp fix_follower_address(params), do: params
460 def remote_user_changeset(struct \\ %User{local: false}, params) do
461 bio_limit = Config.get([:instance, :user_bio_length], 5000)
462 name_limit = Config.get([:instance, :user_name_length], 100)
465 case params[:name] do
466 name when is_binary(name) and byte_size(name) > 0 -> name
467 _ -> params[:nickname]
472 |> Map.put(:name, name)
473 |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
474 |> truncate_if_exists(:name, name_limit)
475 |> truncate_if_exists(:bio, bio_limit)
476 |> truncate_fields_param()
477 |> fix_follower_address()
501 :hide_followers_count,
510 :accepts_chat_messages,
516 |> cast(params, [:name], empty_values: [])
517 |> validate_required([:ap_id])
518 |> validate_required([:name], trim: false)
519 |> unique_constraint(:nickname)
520 |> validate_format(:nickname, @email_regex)
521 |> validate_length(:bio, max: bio_limit)
522 |> validate_length(:name, max: name_limit)
523 |> validate_fields(true)
524 |> validate_non_local()
527 defp validate_non_local(cng) do
528 local? = get_field(cng, :local)
532 |> add_error(:local, "User is local, can't update with this changeset.")
538 def update_changeset(struct, params \\ %{}) do
539 bio_limit = Config.get([:instance, :user_bio_length], 5000)
540 name_limit = Config.get([:instance, :user_name_length], 100)
560 :hide_followers_count,
563 :allow_following_move,
567 :skip_thread_containment,
570 :pleroma_settings_store,
573 :accepts_chat_messages,
579 |> validate_min_age()
580 |> unique_constraint(:nickname)
581 |> validate_format(:nickname, local_nickname_regex())
582 |> validate_length(:bio, max: bio_limit)
583 |> validate_length(:name, min: 1, max: name_limit)
584 |> validate_inclusion(:actor_type, ["Person", "Service"])
587 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
588 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
589 |> put_change_if_present(:banner, &put_upload(&1, :banner))
590 |> put_change_if_present(:background, &put_upload(&1, :background))
591 |> put_change_if_present(
592 :pleroma_settings_store,
593 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
595 |> validate_fields(false)
598 defp put_fields(changeset) do
599 if raw_fields = get_change(changeset, :raw_fields) do
602 |> Enum.filter(fn %{"name" => n} -> n != "" end)
606 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
609 |> put_change(:raw_fields, raw_fields)
610 |> put_change(:fields, fields)
616 defp parse_fields(value) do
618 |> Formatter.linkify(mentions_format: :full)
622 defp put_emoji(changeset) do
623 emojified_fields = [:bio, :name, :raw_fields]
625 if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
626 bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
627 name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
629 emoji = Map.merge(bio, name)
633 |> get_field(:raw_fields)
634 |> Enum.reduce(emoji, fn x, acc ->
635 Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
638 put_change(changeset, :emoji, emoji)
644 defp put_change_if_present(changeset, map_field, value_function) do
645 with {:ok, value} <- fetch_change(changeset, map_field),
646 {:ok, new_value} <- value_function.(value) do
647 put_change(changeset, map_field, new_value)
649 {:error, :file_too_large} ->
650 Ecto.Changeset.validate_change(changeset, map_field, fn map_field, _value ->
651 [{map_field, "file is too large"}]
659 defp put_upload(value, type) do
660 with %Plug.Upload{} <- value,
661 {:ok, object} <- ActivityPub.upload(value, type: type) do
666 def update_as_admin_changeset(struct, params) do
668 |> update_changeset(params)
669 |> cast(params, [:email])
670 |> delete_change(:also_known_as)
671 |> unique_constraint(:email)
672 |> validate_format(:email, @email_regex)
673 |> validate_inclusion(:actor_type, ["Person", "Service"])
676 @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
677 def update_as_admin(user, params) do
678 params = Map.put(params, "password_confirmation", params["password"])
679 changeset = update_as_admin_changeset(user, params)
681 if params["password"] do
682 reset_password(user, changeset, params)
684 User.update_and_set_cache(changeset)
688 def password_update_changeset(struct, params) do
690 |> cast(params, [:password, :password_confirmation])
691 |> validate_required([:password, :password_confirmation])
692 |> validate_confirmation(:password)
693 |> put_password_hash()
694 |> put_change(:password_reset_pending, false)
697 @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
698 def reset_password(%User{} = user, params) do
699 reset_password(user, user, params)
702 def reset_password(%User{id: user_id} = user, struct, params) do
705 |> Multi.update(:user, password_update_changeset(struct, params))
706 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
707 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
709 case Repo.transaction(multi) do
710 {:ok, %{user: user} = _} -> set_cache(user)
711 {:error, _, changeset, _} -> {:error, changeset}
715 def update_password_reset_pending(user, value) do
718 |> put_change(:password_reset_pending, value)
719 |> update_and_set_cache()
722 def force_password_reset_async(user) do
723 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
726 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
727 def force_password_reset(user), do: update_password_reset_pending(user, true)
729 # Used to auto-register LDAP accounts which won't have a password hash stored locally
730 def register_changeset_ldap(struct, params = %{password: password})
731 when is_nil(password) do
732 params = Map.put_new(params, :accepts_chat_messages, true)
735 if Map.has_key?(params, :email) do
736 Map.put_new(params, :email, params[:email])
746 :accepts_chat_messages
748 |> validate_required([:name, :nickname])
749 |> unique_constraint(:nickname)
750 |> validate_not_restricted_nickname(:nickname)
751 |> validate_format(:nickname, local_nickname_regex())
753 |> unique_constraint(:ap_id)
754 |> put_following_and_follower_and_featured_address()
758 def register_changeset(struct, params \\ %{}, opts \\ []) do
759 bio_limit = Config.get([:instance, :user_bio_length], 5000)
760 name_limit = Config.get([:instance, :user_name_length], 100)
761 reason_limit = Config.get([:instance, :registration_reason_length], 500)
762 params = Map.put_new(params, :accepts_chat_messages, true)
765 if is_nil(opts[:confirmed]) do
766 !Config.get([:instance, :account_activation_required])
772 if is_nil(opts[:approved]) do
773 !Config.get([:instance, :account_approval_required])
779 |> confirmation_changeset(set_confirmation: confirmed?)
780 |> approval_changeset(set_approval: approved?)
788 :password_confirmation,
790 :accepts_chat_messages,
791 :registration_reason,
795 |> validate_required([:name, :nickname, :password, :password_confirmation])
796 |> validate_confirmation(:password)
797 |> unique_constraint(:email)
798 |> validate_format(:email, @email_regex)
799 |> validate_email_not_in_blacklisted_domain(:email)
800 |> unique_constraint(:nickname)
801 |> validate_not_restricted_nickname(:nickname)
802 |> validate_format(:nickname, local_nickname_regex())
803 |> validate_length(:bio, max: bio_limit)
804 |> validate_length(:name, min: 1, max: name_limit)
805 |> validate_length(:registration_reason, max: reason_limit)
806 |> maybe_validate_required_email(opts[:external])
807 |> maybe_validate_required_birthday
808 |> validate_min_age()
811 |> unique_constraint(:ap_id)
812 |> put_following_and_follower_and_featured_address()
816 def validate_not_restricted_nickname(changeset, field) do
817 validate_change(changeset, field, fn _, value ->
819 Config.get([User, :restricted_nicknames])
820 |> Enum.all?(fn restricted_nickname ->
821 String.downcase(value) != String.downcase(restricted_nickname)
824 if valid?, do: [], else: [nickname: "Invalid nickname"]
828 def validate_email_not_in_blacklisted_domain(changeset, field) do
829 validate_change(changeset, field, fn _, value ->
831 Config.get([User, :email_blacklist])
832 |> Enum.all?(fn blacklisted_domain ->
833 blacklisted_domain_downcase = String.downcase(blacklisted_domain)
835 !String.ends_with?(String.downcase(value), [
836 "@" <> blacklisted_domain_downcase,
837 "." <> blacklisted_domain_downcase
841 if valid?, do: [], else: [email: "Invalid email"]
845 def maybe_validate_required_email(changeset, true), do: changeset
847 def maybe_validate_required_email(changeset, _) do
848 if Config.get([:instance, :account_activation_required]) do
849 validate_required(changeset, [:email])
855 defp maybe_validate_required_birthday(changeset) do
856 if Config.get([:instance, :birthday_required]) do
857 validate_required(changeset, [:birthday])
863 defp validate_min_age(changeset) do
865 |> validate_change(:birthday, fn :birthday, birthday ->
868 |> Date.diff(birthday) >=
869 Config.get([:instance, :birthday_min_age])
871 if valid?, do: [], else: [birthday: "Invalid age"]
875 defp put_ap_id(changeset) do
876 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
877 put_change(changeset, :ap_id, ap_id)
880 defp put_following_and_follower_and_featured_address(changeset) do
881 user = %User{nickname: get_field(changeset, :nickname)}
882 followers = ap_followers(user)
883 following = ap_following(user)
884 featured = ap_featured_collection(user)
887 |> put_change(:follower_address, followers)
888 |> put_change(:following_address, following)
889 |> put_change(:featured_address, featured)
892 defp put_private_key(changeset) do
893 {:ok, pem} = Keys.generate_rsa_pem()
894 put_change(changeset, :keys, pem)
897 defp autofollow_users(user) do
898 candidates = Config.get([:instance, :autofollowed_nicknames])
901 User.Query.build(%{nickname: candidates, local: true, is_active: true})
904 follow_all(user, autofollowed_users)
907 defp autofollowing_users(user) do
908 candidates = Config.get([:instance, :autofollowing_nicknames])
910 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
912 |> Enum.each(&follow(&1, user, :follow_accept))
917 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
918 def register(%Ecto.Changeset{} = changeset) do
919 with {:ok, user} <- Repo.insert(changeset) do
920 post_register_action(user)
924 def post_register_action(%User{is_confirmed: false} = user) do
925 with {:ok, _} <- maybe_send_confirmation_email(user) do
930 def post_register_action(%User{is_approved: false} = user) do
931 with {:ok, _} <- send_user_approval_email(user),
932 {:ok, _} <- send_admin_approval_emails(user) do
937 def post_register_action(%User{is_approved: true, is_confirmed: true} = user) do
938 with {:ok, user} <- autofollow_users(user),
939 {:ok, _} <- autofollowing_users(user),
940 {:ok, user} <- set_cache(user),
941 {:ok, _} <- maybe_send_registration_email(user),
942 {:ok, _} <- maybe_send_welcome_email(user),
943 {:ok, _} <- maybe_send_welcome_message(user),
944 {:ok, _} <- maybe_send_welcome_chat_message(user) do
949 defp send_user_approval_email(%User{email: email} = user) when is_binary(email) do
951 |> Pleroma.Emails.UserEmail.approval_pending_email()
952 |> Pleroma.Emails.Mailer.deliver_async()
957 defp send_user_approval_email(_user) do
961 defp send_admin_approval_emails(user) do
963 |> Enum.filter(fn user -> not is_nil(user.email) end)
964 |> Enum.each(fn superuser ->
966 |> Pleroma.Emails.AdminEmail.new_unapproved_registration(user)
967 |> Pleroma.Emails.Mailer.deliver_async()
973 defp maybe_send_welcome_message(user) do
974 if User.WelcomeMessage.enabled?() do
975 User.WelcomeMessage.post_message(user)
982 defp maybe_send_welcome_chat_message(user) do
983 if User.WelcomeChatMessage.enabled?() do
984 User.WelcomeChatMessage.post_message(user)
991 defp maybe_send_welcome_email(%User{email: email} = user) when is_binary(email) do
992 if User.WelcomeEmail.enabled?() do
993 User.WelcomeEmail.send_email(user)
1000 defp maybe_send_welcome_email(_), do: {:ok, :noop}
1002 @spec maybe_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
1003 def maybe_send_confirmation_email(%User{is_confirmed: false, email: email} = user)
1004 when is_binary(email) do
1005 if Config.get([:instance, :account_activation_required]) do
1006 send_confirmation_email(user)
1013 def maybe_send_confirmation_email(_), do: {:ok, :noop}
1015 @spec send_confirmation_email(Uset.t()) :: User.t()
1016 def send_confirmation_email(%User{} = user) do
1018 |> Pleroma.Emails.UserEmail.account_confirmation_email()
1019 |> Pleroma.Emails.Mailer.deliver_async()
1024 @spec maybe_send_registration_email(User.t()) :: {:ok, :enqueued | :noop}
1025 defp maybe_send_registration_email(%User{email: email} = user) when is_binary(email) do
1026 with false <- User.WelcomeEmail.enabled?(),
1027 false <- Config.get([:instance, :account_activation_required], false),
1028 false <- Config.get([:instance, :account_approval_required], false) do
1030 |> Pleroma.Emails.UserEmail.successful_registration_email()
1031 |> Pleroma.Emails.Mailer.deliver_async()
1040 defp maybe_send_registration_email(_), do: {:ok, :noop}
1042 def needs_update?(%User{local: true}), do: false
1044 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
1046 def needs_update?(%User{local: false} = user) do
1047 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
1050 def needs_update?(_), do: true
1052 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
1054 # "Locked" (self-locked) users demand explicit authorization of follow requests
1055 def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do
1056 follow(follower, followed, :follow_pending)
1059 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
1060 follow(follower, followed)
1063 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
1064 if not ap_enabled?(followed) do
1065 follow(follower, followed)
1067 {:ok, follower, followed}
1071 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
1072 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
1073 def follow_all(follower, followeds) do
1075 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
1076 |> Enum.each(&follow(follower, &1, :follow_accept))
1081 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
1082 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
1085 not followed.is_active ->
1086 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
1088 deny_follow_blocked and blocks?(followed, follower) ->
1089 {:error, "Could not follow user: #{followed.nickname} blocked you."}
1092 FollowingRelationship.follow(follower, followed, state)
1096 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
1097 {:error, "Not subscribed!"}
1100 @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
1101 def unfollow(%User{} = follower, %User{} = followed) do
1102 case do_unfollow(follower, followed) do
1103 {:ok, follower, followed} ->
1104 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
1111 @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
1112 defp do_unfollow(%User{} = follower, %User{} = followed) do
1113 case get_follow_state(follower, followed) do
1114 state when state in [:follow_pending, :follow_accept] ->
1115 FollowingRelationship.unfollow(follower, followed)
1118 {:error, "Not subscribed!"}
1122 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
1123 def get_follow_state(%User{} = follower, %User{} = following) do
1124 following_relationship = FollowingRelationship.get(follower, following)
1125 get_follow_state(follower, following, following_relationship)
1128 def get_follow_state(
1130 %User{} = following,
1131 following_relationship
1133 case {following_relationship, following.local} do
1135 case Utils.fetch_latest_follow(follower, following) do
1136 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
1137 FollowingRelationship.state_to_enum(state)
1143 {%{state: state}, _} ->
1151 def locked?(%User{} = user) do
1152 user.is_locked || false
1155 def get_by_id(id) do
1156 Repo.get_by(User, id: id)
1159 def get_by_ap_id(ap_id) do
1160 Repo.get_by(User, ap_id: ap_id)
1163 def get_by_uri(uri) do
1164 Repo.get_by(User, uri: uri)
1167 def get_all_by_ap_id(ap_ids) do
1168 from(u in __MODULE__,
1169 where: u.ap_id in ^ap_ids
1174 def get_all_by_ids(ids) do
1175 from(u in __MODULE__, where: u.id in ^ids)
1179 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
1180 # of the ap_id and the domain and tries to get that user
1181 def get_by_guessed_nickname(ap_id) do
1182 domain = URI.parse(ap_id).host
1183 name = List.last(String.split(ap_id, "/"))
1184 nickname = "#{name}@#{domain}"
1186 get_cached_by_nickname(nickname)
1189 def set_cache({:ok, user}), do: set_cache(user)
1190 def set_cache({:error, err}), do: {:error, err}
1192 def set_cache(%User{} = user) do
1193 @cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1194 @cachex.put(:user_cache, "nickname:#{user.nickname}", user)
1195 @cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
1199 def update_and_set_cache(struct, params) do
1201 |> update_changeset(params)
1202 |> update_and_set_cache()
1205 def update_and_set_cache(changeset) do
1206 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
1211 def get_user_friends_ap_ids(user) do
1212 from(u in User.get_friends_query(user), select: u.ap_id)
1216 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
1217 def get_cached_user_friends_ap_ids(user) do
1218 @cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
1219 get_user_friends_ap_ids(user)
1223 def invalidate_cache(user) do
1224 @cachex.del(:user_cache, "ap_id:#{user.ap_id}")
1225 @cachex.del(:user_cache, "nickname:#{user.nickname}")
1226 @cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
1227 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
1228 @cachex.del(:user_cache, "muted_users_ap_ids:#{user.ap_id}")
1231 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
1232 def get_cached_by_ap_id(ap_id) do
1233 key = "ap_id:#{ap_id}"
1235 with {:ok, nil} <- @cachex.get(:user_cache, key),
1236 user when not is_nil(user) <- get_by_ap_id(ap_id),
1237 {:ok, true} <- @cachex.put(:user_cache, key, user) do
1245 def get_cached_by_id(id) do
1249 @cachex.fetch!(:user_cache, key, fn _ ->
1250 user = get_by_id(id)
1253 @cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1254 {:commit, user.ap_id}
1260 get_cached_by_ap_id(ap_id)
1263 def get_cached_by_nickname(nickname) do
1264 key = "nickname:#{nickname}"
1266 @cachex.fetch!(:user_cache, key, fn _ ->
1267 case get_or_fetch_by_nickname(nickname) do
1268 {:ok, user} -> {:commit, user}
1269 {:error, _error} -> {:ignore, nil}
1274 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
1275 restrict_to_local = Config.get([:instance, :limit_to_local_content])
1278 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
1279 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
1281 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
1282 get_cached_by_nickname(nickname_or_id)
1284 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
1285 get_cached_by_nickname(nickname_or_id)
1292 @spec get_by_nickname(String.t()) :: User.t() | nil
1293 def get_by_nickname(nickname) do
1294 Repo.get_by(User, nickname: nickname) ||
1295 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
1296 Repo.get_by(User, nickname: local_nickname(nickname))
1300 def get_by_email(email), do: Repo.get_by(User, email: email)
1302 def get_by_nickname_or_email(nickname_or_email) do
1303 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
1306 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
1308 def get_or_fetch_by_nickname(nickname) do
1309 with %User{} = user <- get_by_nickname(nickname) do
1313 with [_nick, _domain] <- String.split(nickname, "@"),
1314 {:ok, user} <- fetch_by_nickname(nickname) do
1317 _e -> {:error, "not found " <> nickname}
1322 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1323 def get_followers_query(%User{} = user, nil) do
1324 User.Query.build(%{followers: user, is_active: true})
1327 def get_followers_query(%User{} = user, page) do
1329 |> get_followers_query(nil)
1330 |> User.Query.paginate(page, 20)
1333 @spec get_followers_query(User.t()) :: Ecto.Query.t()
1334 def get_followers_query(%User{} = user), do: get_followers_query(user, nil)
1336 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1337 def get_followers(%User{} = user, page \\ nil) do
1339 |> get_followers_query(page)
1343 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1344 def get_external_followers(%User{} = user, page \\ nil) do
1346 |> get_followers_query(page)
1347 |> User.Query.build(%{external: true})
1351 def get_followers_ids(%User{} = user, page \\ nil) do
1353 |> get_followers_query(page)
1354 |> select([u], u.id)
1358 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1359 def get_friends_query(%User{} = user, nil) do
1360 User.Query.build(%{friends: user, deactivated: false})
1363 def get_friends_query(%User{} = user, page) do
1365 |> get_friends_query(nil)
1366 |> User.Query.paginate(page, 20)
1369 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1370 def get_friends_query(%User{} = user), do: get_friends_query(user, nil)
1372 def get_friends(%User{} = user, page \\ nil) do
1374 |> get_friends_query(page)
1378 def get_friends_ap_ids(%User{} = user) do
1380 |> get_friends_query(nil)
1381 |> select([u], u.ap_id)
1385 def get_friends_ids(%User{} = user, page \\ nil) do
1387 |> get_friends_query(page)
1388 |> select([u], u.id)
1392 def increase_note_count(%User{} = user) do
1394 |> where(id: ^user.id)
1395 |> update([u], inc: [note_count: 1])
1397 |> Repo.update_all([])
1399 {1, [user]} -> set_cache(user)
1404 def decrease_note_count(%User{} = user) do
1406 |> where(id: ^user.id)
1409 note_count: fragment("greatest(0, note_count - 1)")
1413 |> Repo.update_all([])
1415 {1, [user]} -> set_cache(user)
1420 def update_note_count(%User{} = user, note_count \\ nil) do
1425 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1431 |> cast(%{note_count: note_count}, [:note_count])
1432 |> update_and_set_cache()
1435 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1436 def maybe_fetch_follow_information(user) do
1437 with {:ok, user} <- fetch_follow_information(user) do
1441 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1447 def fetch_follow_information(user) do
1448 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1450 |> follow_information_changeset(info)
1451 |> update_and_set_cache()
1455 defp follow_information_changeset(user, params) do
1462 :hide_followers_count,
1467 @spec update_follower_count(User.t()) :: {:ok, User.t()}
1468 def update_follower_count(%User{} = user) do
1469 if user.local or !Config.get([:instance, :external_user_synchronization]) do
1470 follower_count = FollowingRelationship.follower_count(user)
1473 |> follow_information_changeset(%{follower_count: follower_count})
1474 |> update_and_set_cache
1476 {:ok, maybe_fetch_follow_information(user)}
1480 @spec update_following_count(User.t()) :: {:ok, User.t()}
1481 def update_following_count(%User{local: false} = user) do
1482 if Config.get([:instance, :external_user_synchronization]) do
1483 {:ok, maybe_fetch_follow_information(user)}
1489 def update_following_count(%User{local: true} = user) do
1490 following_count = FollowingRelationship.following_count(user)
1493 |> follow_information_changeset(%{following_count: following_count})
1494 |> update_and_set_cache()
1497 @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
1498 def get_users_from_set(ap_ids, opts \\ []) do
1499 local_only = Keyword.get(opts, :local_only, true)
1500 criteria = %{ap_id: ap_ids, is_active: true}
1501 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1503 User.Query.build(criteria)
1507 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1508 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1511 query = User.Query.build(%{recipients_from_activity: to, local: true, is_active: true})
1517 @spec mute(User.t(), User.t(), map()) ::
1518 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1519 def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do
1520 notifications? = Map.get(params, :notifications, true)
1521 duration = Map.get(params, :duration, 0)
1526 |> DateTime.add(duration)
1531 with {:ok, user_mute} <- UserRelationship.create_mute(muter, mutee, expires_at),
1532 {:ok, user_notification_mute} <-
1534 UserRelationship.create_notification_mute(
1541 Pleroma.Workers.MuteExpireWorker.enqueue(
1543 %{"muter_id" => muter.id, "mutee_id" => mutee.id},
1544 scheduled_at: expires_at
1548 @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
1550 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
1554 def unmute(%User{} = muter, %User{} = mutee) do
1555 with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee),
1556 {:ok, user_notification_mute} <-
1557 UserRelationship.delete_notification_mute(muter, mutee) do
1558 @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
1559 {:ok, [user_mute, user_notification_mute]}
1563 def unmute(muter_id, mutee_id) do
1564 with {:muter, %User{} = muter} <- {:muter, User.get_by_id(muter_id)},
1565 {:mutee, %User{} = mutee} <- {:mutee, User.get_by_id(mutee_id)} do
1566 unmute(muter, mutee)
1568 {who, result} = error ->
1570 "User.unmute/2 failed. #{who}: #{result}, muter_id: #{muter_id}, mutee_id: #{mutee_id}"
1577 def subscribe(%User{} = subscriber, %User{} = target) do
1578 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
1580 if blocks?(target, subscriber) and deny_follow_blocked do
1581 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1583 # Note: the relationship is inverse: subscriber acts as relationship target
1584 UserRelationship.create_inverse_subscription(target, subscriber)
1588 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1589 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1590 subscribe(subscriber, subscribee)
1594 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1595 # Note: the relationship is inverse: subscriber acts as relationship target
1596 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1599 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1600 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1601 unsubscribe(unsubscriber, user)
1605 def block(%User{} = blocker, %User{} = blocked) do
1606 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1608 if following?(blocker, blocked) do
1609 {:ok, blocker, _} = unfollow(blocker, blocked)
1615 # clear any requested follows from both sides as well
1617 case CommonAPI.reject_follow_request(blocked, blocker) do
1618 {:ok, %User{} = updated_blocked} -> updated_blocked
1623 case CommonAPI.reject_follow_request(blocker, blocked) do
1624 {:ok, %User{} = updated_blocker} -> updated_blocker
1628 unsubscribe(blocked, blocker)
1630 unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
1631 if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
1633 {:ok, blocker} = update_follower_count(blocker)
1634 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1635 add_to_block(blocker, blocked)
1638 # helper to handle the block given only an actor's AP id
1639 def block(%User{} = blocker, %{ap_id: ap_id}) do
1640 block(blocker, get_cached_by_ap_id(ap_id))
1643 def unblock(%User{} = blocker, %User{} = blocked) do
1644 remove_from_block(blocker, blocked)
1647 # helper to handle the block given only an actor's AP id
1648 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1649 unblock(blocker, get_cached_by_ap_id(ap_id))
1652 def endorse(%User{} = endorser, %User{} = target) do
1653 with max_endorsed_users <- Pleroma.Config.get([:instance, :max_endorsed_users], 0),
1655 User.endorsed_users_relation(endorser)
1656 |> Repo.aggregate(:count, :id) do
1658 endorsed_users >= max_endorsed_users ->
1659 {:error, "You have already pinned the maximum number of users"}
1661 not following?(endorser, target) ->
1662 {:error, "Could not endorse: You are not following #{target.nickname}"}
1665 UserRelationship.create_endorsement(endorser, target)
1670 def endorse(%User{} = endorser, %{ap_id: ap_id}) do
1671 with %User{} = endorsed <- get_cached_by_ap_id(ap_id) do
1672 endorse(endorser, endorsed)
1676 def unendorse(%User{} = unendorser, %User{} = target) do
1677 UserRelationship.delete_endorsement(unendorser, target)
1680 def unendorse(%User{} = unendorser, %{ap_id: ap_id}) do
1681 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1682 unendorse(unendorser, user)
1686 def mutes?(nil, _), do: false
1687 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1689 def mutes_user?(%User{} = user, %User{} = target) do
1690 UserRelationship.mute_exists?(user, target)
1693 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1694 def muted_notifications?(nil, _), do: false
1696 def muted_notifications?(%User{} = user, %User{} = target),
1697 do: UserRelationship.notification_mute_exists?(user, target)
1699 def blocks?(nil, _), do: false
1701 def blocks?(%User{} = user, %User{} = target) do
1702 blocks_user?(user, target) ||
1703 (blocks_domain?(user, target) and not User.following?(user, target))
1706 def blocks_user?(%User{} = user, %User{} = target) do
1707 UserRelationship.block_exists?(user, target)
1710 def blocks_user?(_, _), do: false
1712 def blocks_domain?(%User{} = user, %User{} = target) do
1713 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1714 %{host: host} = URI.parse(target.ap_id)
1715 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1718 def blocks_domain?(_, _), do: false
1720 def subscribed_to?(%User{} = user, %User{} = target) do
1721 # Note: the relationship is inverse: subscriber acts as relationship target
1722 UserRelationship.inverse_subscription_exists?(target, user)
1725 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1726 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1727 subscribed_to?(user, target)
1731 def endorses?(%User{} = user, %User{} = target) do
1732 UserRelationship.endorsement_exists?(user, target)
1736 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1737 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1739 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1740 def outgoing_relationships_ap_ids(_user, []), do: %{}
1742 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1744 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1745 when is_list(relationship_types) do
1748 |> assoc(:outgoing_relationships)
1749 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1750 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1751 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1752 |> group_by([user_rel, u], user_rel.relationship_type)
1754 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1759 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1763 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1765 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1767 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1769 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1770 when is_list(relationship_types) do
1772 |> assoc(:incoming_relationships)
1773 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1774 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1775 |> maybe_filter_on_ap_id(ap_ids)
1776 |> select([user_rel, u], u.ap_id)
1781 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1782 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1785 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1787 def set_activation_async(user, status \\ true) do
1788 BackgroundWorker.enqueue("user_activation", %{"user_id" => user.id, "status" => status})
1791 @spec set_activation([User.t()], boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
1792 def set_activation(users, status) when is_list(users) do
1793 Repo.transaction(fn ->
1794 for user <- users, do: set_activation(user, status)
1798 @spec set_activation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
1799 def set_activation(%User{} = user, status) do
1800 with {:ok, user} <- set_activation_status(user, status) do
1803 |> Enum.filter(& &1.local)
1804 |> Enum.each(&set_cache(update_following_count(&1)))
1806 # Only update local user counts, remote will be update during the next pull.
1809 |> Enum.filter(& &1.local)
1810 |> Enum.each(&do_unfollow(user, &1))
1816 def approve(users) when is_list(users) do
1817 Repo.transaction(fn ->
1818 Enum.map(users, fn user ->
1819 with {:ok, user} <- approve(user), do: user
1824 def approve(%User{is_approved: false} = user) do
1825 with chg <- change(user, is_approved: true),
1826 {:ok, user} <- update_and_set_cache(chg) do
1827 post_register_action(user)
1832 def approve(%User{} = user), do: {:ok, user}
1834 def confirm(users) when is_list(users) do
1835 Repo.transaction(fn ->
1836 Enum.map(users, fn user ->
1837 with {:ok, user} <- confirm(user), do: user
1842 def confirm(%User{is_confirmed: false} = user) do
1843 with chg <- confirmation_changeset(user, set_confirmation: true),
1844 {:ok, user} <- update_and_set_cache(chg) do
1845 post_register_action(user)
1850 def confirm(%User{} = user), do: {:ok, user}
1852 def set_suggestion(users, is_suggested) when is_list(users) do
1853 Repo.transaction(fn ->
1854 Enum.map(users, fn user ->
1855 with {:ok, user} <- set_suggestion(user, is_suggested), do: user
1860 def set_suggestion(%User{is_suggested: is_suggested} = user, is_suggested), do: {:ok, user}
1862 def set_suggestion(%User{} = user, is_suggested) when is_boolean(is_suggested) do
1864 |> change(is_suggested: is_suggested)
1865 |> update_and_set_cache()
1868 def update_notification_settings(%User{} = user, settings) do
1870 |> cast(%{notification_settings: settings}, [])
1871 |> cast_embed(:notification_settings)
1872 |> validate_required([:notification_settings])
1873 |> update_and_set_cache()
1876 @spec purge_user_changeset(User.t()) :: Changeset.t()
1877 def purge_user_changeset(user) do
1878 # "Right to be forgotten"
1879 # https://gdpr.eu/right-to-be-forgotten/
1888 last_refreshed_at: nil,
1889 last_digest_emailed_at: nil,
1896 password_reset_pending: false,
1897 registration_reason: nil,
1898 confirmation_token: nil,
1902 is_moderator: false,
1906 pleroma_settings_store: %{},
1909 is_discoverable: false,
1913 # nickname: preserved
1917 # Purge doesn't delete the user from the database.
1918 # It just nulls all its fields and deactivates it.
1919 # See `User.purge_user_changeset/1` above.
1920 defp purge(%User{} = user) do
1922 |> purge_user_changeset()
1923 |> update_and_set_cache()
1926 def delete(users) when is_list(users) do
1927 for user <- users, do: delete(user)
1930 def delete(%User{} = user) do
1931 # Purge the user immediately
1933 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1936 # *Actually* delete the user from the DB
1937 defp delete_from_db(%User{} = user) do
1938 invalidate_cache(user)
1942 # If the user never finalized their account, it's safe to delete them.
1943 defp maybe_delete_from_db(%User{local: true, is_confirmed: false} = user),
1944 do: delete_from_db(user)
1946 defp maybe_delete_from_db(%User{local: true, is_approved: false} = user),
1947 do: delete_from_db(user)
1949 defp maybe_delete_from_db(user), do: {:ok, user}
1951 def perform(:force_password_reset, user), do: force_password_reset(user)
1953 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1954 def perform(:delete, %User{} = user) do
1955 # Purge the user again, in case perform/2 is called directly
1958 # Remove all relationships
1961 |> Enum.each(fn follower ->
1962 ActivityPub.unfollow(follower, user)
1963 unfollow(follower, user)
1968 |> Enum.each(fn followed ->
1969 ActivityPub.unfollow(user, followed)
1970 unfollow(user, followed)
1973 delete_user_activities(user)
1974 delete_notifications_from_user_activities(user)
1975 delete_outgoing_pending_follow_requests(user)
1977 maybe_delete_from_db(user)
1980 def perform(:set_activation_async, user, status), do: set_activation(user, status)
1982 @spec external_users_query() :: Ecto.Query.t()
1983 def external_users_query do
1991 @spec external_users(keyword()) :: [User.t()]
1992 def external_users(opts \\ []) do
1994 external_users_query()
1995 |> select([u], struct(u, [:id, :ap_id]))
1999 do: where(query, [u], u.id > ^opts[:max_id]),
2004 do: limit(query, ^opts[:limit]),
2010 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
2012 |> join(:inner, [n], activity in assoc(n, :activity))
2013 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
2014 |> Repo.delete_all()
2017 def delete_user_activities(%User{ap_id: ap_id} = user) do
2019 |> Activity.Queries.by_actor()
2020 |> Repo.chunk_stream(50, :batches)
2021 |> Stream.each(fn activities ->
2022 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
2027 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
2028 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
2029 {:ok, delete_data, _} <- Builder.delete(user, object) do
2030 Pipeline.common_pipeline(delete_data, local: user.local)
2032 {:find_object, nil} ->
2033 # We have the create activity, but not the object, it was probably pruned.
2034 # Insert a tombstone and try again
2035 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
2036 {:ok, _tombstone} <- Object.create(tombstone_data) do
2037 delete_activity(activity, user)
2041 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
2042 Logger.error("Error: #{inspect(e)}")
2046 defp delete_activity(%{data: %{"type" => type}} = activity, user)
2047 when type in ["Like", "Announce"] do
2048 {:ok, undo, _} = Builder.undo(user, activity)
2049 Pipeline.common_pipeline(undo, local: user.local)
2052 defp delete_activity(_activity, _user), do: "Doing nothing"
2054 defp delete_outgoing_pending_follow_requests(user) do
2056 |> FollowingRelationship.outgoing_pending_follow_requests_query()
2057 |> Repo.delete_all()
2060 def html_filter_policy(%User{no_rich_text: true}) do
2061 Pleroma.HTML.Scrubber.TwitterText
2064 def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
2066 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
2068 def get_or_fetch_by_ap_id(ap_id) do
2069 cached_user = get_cached_by_ap_id(ap_id)
2071 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
2073 case {cached_user, maybe_fetched_user} do
2074 {_, {:ok, %User{} = user}} ->
2077 {%User{} = user, _} ->
2081 {:error, :not_found}
2086 Creates an internal service actor by URI if missing.
2087 Optionally takes nickname for addressing.
2089 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
2090 def get_or_create_service_actor_by_ap_id(uri, nickname) do
2092 case get_cached_by_ap_id(uri) do
2094 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
2095 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
2099 %User{invisible: false} = user ->
2109 @spec set_invisible(User.t()) :: {:ok, User.t()}
2110 defp set_invisible(user) do
2112 |> change(%{invisible: true})
2113 |> update_and_set_cache()
2116 @spec create_service_actor(String.t(), String.t()) ::
2117 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
2118 defp create_service_actor(uri, nickname) do
2124 follower_address: uri <> "/followers"
2127 |> put_private_key()
2128 |> unique_constraint(:nickname)
2133 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
2136 |> :public_key.pem_decode()
2138 |> :public_key.pem_entry_decode()
2143 def public_key(_), do: {:error, "key not found"}
2145 def get_public_key_for_ap_id(ap_id) do
2146 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
2147 {:ok, public_key} <- public_key(user) do
2154 def ap_enabled?(%User{local: true}), do: true
2155 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
2156 def ap_enabled?(_), do: false
2158 @doc "Gets or fetch a user by uri or nickname."
2159 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
2160 def get_or_fetch("http://" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
2161 def get_or_fetch("https://" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
2162 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
2164 # wait a period of time and return newest version of the User structs
2165 # this is because we have synchronous follow APIs and need to simulate them
2166 # with an async handshake
2167 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
2168 with %User{} = a <- get_cached_by_id(a.id),
2169 %User{} = b <- get_cached_by_id(b.id) do
2176 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
2177 with :ok <- :timer.sleep(timeout),
2178 %User{} = a <- get_cached_by_id(a.id),
2179 %User{} = b <- get_cached_by_id(b.id) do
2186 def parse_bio(bio) when is_binary(bio) and bio != "" do
2188 |> CommonUtils.format_input("text/plain", mentions_format: :full)
2192 def parse_bio(_), do: ""
2194 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
2195 # TODO: get profile URLs other than user.ap_id
2196 profile_urls = [user.ap_id]
2199 |> CommonUtils.format_input("text/plain",
2200 mentions_format: :full,
2201 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
2206 def parse_bio(_, _), do: ""
2208 def tag(user_identifiers, tags) when is_list(user_identifiers) do
2209 Repo.transaction(fn ->
2210 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
2214 def tag(nickname, tags) when is_binary(nickname),
2215 do: tag(get_by_nickname(nickname), tags)
2217 def tag(%User{} = user, tags),
2218 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
2220 def untag(user_identifiers, tags) when is_list(user_identifiers) do
2221 Repo.transaction(fn ->
2222 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
2226 def untag(nickname, tags) when is_binary(nickname),
2227 do: untag(get_by_nickname(nickname), tags)
2229 def untag(%User{} = user, tags),
2230 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
2232 defp update_tags(%User{} = user, new_tags) do
2233 {:ok, updated_user} =
2235 |> change(%{tags: new_tags})
2236 |> update_and_set_cache()
2241 defp normalize_tags(tags) do
2244 |> Enum.map(&String.downcase/1)
2247 defp local_nickname_regex do
2248 if Config.get([:instance, :extended_nickname_format]) do
2249 @extended_local_nickname_regex
2251 @strict_local_nickname_regex
2255 def local_nickname(nickname_or_mention) do
2258 |> String.split("@")
2262 def full_nickname(%User{} = user) do
2263 if String.contains?(user.nickname, "@") do
2266 %{host: host} = URI.parse(user.ap_id)
2267 user.nickname <> "@" <> host
2271 def full_nickname(nickname_or_mention),
2272 do: String.trim_leading(nickname_or_mention, "@")
2274 def error_user(ap_id) do
2278 nickname: "erroruser@example.com",
2279 inserted_at: NaiveDateTime.utc_now()
2283 @spec all_superusers() :: [User.t()]
2284 def all_superusers do
2285 User.Query.build(%{super_users: true, local: true, is_active: true})
2289 @spec all_users_with_privilege(atom()) :: [User.t()]
2290 def all_users_with_privilege(privilege) do
2291 User.Query.build(%{is_privileged: privilege}) |> Repo.all()
2294 def muting_reblogs?(%User{} = user, %User{} = target) do
2295 UserRelationship.reblog_mute_exists?(user, target)
2298 def showing_reblogs?(%User{} = user, %User{} = target) do
2299 not muting_reblogs?(user, target)
2303 The function returns a query to get users with no activity for given interval of days.
2304 Inactive users are those who didn't read any notification, or had any activity where
2305 the user is the activity's actor, during `inactivity_threshold` days.
2306 Deactivated users will not appear in this list.
2310 iex> Pleroma.User.list_inactive_users()
2313 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
2314 def list_inactive_users_query(inactivity_threshold \\ 7) do
2315 negative_inactivity_threshold = -inactivity_threshold
2316 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2317 # Subqueries are not supported in `where` clauses, join gets too complicated.
2318 has_read_notifications =
2319 from(n in Pleroma.Notification,
2320 where: n.seen == true,
2322 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
2325 |> Pleroma.Repo.all()
2327 from(u in Pleroma.User,
2328 left_join: a in Pleroma.Activity,
2329 on: u.ap_id == a.actor,
2330 where: not is_nil(u.nickname),
2331 where: u.is_active == ^true,
2332 where: u.id not in ^has_read_notifications,
2335 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
2336 is_nil(max(a.inserted_at))
2341 Enable or disable email notifications for user
2345 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
2346 Pleroma.User{email_notifications: %{"digest" => true}}
2348 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
2349 Pleroma.User{email_notifications: %{"digest" => false}}
2351 @spec switch_email_notifications(t(), String.t(), boolean()) ::
2352 {:ok, t()} | {:error, Ecto.Changeset.t()}
2353 def switch_email_notifications(user, type, status) do
2354 User.update_email_notifications(user, %{type => status})
2358 Set `last_digest_emailed_at` value for the user to current time
2360 @spec touch_last_digest_emailed_at(t()) :: t()
2361 def touch_last_digest_emailed_at(user) do
2362 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2364 {:ok, updated_user} =
2366 |> change(%{last_digest_emailed_at: now})
2367 |> update_and_set_cache()
2372 @spec set_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
2373 def set_confirmation(%User{} = user, bool) do
2375 |> confirmation_changeset(set_confirmation: bool)
2376 |> update_and_set_cache()
2379 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
2383 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
2384 # use instance-default
2385 config = Config.get([:assets, :mascots])
2386 default_mascot = Config.get([:assets, :default_mascot])
2387 mascot = Keyword.get(config, default_mascot)
2390 "id" => "default-mascot",
2391 "url" => mascot[:url],
2392 "preview_url" => mascot[:url],
2394 "mime_type" => mascot[:mime_type]
2399 def get_ap_ids_by_nicknames(nicknames) do
2401 where: u.nickname in ^nicknames,
2402 order_by: fragment("array_position(?, ?)", ^nicknames, u.nickname),
2408 defp put_password_hash(
2409 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2411 change(changeset, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
2414 defp put_password_hash(changeset), do: changeset
2416 def is_internal_user?(%User{nickname: nil}), do: true
2417 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2418 def is_internal_user?(_), do: false
2420 # A hack because user delete activities have a fake id for whatever reason
2421 # TODO: Get rid of this
2422 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2424 def get_delivered_users_by_object_id(object_id) do
2426 inner_join: delivery in assoc(u, :deliveries),
2427 where: delivery.object_id == ^object_id
2432 def change_email(user, email) do
2434 |> cast(%{email: email}, [:email])
2435 |> maybe_validate_required_email(false)
2436 |> unique_constraint(:email)
2437 |> validate_format(:email, @email_regex)
2438 |> update_and_set_cache()
2441 def alias_users(user) do
2443 |> Enum.map(&User.get_cached_by_ap_id/1)
2444 |> Enum.filter(fn user -> user != nil end)
2447 def add_alias(user, new_alias_user) do
2448 current_aliases = user.also_known_as || []
2449 new_alias_ap_id = new_alias_user.ap_id
2451 if new_alias_ap_id in current_aliases do
2455 |> cast(%{also_known_as: current_aliases ++ [new_alias_ap_id]}, [:also_known_as])
2456 |> update_and_set_cache()
2460 def delete_alias(user, alias_user) do
2461 current_aliases = user.also_known_as || []
2462 alias_ap_id = alias_user.ap_id
2464 if alias_ap_id in current_aliases do
2466 |> cast(%{also_known_as: current_aliases -- [alias_ap_id]}, [:also_known_as])
2467 |> update_and_set_cache()
2469 {:error, :no_such_alias}
2473 # Internal function; public one is `deactivate/2`
2474 defp set_activation_status(user, status) do
2476 |> cast(%{is_active: status}, [:is_active])
2477 |> update_and_set_cache()
2480 def update_banner(user, banner) do
2482 |> cast(%{banner: banner}, [:banner])
2483 |> update_and_set_cache()
2486 def update_background(user, background) do
2488 |> cast(%{background: background}, [:background])
2489 |> update_and_set_cache()
2492 def validate_fields(changeset, remote? \\ false) do
2493 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2494 limit = Config.get([:instance, limit_name], 0)
2497 |> validate_length(:fields, max: limit)
2498 |> validate_change(:fields, fn :fields, fields ->
2499 if Enum.all?(fields, &valid_field?/1) do
2507 defp valid_field?(%{"name" => name, "value" => value}) do
2508 name_limit = Config.get([:instance, :account_field_name_length], 255)
2509 value_limit = Config.get([:instance, :account_field_value_length], 255)
2511 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2512 String.length(value) <= value_limit
2515 defp valid_field?(_), do: false
2517 defp truncate_field(%{"name" => name, "value" => value}) do
2519 String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
2522 String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
2524 %{"name" => name, "value" => value}
2527 def admin_api_update(user, params) do
2534 |> update_and_set_cache()
2537 @doc "Signs user out of all applications"
2538 def global_sign_out(user) do
2539 OAuth.Authorization.delete_user_authorizations(user)
2540 OAuth.Token.delete_user_tokens(user)
2543 def mascot_update(user, url) do
2545 |> cast(%{mascot: url}, [:mascot])
2546 |> validate_required([:mascot])
2547 |> update_and_set_cache()
2550 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2551 def confirmation_changeset(user, set_confirmation: confirmed?) do
2556 confirmation_token: nil
2560 is_confirmed: false,
2561 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2565 cast(user, params, [:is_confirmed, :confirmation_token])
2568 @spec approval_changeset(User.t(), keyword()) :: Changeset.t()
2569 def approval_changeset(user, set_approval: approved?) do
2570 cast(user, %{is_approved: approved?}, [:is_approved])
2573 @spec add_pinned_object_id(User.t(), String.t()) :: {:ok, User.t()} | {:error, term()}
2574 def add_pinned_object_id(%User{} = user, object_id) do
2575 if !user.pinned_objects[object_id] do
2576 params = %{pinned_objects: Map.put(user.pinned_objects, object_id, NaiveDateTime.utc_now())}
2579 |> cast(params, [:pinned_objects])
2580 |> validate_change(:pinned_objects, fn :pinned_objects, pinned_objects ->
2581 max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
2583 if Enum.count(pinned_objects) <= max_pinned_statuses do
2586 [pinned_objects: "You have already pinned the maximum number of statuses"]
2592 |> update_and_set_cache()
2595 @spec remove_pinned_object_id(User.t(), String.t()) :: {:ok, t()} | {:error, term()}
2596 def remove_pinned_object_id(%User{} = user, object_id) do
2599 %{pinned_objects: Map.delete(user.pinned_objects, object_id)},
2602 |> update_and_set_cache()
2605 def update_email_notifications(user, settings) do
2606 email_notifications =
2607 user.email_notifications
2608 |> Map.merge(settings)
2609 |> Map.take(["digest"])
2611 params = %{email_notifications: email_notifications}
2612 fields = [:email_notifications]
2615 |> cast(params, fields)
2616 |> validate_required(fields)
2617 |> update_and_set_cache()
2620 defp set_domain_blocks(user, domain_blocks) do
2621 params = %{domain_blocks: domain_blocks}
2624 |> cast(params, [:domain_blocks])
2625 |> validate_required([:domain_blocks])
2626 |> update_and_set_cache()
2629 def block_domain(user, domain_blocked) do
2630 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2633 def unblock_domain(user, domain_blocked) do
2634 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2637 @spec add_to_block(User.t(), User.t()) ::
2638 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2639 defp add_to_block(%User{} = user, %User{} = blocked) do
2640 with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do
2641 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2646 @spec add_to_block(User.t(), User.t()) ::
2647 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2648 defp remove_from_block(%User{} = user, %User{} = blocked) do
2649 with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
2650 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2655 def set_invisible(user, invisible) do
2656 params = %{invisible: invisible}
2659 |> cast(params, [:invisible])
2660 |> validate_required([:invisible])
2661 |> update_and_set_cache()
2664 def sanitize_html(%User{} = user) do
2665 sanitize_html(user, nil)
2668 # User data that mastodon isn't filtering (treated as plaintext):
2671 def sanitize_html(%User{} = user, filter) do
2673 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2676 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2681 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2682 |> Map.put(:fields, fields)
2685 def get_host(%User{ap_id: ap_id} = _user) do
2686 URI.parse(ap_id).host
2689 def update_last_active_at(%__MODULE__{local: true} = user) do
2691 |> cast(%{last_active_at: NaiveDateTime.utc_now()}, [:last_active_at])
2692 |> update_and_set_cache()
2695 def active_user_count(days \\ 30) do
2696 active_after = Timex.shift(NaiveDateTime.utc_now(), days: -days)
2699 |> where([u], u.last_active_at >= ^active_after)
2700 |> where([u], u.local == true)
2701 |> Repo.aggregate(:count)
2704 def update_last_status_at(user) do
2706 |> where(id: ^user.id)
2707 |> update([u], set: [last_status_at: fragment("NOW()")])
2709 |> Repo.update_all([])
2711 {1, [user]} -> set_cache(user)
2716 def get_friends_birthdays_query(%User{} = user, day, month) do
2721 birthday_month: month