1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.User do
7 A user, local or remote
14 import Ecto, only: [assoc: 2]
17 alias Pleroma.Activity
19 alias Pleroma.Conversation.Participation
20 alias Pleroma.Delivery
21 alias Pleroma.EctoType.ActivityPub.ObjectValidators
23 alias Pleroma.FollowingRelationship
24 alias Pleroma.Formatter
26 alias Pleroma.User.HashtagFollow
30 alias Pleroma.Notification
32 alias Pleroma.Registration
35 alias Pleroma.UserRelationship
36 alias Pleroma.Web.ActivityPub.ActivityPub
37 alias Pleroma.Web.ActivityPub.Builder
38 alias Pleroma.Web.ActivityPub.Pipeline
39 alias Pleroma.Web.ActivityPub.Utils
40 alias Pleroma.Web.CommonAPI
41 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
42 alias Pleroma.Web.Endpoint
43 alias Pleroma.Web.OAuth
44 alias Pleroma.Web.RelMe
45 alias Pleroma.Workers.BackgroundWorker
49 @type t :: %__MODULE__{}
50 @type account_status ::
53 | :password_reset_pending
54 | :confirmation_pending
56 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
58 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
59 @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])?)*$/
61 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
62 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
64 # AP ID user relationships (blocks, mutes etc.)
65 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
66 @user_relationships_config [
68 blocker_blocks: :blocked_users,
69 blockee_blocks: :blocker_users
72 muter_mutes: :muted_users,
73 mutee_mutes: :muter_users
76 reblog_muter_mutes: :reblog_muted_users,
77 reblog_mutee_mutes: :reblog_muter_users
80 notification_muter_mutes: :notification_muted_users,
81 notification_mutee_mutes: :notification_muter_users
83 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
84 inverse_subscription: [
85 subscribee_subscriptions: :subscriber_users,
86 subscriber_subscriptions: :subscribee_users
90 @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
93 field(:bio, :string, default: "")
94 field(:raw_bio, :string)
95 field(:email, :string)
97 field(:nickname, :string)
98 field(:password_hash, :string)
99 field(:password, :string, virtual: true)
100 field(:password_confirmation, :string, virtual: true)
101 field(:keys, :string)
102 field(:public_key, :string)
103 field(:ap_id, :string)
104 field(:avatar, :map, default: %{})
105 field(:local, :boolean, default: true)
106 field(:follower_address, :string)
107 field(:following_address, :string)
108 field(:featured_address, :string)
109 field(:search_rank, :float, virtual: true)
110 field(:search_type, :integer, virtual: true)
111 field(:tags, {:array, :string}, default: [])
112 field(:last_refreshed_at, :naive_datetime_usec)
113 field(:last_digest_emailed_at, :naive_datetime)
114 field(:banner, :map, default: %{})
115 field(:background, :map, default: %{})
116 field(:note_count, :integer, default: 0)
117 field(:follower_count, :integer, default: 0)
118 field(:following_count, :integer, default: 0)
119 field(:is_locked, :boolean, default: false)
120 field(:is_confirmed, :boolean, default: true)
121 field(:password_reset_pending, :boolean, default: false)
122 field(:is_approved, :boolean, default: true)
123 field(:registration_reason, :string, default: nil)
124 field(:confirmation_token, :string, default: nil)
125 field(:default_scope, :string, default: "public")
126 field(:domain_blocks, {:array, :string}, default: [])
127 field(:is_active, :boolean, default: true)
128 field(:no_rich_text, :boolean, default: false)
129 field(:ap_enabled, :boolean, default: false)
130 field(:is_moderator, :boolean, default: false)
131 field(:is_admin, :boolean, default: false)
132 field(:show_role, :boolean, default: true)
133 field(:mastofe_settings, :map, default: nil)
134 field(:uri, ObjectValidators.Uri, default: nil)
135 field(:hide_followers_count, :boolean, default: false)
136 field(:hide_follows_count, :boolean, default: false)
137 field(:hide_followers, :boolean, default: false)
138 field(:hide_follows, :boolean, default: false)
139 field(:hide_favorites, :boolean, default: true)
140 field(:email_notifications, :map, default: %{"digest" => false})
141 field(:mascot, :map, default: nil)
142 field(:emoji, :map, default: %{})
143 field(:pleroma_settings_store, :map, default: %{})
144 field(:fields, {:array, :map}, default: [])
145 field(:raw_fields, {:array, :map}, default: [])
146 field(:is_discoverable, :boolean, default: false)
147 field(:invisible, :boolean, default: false)
148 field(:allow_following_move, :boolean, default: true)
149 field(:skip_thread_containment, :boolean, default: false)
150 field(:actor_type, :string, default: "Person")
151 field(:also_known_as, {:array, ObjectValidators.ObjectID}, default: [])
152 field(:inbox, :string)
153 field(:shared_inbox, :string)
154 field(:last_active_at, :naive_datetime)
155 field(:disclose_client, :boolean, default: true)
156 field(:pinned_objects, :map, default: %{})
157 field(:is_suggested, :boolean, default: false)
158 field(:last_status_at, :naive_datetime)
159 field(:language, :string)
160 field(:status_ttl_days, :integer, default: nil)
163 :notification_settings,
164 Pleroma.User.NotificationSetting,
168 has_many(:notifications, Notification)
169 has_many(:registrations, Registration)
170 has_many(:deliveries, Delivery)
172 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
173 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
175 has_many(:frontend_profiles, Pleroma.Akkoma.FrontendSettingsProfile)
177 many_to_many(:followed_hashtags, Hashtag,
179 on_delete: :delete_all,
180 join_through: HashtagFollow
183 for {relationship_type,
185 {outgoing_relation, outgoing_relation_target},
186 {incoming_relation, incoming_relation_source}
187 ]} <- @user_relationships_config do
188 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
189 # :notification_muter_mutes, :subscribee_subscriptions
190 has_many(outgoing_relation, UserRelationship,
191 foreign_key: :source_id,
192 where: [relationship_type: relationship_type]
195 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
196 # :notification_mutee_mutes, :subscriber_subscriptions
197 has_many(incoming_relation, UserRelationship,
198 foreign_key: :target_id,
199 where: [relationship_type: relationship_type]
202 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
203 # :notification_muted_users, :subscriber_users
204 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
206 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
207 # :notification_muter_users, :subscribee_users
208 has_many(incoming_relation_source, through: [incoming_relation, :source])
212 :multi_factor_authentication_settings,
220 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
221 @user_relationships_config do
222 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
223 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
224 # `def subscriber_users/2`
225 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
226 target_users_query = assoc(user, unquote(outgoing_relation_target))
228 if restrict_deactivated? do
230 |> User.Query.build(%{deactivated: false})
236 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
237 # `def notification_muted_users/2`, `def subscriber_users/2`
238 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
240 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
242 restrict_deactivated?
247 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
248 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
249 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
251 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
253 restrict_deactivated?
255 |> select([u], u.ap_id)
260 def cached_blocked_users_ap_ids(user) do
261 @cachex.fetch!(:user_cache, "blocked_users_ap_ids:#{user.ap_id}", fn _ ->
262 blocked_users_ap_ids(user)
266 def cached_muted_users_ap_ids(user) do
267 @cachex.fetch!(:user_cache, "muted_users_ap_ids:#{user.ap_id}", fn _ ->
268 muted_users_ap_ids(user)
272 defdelegate following_count(user), to: FollowingRelationship
273 defdelegate following(user), to: FollowingRelationship
274 defdelegate following?(follower, followed), to: FollowingRelationship
275 defdelegate following_ap_ids(user), to: FollowingRelationship
276 defdelegate get_follow_requests(user), to: FollowingRelationship
277 defdelegate search(query, opts \\ []), to: User.Search
280 Dumps Flake Id to SQL-compatible format (16-byte UUID).
281 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
283 def binary_id(source_id) when is_binary(source_id) do
284 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
291 def binary_id(source_ids) when is_list(source_ids) do
292 Enum.map(source_ids, &binary_id/1)
295 def binary_id(%User{} = user), do: binary_id(user.id)
297 @doc "Returns status account"
298 @spec account_status(User.t()) :: account_status()
299 def account_status(%User{is_active: false}), do: :deactivated
300 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
301 def account_status(%User{local: true, is_approved: false}), do: :approval_pending
302 def account_status(%User{local: true, is_confirmed: false}), do: :confirmation_pending
303 def account_status(%User{}), do: :active
305 @spec visible_for(User.t(), User.t() | nil) ::
308 | :restricted_unauthenticated
310 | :confirmation_pending
311 def visible_for(user, for_user \\ nil)
313 def visible_for(%User{invisible: true}, _), do: :invisible
315 def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
317 def visible_for(%User{} = user, nil) do
318 if restrict_unauthenticated?(user) do
319 :restrict_unauthenticated
321 visible_account_status(user)
325 def visible_for(%User{} = user, for_user) do
326 if superuser?(for_user) do
329 visible_account_status(user)
333 def visible_for(_, _), do: :invisible
335 defp restrict_unauthenticated?(%User{local: true}) do
336 Config.restrict_unauthenticated_access?(:profiles, :local)
339 defp restrict_unauthenticated?(%User{local: _}) do
340 Config.restrict_unauthenticated_access?(:profiles, :remote)
343 defp visible_account_status(user) do
344 status = account_status(user)
346 if status in [:active, :password_reset_pending] do
353 @spec superuser?(User.t()) :: boolean()
354 def superuser?(%User{local: true, is_admin: true}), do: true
355 def superuser?(%User{local: true, is_moderator: true}), do: true
356 def superuser?(_), do: false
358 @spec invisible?(User.t()) :: boolean()
359 def invisible?(%User{invisible: true}), do: true
360 def invisible?(_), do: false
362 def avatar_url(user, options \\ []) do
364 %{"url" => [%{"href" => href} | _]} ->
368 unless options[:no_default] do
369 Config.get([:assets, :default_user_avatar], "#{Endpoint.url()}/images/avi.png")
374 def banner_url(user, options \\ []) do
376 %{"url" => [%{"href" => href} | _]} -> href
377 _ -> !options[:no_default] && "#{Endpoint.url()}/images/banner.png"
381 # Should probably be renamed or removed
382 @spec ap_id(User.t()) :: String.t()
383 def ap_id(%User{nickname: nickname}), do: "#{Endpoint.url()}/users/#{nickname}"
385 @spec ap_followers(User.t()) :: String.t()
386 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
387 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
389 @spec ap_following(User.t()) :: String.t()
390 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
391 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
393 @spec ap_featured_collection(User.t()) :: String.t()
394 def ap_featured_collection(%User{featured_address: fa}) when is_binary(fa), do: fa
396 def ap_featured_collection(%User{} = user), do: "#{ap_id(user)}/collections/featured"
398 defp truncate_fields_param(params) do
399 if Map.has_key?(params, :fields) do
400 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
406 defp truncate_if_exists(params, key, max_length) do
407 if Map.has_key?(params, key) and is_binary(params[key]) do
408 {value, _chopped} = String.split_at(params[key], max_length)
409 Map.put(params, key, value)
415 defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
417 defp fix_follower_address(%{nickname: nickname} = params),
418 do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
420 defp fix_follower_address(params), do: params
422 def remote_user_changeset(struct \\ %User{local: false}, params) do
423 bio_limit = Config.get([:instance, :user_bio_length], 5000)
424 name_limit = Config.get([:instance, :user_name_length], 100)
427 case params[:name] do
428 name when is_binary(name) and byte_size(name) > 0 -> name
429 _ -> params[:nickname]
434 |> Map.put(:name, name)
435 |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
436 |> truncate_if_exists(:name, name_limit)
437 |> truncate_if_exists(:bio, bio_limit)
438 |> truncate_fields_param()
439 |> fix_follower_address()
463 :hide_followers_count,
475 |> cast(params, [:name], empty_values: [])
476 |> validate_required([:ap_id])
477 |> validate_required([:name], trim: false)
478 |> unique_constraint(:nickname)
479 |> validate_format(:nickname, @email_regex)
480 |> validate_length(:bio, max: bio_limit)
481 |> validate_length(:name, max: name_limit)
482 |> validate_fields(true, struct)
483 |> validate_non_local()
486 defp validate_non_local(cng) do
487 local? = get_field(cng, :local)
491 |> add_error(:local, "User is local, can't update with this changeset.")
497 def update_changeset(struct, params \\ %{}) do
498 bio_limit = Config.get([:instance, :user_bio_length], 5000)
499 name_limit = Config.get([:instance, :user_name_length], 100)
519 :hide_followers_count,
522 :allow_following_move,
526 :skip_thread_containment,
529 :pleroma_settings_store,
536 |> unique_constraint(:nickname)
537 |> validate_format(:nickname, local_nickname_regex())
538 |> validate_length(:bio, max: bio_limit)
539 |> validate_length(:name, min: 1, max: name_limit)
540 |> validate_inclusion(:actor_type, ["Person", "Service"])
541 |> validate_number(:status_ttl_days, greater_than: 0)
544 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
545 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
546 |> put_change_if_present(:banner, &put_upload(&1, :banner))
547 |> put_change_if_present(:background, &put_upload(&1, :background))
548 |> put_change_if_present(
549 :pleroma_settings_store,
550 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
552 |> validate_fields(false, struct)
555 defp put_fields(changeset) do
556 # These fields are inconsistent in tests when it comes to binary/atom keys
557 if raw_fields = get_change(changeset, :raw_fields) do
561 %{name: name, value: value} ->
562 %{"name" => name, "value" => value}
564 %{"name" => _} = field ->
567 |> Enum.filter(fn %{"name" => n} -> n != "" end)
571 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
574 |> put_change(:raw_fields, raw_fields)
575 |> put_change(:fields, fields)
581 defp parse_fields(value) do
583 |> Formatter.linkify(mentions_format: :full)
587 defp put_emoji(changeset) do
588 emojified_fields = [:bio, :name, :raw_fields]
590 if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
591 bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
592 name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
594 emoji = Map.merge(bio, name)
598 |> get_field(:raw_fields)
599 |> Enum.reduce(emoji, fn x, acc ->
600 Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
603 put_change(changeset, :emoji, emoji)
609 defp put_change_if_present(changeset, map_field, value_function) do
610 with {:ok, value} <- fetch_change(changeset, map_field),
611 {:ok, new_value} <- value_function.(value) do
612 put_change(changeset, map_field, new_value)
614 {:error, :file_too_large} ->
615 Ecto.Changeset.validate_change(changeset, map_field, fn map_field, _value ->
616 [{map_field, "file is too large"}]
624 defp put_upload(value, type) do
625 with %Plug.Upload{} <- value,
626 {:ok, object} <- ActivityPub.upload(value, type: type) do
631 def update_as_admin_changeset(struct, params) do
633 |> update_changeset(params)
634 |> cast(params, [:email])
635 |> delete_change(:also_known_as)
636 |> unique_constraint(:email)
637 |> validate_format(:email, @email_regex)
638 |> validate_inclusion(:actor_type, ["Person", "Service"])
641 @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
642 def update_as_admin(user, params) do
643 params = Map.put(params, "password_confirmation", params["password"])
644 changeset = update_as_admin_changeset(user, params)
646 if params["password"] do
647 reset_password(user, changeset, params)
649 User.update_and_set_cache(changeset)
653 def password_update_changeset(struct, params) do
655 |> cast(params, [:password, :password_confirmation])
656 |> validate_required([:password, :password_confirmation])
657 |> validate_confirmation(:password)
658 |> put_password_hash()
659 |> put_change(:password_reset_pending, false)
662 @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
663 def reset_password(%User{} = user, params) do
664 reset_password(user, user, params)
667 def reset_password(%User{id: user_id} = user, struct, params) do
670 |> Multi.update(:user, password_update_changeset(struct, params))
671 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
672 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
674 case Repo.transaction(multi) do
675 {:ok, %{user: user} = _} -> set_cache(user)
676 {:error, _, changeset, _} -> {:error, changeset}
680 def update_password_reset_pending(user, value) do
683 |> put_change(:password_reset_pending, value)
684 |> update_and_set_cache()
687 def force_password_reset_async(user) do
688 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
691 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
692 def force_password_reset(user), do: update_password_reset_pending(user, true)
694 # Used to auto-register LDAP accounts which won't have a password hash stored locally
695 def register_changeset_ldap(struct, params = %{password: password})
696 when is_nil(password) do
698 if Map.has_key?(params, :email) do
699 Map.put_new(params, :email, params[:email])
710 |> validate_required([:name, :nickname])
711 |> unique_constraint(:nickname)
712 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
713 |> validate_format(:nickname, local_nickname_regex())
715 |> unique_constraint(:ap_id)
716 |> put_following_and_follower_and_featured_address()
720 @spec register_changeset(User.t(), map(), keyword()) :: Changeset.t()
721 def register_changeset(%User{} = struct, params \\ %{}, opts \\ []) do
722 bio_limit = Config.get([:instance, :user_bio_length], 5000)
723 name_limit = Config.get([:instance, :user_name_length], 100)
724 reason_limit = Config.get([:instance, :registration_reason_length], 500)
727 if is_nil(opts[:confirmed]) do
728 !Config.get([:instance, :account_activation_required])
734 if is_nil(opts[:approved]) do
735 !Config.get([:instance, :account_approval_required])
741 |> confirmation_changeset(set_confirmation: confirmed?)
742 |> approval_changeset(set_approval: approved?)
750 :password_confirmation,
752 :registration_reason,
755 |> validate_required([:name, :nickname, :password, :password_confirmation])
756 |> validate_confirmation(:password)
757 |> unique_constraint(:email)
758 |> validate_format(:email, @email_regex)
759 |> validate_change(:email, fn :email, email ->
761 Config.get([User, :email_blacklist])
762 |> Enum.all?(fn blacklisted_domain ->
763 !String.ends_with?(email, ["@" <> blacklisted_domain, "." <> blacklisted_domain])
766 if valid?, do: [], else: [email: "Invalid email"]
768 |> unique_constraint(:nickname)
769 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
770 |> validate_format(:nickname, local_nickname_regex())
771 |> validate_length(:bio, max: bio_limit)
772 |> validate_length(:name, min: 1, max: name_limit)
773 |> validate_length(:registration_reason, max: reason_limit)
774 |> maybe_validate_required_email(opts[:external])
777 |> unique_constraint(:ap_id)
778 |> put_following_and_follower_and_featured_address()
782 def maybe_validate_required_email(changeset, true), do: changeset
784 def maybe_validate_required_email(changeset, _) do
785 if Config.get([:instance, :account_activation_required]) do
786 validate_required(changeset, [:email])
792 def put_ap_id(changeset) do
793 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
794 put_change(changeset, :ap_id, ap_id)
797 def put_following_and_follower_and_featured_address(changeset) do
798 user = %User{nickname: get_field(changeset, :nickname)}
799 followers = ap_followers(user)
800 following = ap_following(user)
801 featured = ap_featured_collection(user)
804 |> put_change(:follower_address, followers)
805 |> put_change(:following_address, following)
806 |> put_change(:featured_address, featured)
809 defp put_private_key(changeset) do
810 {:ok, pem} = Keys.generate_rsa_pem()
811 put_change(changeset, :keys, pem)
814 defp autofollow_users(user) do
815 candidates = Config.get([:instance, :autofollowed_nicknames])
818 User.Query.build(%{nickname: candidates, local: true, is_active: true})
821 follow_all(user, autofollowed_users)
824 defp autofollowing_users(user) do
825 candidates = Config.get([:instance, :autofollowing_nicknames])
827 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
829 |> Enum.each(&follow(&1, user, :follow_accept))
834 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
835 @spec register(Changeset.t()) :: {:ok, User.t()} | {:error, any} | nil
836 def register(%Ecto.Changeset{} = changeset) do
837 with {:ok, user} <- Repo.insert(changeset) do
838 post_register_action(user)
842 @spec post_register_action(User.t()) :: {:error, any} | {:ok, User.t()}
843 def post_register_action(%User{is_confirmed: false} = user) do
844 with {:ok, _} <- maybe_send_confirmation_email(user) do
849 def post_register_action(%User{is_approved: false} = user) do
850 with {:ok, _} <- send_user_approval_email(user),
851 {:ok, _} <- send_admin_approval_emails(user) do
856 def post_register_action(%User{is_approved: true, is_confirmed: true} = user) do
857 with {:ok, user} <- autofollow_users(user),
858 {:ok, _} <- autofollowing_users(user),
859 {:ok, user} <- set_cache(user),
860 {:ok, _} <- maybe_send_registration_email(user),
861 {:ok, _} <- maybe_send_welcome_email(user),
862 {:ok, _} <- maybe_send_welcome_message(user) do
867 defp send_user_approval_email(user) do
869 |> Pleroma.Emails.UserEmail.approval_pending_email()
870 |> Pleroma.Emails.Mailer.deliver_async()
875 defp send_admin_approval_emails(user) do
877 |> Enum.filter(fn user -> not is_nil(user.email) end)
878 |> Enum.each(fn superuser ->
880 |> Pleroma.Emails.AdminEmail.new_unapproved_registration(user)
881 |> Pleroma.Emails.Mailer.deliver_async()
887 defp maybe_send_welcome_message(user) do
888 if User.WelcomeMessage.enabled?() do
889 User.WelcomeMessage.post_message(user)
896 defp maybe_send_welcome_email(%User{email: email} = user) when is_binary(email) do
897 if User.WelcomeEmail.enabled?() do
898 User.WelcomeEmail.send_email(user)
905 defp maybe_send_welcome_email(_), do: {:ok, :noop}
907 @spec maybe_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
908 def maybe_send_confirmation_email(%User{is_confirmed: false, email: email} = user)
909 when is_binary(email) do
910 if Config.get([:instance, :account_activation_required]) do
911 send_confirmation_email(user)
918 def maybe_send_confirmation_email(_), do: {:ok, :noop}
920 @spec send_confirmation_email(Uset.t()) :: User.t()
921 def send_confirmation_email(%User{} = user) do
923 |> Pleroma.Emails.UserEmail.account_confirmation_email()
924 |> Pleroma.Emails.Mailer.deliver_async()
929 @spec maybe_send_registration_email(User.t()) :: {:ok, :enqueued | :noop}
930 defp maybe_send_registration_email(%User{email: email} = user) when is_binary(email) do
931 with false <- User.WelcomeEmail.enabled?(),
932 false <- Config.get([:instance, :account_activation_required], false),
933 false <- Config.get([:instance, :account_approval_required], false) do
935 |> Pleroma.Emails.UserEmail.successful_registration_email()
936 |> Pleroma.Emails.Mailer.deliver_async()
945 defp maybe_send_registration_email(_), do: {:ok, :noop}
947 def needs_update?(%User{local: true}), do: false
949 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
951 def needs_update?(%User{local: false} = user) do
952 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
955 def needs_update?(_), do: true
957 @spec maybe_direct_follow(User.t(), User.t()) ::
958 {:ok, User.t(), User.t()} | {:error, String.t()}
960 # "Locked" (self-locked) users demand explicit authorization of follow requests
961 def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do
962 follow(follower, followed, :follow_pending)
965 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
966 follow(follower, followed)
969 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
970 if not ap_enabled?(followed) do
971 follow(follower, followed)
973 {:ok, follower, followed}
977 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
978 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
979 def follow_all(follower, followeds) do
981 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
982 |> Enum.each(&follow(follower, &1, :follow_accept))
987 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
988 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
991 not followed.is_active ->
992 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
994 deny_follow_blocked and blocks?(followed, follower) ->
995 {:error, "Could not follow user: #{followed.nickname} blocked you."}
998 FollowingRelationship.follow(follower, followed, state)
1002 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
1003 {:error, "Not subscribed!"}
1006 @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
1007 def unfollow(%User{} = follower, %User{} = followed) do
1008 case do_unfollow(follower, followed) do
1009 {:ok, follower, followed} ->
1010 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
1017 @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
1018 defp do_unfollow(%User{} = follower, %User{} = followed) do
1019 case get_follow_state(follower, followed) do
1020 state when state in [:follow_pending, :follow_accept] ->
1021 FollowingRelationship.unfollow(follower, followed)
1024 {:error, "Not subscribed!"}
1028 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
1029 def get_follow_state(%User{} = follower, %User{} = following) do
1030 following_relationship = FollowingRelationship.get(follower, following)
1031 get_follow_state(follower, following, following_relationship)
1034 def get_follow_state(
1036 %User{} = following,
1037 following_relationship
1039 case {following_relationship, following.local} do
1041 case Utils.fetch_latest_follow(follower, following) do
1042 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
1043 FollowingRelationship.state_to_enum(state)
1049 {%{state: state}, _} ->
1057 def locked?(%User{} = user) do
1058 user.is_locked || false
1061 def get_by_id(id) do
1062 Repo.get_by(User, id: id)
1065 def get_by_ap_id(ap_id) do
1066 Repo.get_by(User, ap_id: ap_id)
1069 def get_all_by_ap_id(ap_ids) do
1070 from(u in __MODULE__,
1071 where: u.ap_id in ^ap_ids
1076 def get_all_by_ids(ids) do
1077 from(u in __MODULE__, where: u.id in ^ids)
1081 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
1082 # of the ap_id and the domain and tries to get that user
1083 def get_by_guessed_nickname(ap_id) do
1084 domain = URI.parse(ap_id).host
1085 name = List.last(String.split(ap_id, "/"))
1086 nickname = "#{name}@#{domain}"
1088 get_cached_by_nickname(nickname)
1095 ) :: {:ok, User.t()} | {:error, any}
1096 def set_cache({:ok, user}), do: set_cache(user)
1097 def set_cache({:error, err}), do: {:error, err}
1099 def set_cache(%User{} = user) do
1100 @cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1101 @cachex.put(:user_cache, "nickname:#{user.nickname}", user)
1102 @cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
1106 @spec update_and_set_cache(User.t(), map()) :: {:ok, User.t()} | {:error, any}
1107 def update_and_set_cache(struct, params) do
1109 |> update_changeset(params)
1110 |> update_and_set_cache()
1113 @spec update_and_set_cache(Changeset.t()) :: {:ok, User.t()} | {:error, any}
1114 def update_and_set_cache(%{data: %Pleroma.User{} = user} = changeset) do
1115 was_superuser_before_update = User.superuser?(user)
1117 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
1120 |> maybe_remove_report_notifications(was_superuser_before_update)
1123 defp maybe_remove_report_notifications({:ok, %Pleroma.User{} = user} = result, true) do
1124 if not User.superuser?(user),
1125 do: user |> Notification.destroy_multiple_from_types(["pleroma:report"])
1130 defp maybe_remove_report_notifications(result, _) do
1134 def get_user_friends_ap_ids(user) do
1135 from(u in User.get_friends_query(user), select: u.ap_id)
1139 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
1140 def get_cached_user_friends_ap_ids(user) do
1141 @cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
1142 get_user_friends_ap_ids(user)
1146 def invalidate_cache(user) do
1147 @cachex.del(:user_cache, "ap_id:#{user.ap_id}")
1148 @cachex.del(:user_cache, "nickname:#{user.nickname}")
1149 @cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
1150 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
1151 @cachex.del(:user_cache, "muted_users_ap_ids:#{user.ap_id}")
1154 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
1155 def get_cached_by_ap_id(ap_id) do
1156 key = "ap_id:#{ap_id}"
1158 with {:ok, nil} <- @cachex.get(:user_cache, key),
1159 user when not is_nil(user) <- get_by_ap_id(ap_id),
1160 {:ok, true} <- @cachex.put(:user_cache, key, user) do
1168 @spec get_cached_by_id(String.t()) :: nil | Pleroma.User.t()
1169 def get_cached_by_id(id) do
1173 @cachex.fetch!(:user_cache, key, fn _ ->
1174 user = get_by_id(id)
1177 @cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1178 {:commit, user.ap_id}
1184 get_cached_by_ap_id(ap_id)
1187 def get_cached_by_nickname(nickname) do
1188 key = "nickname:#{nickname}"
1190 @cachex.fetch!(:user_cache, key, fn _ ->
1191 case get_or_fetch_by_nickname(nickname) do
1192 {:ok, user} -> {:commit, user}
1193 {:error, _error} -> {:ignore, nil}
1198 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
1199 restrict_to_local = Config.get([:instance, :limit_to_local_content])
1202 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
1203 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
1205 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
1206 get_cached_by_nickname(nickname_or_id)
1208 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
1209 get_cached_by_nickname(nickname_or_id)
1216 @spec get_by_nickname(String.t()) :: User.t() | nil
1217 def get_by_nickname(nickname) do
1218 Repo.get_by(User, nickname: nickname) ||
1219 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
1220 Repo.get_by(User, nickname: local_nickname(nickname))
1224 def get_by_email(email), do: Repo.get_by(User, email: email)
1226 def get_by_nickname_or_email(nickname_or_email) do
1227 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
1230 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
1232 def get_or_fetch_by_nickname(nickname) do
1233 with %User{} = user <- get_by_nickname(nickname) do
1237 with [_nick, _domain] <- String.split(nickname, "@"),
1238 {:ok, user} <- fetch_by_nickname(nickname) do
1241 _e -> {:error, "not found " <> nickname}
1246 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1247 def get_followers_query(%User{} = user, nil) do
1248 User.Query.build(%{followers: user, is_active: true})
1251 def get_followers_query(%User{} = user, page) do
1253 |> get_followers_query(nil)
1254 |> User.Query.paginate(page, 20)
1257 @spec get_followers_query(User.t()) :: Ecto.Query.t()
1258 def get_followers_query(%User{} = user), do: get_followers_query(user, nil)
1260 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1261 def get_followers(%User{} = user, page \\ nil) do
1263 |> get_followers_query(page)
1267 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1268 def get_external_followers(%User{} = user, page \\ nil) do
1270 |> get_followers_query(page)
1271 |> User.Query.build(%{external: true})
1275 def get_followers_ids(%User{} = user, page \\ nil) do
1277 |> get_followers_query(page)
1278 |> select([u], u.id)
1282 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1283 def get_friends_query(%User{} = user, nil) do
1284 User.Query.build(%{friends: user, deactivated: false})
1287 def get_friends_query(%User{} = user, page) do
1289 |> get_friends_query(nil)
1290 |> User.Query.paginate(page, 20)
1293 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1294 def get_friends_query(%User{} = user), do: get_friends_query(user, nil)
1296 def get_friends(%User{} = user, page \\ nil) do
1298 |> get_friends_query(page)
1302 def get_friends_ap_ids(%User{} = user) do
1304 |> get_friends_query(nil)
1305 |> select([u], u.ap_id)
1309 def get_friends_ids(%User{} = user, page \\ nil) do
1311 |> get_friends_query(page)
1312 |> select([u], u.id)
1316 def increase_note_count(%User{} = user) do
1318 |> where(id: ^user.id)
1319 |> update([u], inc: [note_count: 1])
1321 |> Repo.update_all([])
1323 {1, [user]} -> set_cache(user)
1328 def decrease_note_count(%User{} = user) do
1330 |> where(id: ^user.id)
1333 note_count: fragment("greatest(0, note_count - 1)")
1337 |> Repo.update_all([])
1339 {1, [user]} -> set_cache(user)
1344 def update_note_count(%User{} = user, note_count \\ nil) do
1349 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1355 |> cast(%{note_count: note_count}, [:note_count])
1356 |> update_and_set_cache()
1359 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1360 def maybe_fetch_follow_information(user) do
1361 with {:ok, user} <- fetch_follow_information(user) do
1365 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1371 def fetch_follow_information(user) do
1372 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1374 |> follow_information_changeset(info)
1375 |> update_and_set_cache()
1379 defp follow_information_changeset(user, params) do
1386 :hide_followers_count,
1391 @spec update_follower_count(User.t()) :: {:ok, User.t()}
1392 def update_follower_count(%User{} = user) do
1393 if user.local or !Config.get([:instance, :external_user_synchronization]) do
1394 follower_count = FollowingRelationship.follower_count(user)
1397 |> follow_information_changeset(%{follower_count: follower_count})
1398 |> update_and_set_cache
1400 {:ok, maybe_fetch_follow_information(user)}
1404 @spec update_following_count(User.t()) :: {:ok, User.t()}
1405 def update_following_count(%User{local: false} = user) do
1406 if Config.get([:instance, :external_user_synchronization]) do
1407 {:ok, maybe_fetch_follow_information(user)}
1413 def update_following_count(%User{local: true} = user) do
1414 following_count = FollowingRelationship.following_count(user)
1417 |> follow_information_changeset(%{following_count: following_count})
1418 |> update_and_set_cache()
1421 @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
1422 def get_users_from_set(ap_ids, opts \\ []) do
1423 local_only = Keyword.get(opts, :local_only, true)
1424 criteria = %{ap_id: ap_ids, is_active: true}
1425 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1427 User.Query.build(criteria)
1431 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1432 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1435 query = User.Query.build(%{recipients_from_activity: to, local: true, is_active: true})
1441 @spec mute(User.t(), User.t(), map()) ::
1442 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1443 def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do
1444 notifications? = Map.get(params, :notifications, true)
1445 expires_in = Map.get(params, :expires_in, 0)
1447 with {:ok, user_mute} <- UserRelationship.create_mute(muter, mutee),
1448 {:ok, user_notification_mute} <-
1449 (notifications? && UserRelationship.create_notification_mute(muter, mutee)) ||
1451 if expires_in > 0 do
1452 Pleroma.Workers.MuteExpireWorker.enqueue(
1454 %{"muter_id" => muter.id, "mutee_id" => mutee.id},
1455 schedule_in: expires_in
1459 @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
1461 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
1465 def unmute(%User{} = muter, %User{} = mutee) do
1466 with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee),
1467 {:ok, user_notification_mute} <-
1468 UserRelationship.delete_notification_mute(muter, mutee) do
1469 @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
1470 {:ok, [user_mute, user_notification_mute]}
1474 def unmute(muter_id, mutee_id) do
1475 with {:muter, %User{} = muter} <- {:muter, User.get_by_id(muter_id)},
1476 {:mutee, %User{} = mutee} <- {:mutee, User.get_by_id(mutee_id)} do
1477 unmute(muter, mutee)
1479 {who, result} = error ->
1481 "User.unmute/2 failed. #{who}: #{result}, muter_id: #{muter_id}, mutee_id: #{mutee_id}"
1488 def subscribe(%User{} = subscriber, %User{} = target) do
1489 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
1491 if blocks?(target, subscriber) and deny_follow_blocked do
1492 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1494 # Note: the relationship is inverse: subscriber acts as relationship target
1495 UserRelationship.create_inverse_subscription(target, subscriber)
1499 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1500 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1501 subscribe(subscriber, subscribee)
1505 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1506 # Note: the relationship is inverse: subscriber acts as relationship target
1507 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1510 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1511 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1512 unsubscribe(unsubscriber, user)
1516 def block(%User{} = blocker, %User{} = blocked) do
1517 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1519 if following?(blocker, blocked) do
1520 {:ok, blocker, _} = unfollow(blocker, blocked)
1526 # clear any requested follows from both sides as well
1528 case CommonAPI.reject_follow_request(blocked, blocker) do
1529 {:ok, %User{} = updated_blocked} -> updated_blocked
1534 case CommonAPI.reject_follow_request(blocker, blocked) do
1535 {:ok, %User{} = updated_blocker} -> updated_blocker
1539 unsubscribe(blocked, blocker)
1541 unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
1542 if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
1544 {:ok, blocker} = update_follower_count(blocker)
1545 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1546 add_to_block(blocker, blocked)
1549 # helper to handle the block given only an actor's AP id
1550 def block(%User{} = blocker, %{ap_id: ap_id}) do
1551 block(blocker, get_cached_by_ap_id(ap_id))
1554 def unblock(%User{} = blocker, %User{} = blocked) do
1555 remove_from_block(blocker, blocked)
1558 # helper to handle the block given only an actor's AP id
1559 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1560 unblock(blocker, get_cached_by_ap_id(ap_id))
1563 def mutes?(nil, _), do: false
1564 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1566 def mutes_user?(%User{} = user, %User{} = target) do
1567 UserRelationship.mute_exists?(user, target)
1570 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1571 def muted_notifications?(nil, _), do: false
1573 def muted_notifications?(%User{} = user, %User{} = target),
1574 do: UserRelationship.notification_mute_exists?(user, target)
1576 def blocks?(nil, _), do: false
1578 def blocks?(%User{} = user, %User{} = target) do
1579 blocks_user?(user, target) ||
1580 (blocks_domain?(user, target) and not User.following?(user, target))
1583 def blocks_user?(%User{} = user, %User{} = target) do
1584 UserRelationship.block_exists?(user, target)
1587 def blocks_user?(_, _), do: false
1589 def blocks_domain?(%User{} = user, %User{} = target) do
1590 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1591 %{host: host} = URI.parse(target.ap_id)
1592 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1595 def blocks_domain?(_, _), do: false
1597 def subscribed_to?(%User{} = user, %User{} = target) do
1598 # Note: the relationship is inverse: subscriber acts as relationship target
1599 UserRelationship.inverse_subscription_exists?(target, user)
1602 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1603 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1604 subscribed_to?(user, target)
1609 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1610 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1612 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1613 def outgoing_relationships_ap_ids(_user, []), do: %{}
1615 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1617 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1618 when is_list(relationship_types) do
1621 |> assoc(:outgoing_relationships)
1622 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1623 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1624 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1625 |> group_by([user_rel, u], user_rel.relationship_type)
1627 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1632 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1636 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1638 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1640 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1642 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1643 when is_list(relationship_types) do
1645 |> assoc(:incoming_relationships)
1646 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1647 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1648 |> maybe_filter_on_ap_id(ap_ids)
1649 |> select([user_rel, u], u.ap_id)
1654 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1655 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1658 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1660 def set_activation_async(user, status \\ true) do
1661 BackgroundWorker.enqueue("user_activation", %{"user_id" => user.id, "status" => status})
1664 @spec set_activation([User.t()], boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
1665 def set_activation(users, status) when is_list(users) do
1666 Repo.transaction(fn ->
1667 for user <- users, do: set_activation(user, status)
1671 @spec set_activation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
1672 def set_activation(%User{} = user, status) do
1673 with {:ok, user} <- set_activation_status(user, status) do
1676 |> Enum.filter(& &1.local)
1677 |> Enum.each(&set_cache(update_following_count(&1)))
1679 # Only update local user counts, remote will be update during the next pull.
1682 |> Enum.filter(& &1.local)
1683 |> Enum.each(&do_unfollow(user, &1))
1689 def approve(users) when is_list(users) do
1690 Repo.transaction(fn ->
1691 Enum.map(users, fn user ->
1692 with {:ok, user} <- approve(user), do: user
1697 def approve(%User{is_approved: false} = user) do
1698 with chg <- change(user, is_approved: true),
1699 {:ok, user} <- update_and_set_cache(chg) do
1700 post_register_action(user)
1705 def approve(%User{} = user), do: {:ok, user}
1707 def confirm(users) when is_list(users) do
1708 Repo.transaction(fn ->
1709 Enum.map(users, fn user ->
1710 with {:ok, user} <- confirm(user), do: user
1715 def confirm(%User{is_confirmed: false} = user) do
1716 with chg <- confirmation_changeset(user, set_confirmation: true),
1717 {:ok, user} <- update_and_set_cache(chg) do
1718 post_register_action(user)
1723 def confirm(%User{} = user), do: {:ok, user}
1725 def set_suggestion(users, is_suggested) when is_list(users) do
1726 Repo.transaction(fn ->
1727 Enum.map(users, fn user ->
1728 with {:ok, user} <- set_suggestion(user, is_suggested), do: user
1733 def set_suggestion(%User{is_suggested: is_suggested} = user, is_suggested), do: {:ok, user}
1735 def set_suggestion(%User{} = user, is_suggested) when is_boolean(is_suggested) do
1737 |> change(is_suggested: is_suggested)
1738 |> update_and_set_cache()
1741 def update_notification_settings(%User{} = user, settings) do
1743 |> cast(%{notification_settings: settings}, [])
1744 |> cast_embed(:notification_settings)
1745 |> validate_required([:notification_settings])
1746 |> update_and_set_cache()
1749 @spec purge_user_changeset(User.t()) :: Changeset.t()
1750 def purge_user_changeset(user) do
1751 # "Right to be forgotten"
1752 # https://gdpr.eu/right-to-be-forgotten/
1761 last_refreshed_at: nil,
1762 last_digest_emailed_at: nil,
1769 password_reset_pending: false,
1770 registration_reason: nil,
1771 confirmation_token: nil,
1775 is_moderator: false,
1777 mastofe_settings: nil,
1780 pleroma_settings_store: %{},
1783 is_discoverable: false,
1787 # nickname: preserved
1791 # Purge doesn't delete the user from the database.
1792 # It just nulls all its fields and deactivates it.
1793 # See `User.purge_user_changeset/1` above.
1794 defp purge(%User{} = user) do
1796 |> purge_user_changeset()
1797 |> update_and_set_cache()
1800 def delete(users) when is_list(users) do
1801 for user <- users, do: delete(user)
1804 def delete(%User{} = user) do
1805 # Purge the user immediately
1807 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1810 # *Actually* delete the user from the DB
1811 defp delete_from_db(%User{} = user) do
1812 invalidate_cache(user)
1816 # If the user never finalized their account, it's safe to delete them.
1817 defp maybe_delete_from_db(%User{local: true, is_confirmed: false} = user),
1818 do: delete_from_db(user)
1820 defp maybe_delete_from_db(%User{local: true, is_approved: false} = user),
1821 do: delete_from_db(user)
1823 defp maybe_delete_from_db(user), do: {:ok, user}
1825 def perform(:force_password_reset, user), do: force_password_reset(user)
1827 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1828 def perform(:delete, %User{} = user) do
1829 # Purge the user again, in case perform/2 is called directly
1832 # Remove all relationships
1835 |> Enum.each(fn follower ->
1836 ActivityPub.unfollow(follower, user)
1837 unfollow(follower, user)
1842 |> Enum.each(fn followed ->
1843 ActivityPub.unfollow(user, followed)
1844 unfollow(user, followed)
1847 delete_user_activities(user)
1848 delete_notifications_from_user_activities(user)
1849 delete_outgoing_pending_follow_requests(user)
1851 maybe_delete_from_db(user)
1854 def perform(:set_activation_async, user, status), do: set_activation(user, status)
1856 @spec external_users_query() :: Ecto.Query.t()
1857 def external_users_query do
1865 @spec external_users(keyword()) :: [User.t()]
1866 def external_users(opts \\ []) do
1868 external_users_query()
1869 |> select([u], struct(u, [:id, :ap_id]))
1873 do: where(query, [u], u.id > ^opts[:max_id]),
1878 do: limit(query, ^opts[:limit]),
1884 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1886 |> join(:inner, [n], activity in assoc(n, :activity))
1887 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1888 |> Repo.delete_all()
1891 def delete_user_activities(%User{ap_id: ap_id} = user) do
1893 |> Activity.Queries.by_actor()
1894 |> Repo.chunk_stream(50, :batches)
1895 |> Stream.each(fn activities ->
1896 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1901 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1902 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1903 {:ok, delete_data, _} <- Builder.delete(user, object) do
1904 Pipeline.common_pipeline(delete_data, local: user.local)
1906 {:find_object, nil} ->
1907 # We have the create activity, but not the object, it was probably pruned.
1908 # Insert a tombstone and try again
1909 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1910 {:ok, _tombstone} <- Object.create(tombstone_data) do
1911 delete_activity(activity, user)
1915 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1916 Logger.error("Error: #{inspect(e)}")
1920 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1921 when type in ["Like", "Announce"] do
1922 {:ok, undo, _} = Builder.undo(user, activity)
1923 Pipeline.common_pipeline(undo, local: user.local)
1926 defp delete_activity(_activity, _user), do: "Doing nothing"
1928 defp delete_outgoing_pending_follow_requests(user) do
1930 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1931 |> Repo.delete_all()
1934 def html_filter_policy(%User{no_rich_text: true}) do
1935 Pleroma.HTML.Scrubber.TwitterText
1938 def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
1940 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1942 def get_or_fetch_by_ap_id(ap_id) do
1943 cached_user = get_cached_by_ap_id(ap_id)
1945 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
1947 case {cached_user, maybe_fetched_user} do
1948 {_, {:ok, %User{} = user}} ->
1951 {%User{} = user, _} ->
1955 Logger.error("Could not fetch user #{ap_id}, #{inspect(e)}")
1956 {:error, :not_found}
1961 Creates an internal service actor by URI if missing.
1962 Optionally takes nickname for addressing.
1964 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1965 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1967 case get_cached_by_ap_id(uri) do
1969 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1970 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1974 %User{invisible: false} = user ->
1984 @spec set_invisible(User.t()) :: {:ok, User.t()}
1985 defp set_invisible(user) do
1987 |> change(%{invisible: true})
1988 |> update_and_set_cache()
1991 @spec create_service_actor(String.t(), String.t()) ::
1992 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1993 defp create_service_actor(uri, nickname) do
1999 follower_address: uri <> "/followers"
2002 |> put_private_key()
2003 |> unique_constraint(:nickname)
2008 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
2011 |> :public_key.pem_decode()
2013 |> :public_key.pem_entry_decode()
2018 def public_key(_), do: {:error, "key not found"}
2020 def get_public_key_for_ap_id(ap_id) do
2021 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
2022 {:ok, public_key} <- public_key(user) do
2029 def ap_enabled?(%User{local: true}), do: true
2030 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
2031 def ap_enabled?(_), do: false
2033 @doc "Gets or fetch a user by uri or nickname."
2034 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
2035 def get_or_fetch("http://" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
2036 def get_or_fetch("https://" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
2037 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
2039 # wait a period of time and return newest version of the User structs
2040 # this is because we have synchronous follow APIs and need to simulate them
2041 # with an async handshake
2042 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
2043 with %User{} = a <- get_cached_by_id(a.id),
2044 %User{} = b <- get_cached_by_id(b.id) do
2051 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
2052 with :ok <- :timer.sleep(timeout),
2053 %User{} = a <- get_cached_by_id(a.id),
2054 %User{} = b <- get_cached_by_id(b.id) do
2061 def parse_bio(bio) when is_binary(bio) and bio != "" do
2063 |> CommonUtils.format_input("text/plain", mentions_format: :full)
2067 def parse_bio(_), do: ""
2069 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
2070 # TODO: get profile URLs other than user.ap_id
2071 profile_urls = [user.ap_id]
2074 |> CommonUtils.format_input("text/plain",
2075 mentions_format: :full,
2076 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
2081 def parse_bio(_, _), do: ""
2083 def tag(user_identifiers, tags) when is_list(user_identifiers) do
2084 Repo.transaction(fn ->
2085 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
2089 def tag(nickname, tags) when is_binary(nickname),
2090 do: tag(get_by_nickname(nickname), tags)
2092 def tag(%User{} = user, tags),
2093 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
2095 def untag(user_identifiers, tags) when is_list(user_identifiers) do
2096 Repo.transaction(fn ->
2097 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
2101 def untag(nickname, tags) when is_binary(nickname),
2102 do: untag(get_by_nickname(nickname), tags)
2104 def untag(%User{} = user, tags),
2105 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
2107 defp update_tags(%User{} = user, new_tags) do
2108 {:ok, updated_user} =
2110 |> change(%{tags: new_tags})
2111 |> update_and_set_cache()
2116 defp normalize_tags(tags) do
2119 |> Enum.map(&String.downcase/1)
2122 def local_nickname_regex do
2123 if Config.get([:instance, :extended_nickname_format]) do
2124 @extended_local_nickname_regex
2126 @strict_local_nickname_regex
2130 def local_nickname(nickname_or_mention) do
2133 |> String.split("@")
2137 def full_nickname(%User{} = user) do
2138 if String.contains?(user.nickname, "@") do
2141 %{host: host} = URI.parse(user.ap_id)
2142 user.nickname <> "@" <> host
2146 def full_nickname(nickname_or_mention),
2147 do: String.trim_leading(nickname_or_mention, "@")
2149 def error_user(ap_id) do
2153 nickname: "erroruser@example.com",
2154 inserted_at: NaiveDateTime.utc_now()
2158 @spec all_superusers() :: [User.t()]
2159 def all_superusers do
2160 User.Query.build(%{super_users: true, local: true, is_active: true})
2164 def muting_reblogs?(%User{} = user, %User{} = target) do
2165 UserRelationship.reblog_mute_exists?(user, target)
2168 def showing_reblogs?(%User{} = user, %User{} = target) do
2169 not muting_reblogs?(user, target)
2173 The function returns a query to get users with no activity for given interval of days.
2174 Inactive users are those who didn't read any notification, or had any activity where
2175 the user is the activity's actor, during `inactivity_threshold` days.
2176 Deactivated users will not appear in this list.
2180 iex> Pleroma.User.list_inactive_users()
2183 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
2184 def list_inactive_users_query(inactivity_threshold \\ 7) do
2185 negative_inactivity_threshold = -inactivity_threshold
2186 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2187 # Subqueries are not supported in `where` clauses, join gets too complicated.
2188 has_read_notifications =
2189 from(n in Pleroma.Notification,
2190 where: n.seen == true,
2192 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
2195 |> Pleroma.Repo.all()
2197 from(u in Pleroma.User,
2198 left_join: a in Pleroma.Activity,
2199 on: u.ap_id == a.actor,
2200 where: not is_nil(u.nickname),
2201 where: u.is_active == ^true,
2202 where: u.id not in ^has_read_notifications,
2205 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
2206 is_nil(max(a.inserted_at))
2211 Enable or disable email notifications for user
2215 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
2216 Pleroma.User{email_notifications: %{"digest" => true}}
2218 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
2219 Pleroma.User{email_notifications: %{"digest" => false}}
2221 @spec switch_email_notifications(t(), String.t(), boolean()) ::
2222 {:ok, t()} | {:error, Ecto.Changeset.t()}
2223 def switch_email_notifications(user, type, status) do
2224 User.update_email_notifications(user, %{type => status})
2228 Set `last_digest_emailed_at` value for the user to current time
2230 @spec touch_last_digest_emailed_at(t()) :: t()
2231 def touch_last_digest_emailed_at(user) do
2232 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2234 {:ok, updated_user} =
2236 |> change(%{last_digest_emailed_at: now})
2237 |> update_and_set_cache()
2242 @spec set_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
2243 def set_confirmation(%User{} = user, bool) do
2245 |> confirmation_changeset(set_confirmation: bool)
2246 |> update_and_set_cache()
2249 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
2253 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
2254 # use instance-default
2255 config = Config.get([:assets, :mascots])
2256 default_mascot = Config.get([:assets, :default_mascot])
2257 mascot = Keyword.get(config, default_mascot)
2260 "id" => "default-mascot",
2261 "url" => mascot[:url],
2262 "preview_url" => mascot[:url],
2264 "mime_type" => mascot[:mime_type]
2269 def get_ap_ids_by_nicknames(nicknames) do
2271 where: u.nickname in ^nicknames,
2277 defp put_password_hash(
2278 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2280 change(changeset, password_hash: Pleroma.Password.hash_pwd_salt(password))
2283 defp put_password_hash(changeset), do: changeset
2285 def is_internal_user?(%User{nickname: nil}), do: true
2286 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2287 def is_internal_user?(_), do: false
2289 # A hack because user delete activities have a fake id for whatever reason
2290 # TODO: Get rid of this
2291 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2293 def get_delivered_users_by_object_id(object_id) do
2295 inner_join: delivery in assoc(u, :deliveries),
2296 where: delivery.object_id == ^object_id
2301 def change_email(user, email) do
2303 |> cast(%{email: email}, [:email])
2304 |> maybe_validate_required_email(false)
2305 |> unique_constraint(:email)
2306 |> validate_format(:email, @email_regex)
2307 |> update_and_set_cache()
2310 def alias_users(user) do
2312 |> Enum.map(&User.get_cached_by_ap_id/1)
2313 |> Enum.filter(fn user -> user != nil end)
2316 def add_alias(user, new_alias_user) do
2317 current_aliases = user.also_known_as || []
2318 new_alias_ap_id = new_alias_user.ap_id
2320 if new_alias_ap_id in current_aliases do
2324 |> cast(%{also_known_as: current_aliases ++ [new_alias_ap_id]}, [:also_known_as])
2325 |> update_and_set_cache()
2329 @spec delete_alias(User.t(), User.t()) :: {:error, :no_such_alias}
2330 def delete_alias(user, alias_user) do
2331 current_aliases = user.also_known_as || []
2332 alias_ap_id = alias_user.ap_id
2334 if alias_ap_id in current_aliases do
2336 |> cast(%{also_known_as: current_aliases -- [alias_ap_id]}, [:also_known_as])
2337 |> update_and_set_cache()
2339 {:error, :no_such_alias}
2343 # Internal function; public one is `deactivate/2`
2344 defp set_activation_status(user, status) do
2346 |> cast(%{is_active: status}, [:is_active])
2347 |> update_and_set_cache()
2350 def update_banner(user, banner) do
2352 |> cast(%{banner: banner}, [:banner])
2353 |> update_and_set_cache()
2356 def update_background(user, background) do
2358 |> cast(%{background: background}, [:background])
2359 |> update_and_set_cache()
2362 @spec validate_fields(Ecto.Changeset.t(), Boolean.t(), User.t()) :: Ecto.Changeset.t()
2363 def validate_fields(changeset, remote? \\ false, struct) do
2364 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2365 limit = Config.get([:instance, limit_name], 0)
2368 |> validate_length(:fields, max: limit)
2369 |> validate_change(:fields, fn :fields, fields ->
2370 if Enum.all?(fields, &valid_field?/1) do
2376 |> maybe_validate_rel_me_field(struct)
2379 defp valid_field?(%{"name" => name, "value" => value}) do
2380 name_limit = Config.get([:instance, :account_field_name_length], 255)
2381 value_limit = Config.get([:instance, :account_field_value_length], 255)
2383 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2384 String.length(value) <= value_limit
2387 defp valid_field?(_), do: false
2389 defp is_url(nil), do: nil
2392 case URI.parse(uri) do
2393 %URI{host: nil} -> false
2394 %URI{scheme: nil} -> false
2399 @spec maybe_validate_rel_me_field(Changeset.t(), User.t()) :: Changeset.t()
2400 defp maybe_validate_rel_me_field(changeset, %User{ap_id: _ap_id} = struct) do
2401 fields = get_change(changeset, :fields)
2402 raw_fields = get_change(changeset, :raw_fields)
2404 if is_nil(fields) do
2407 validate_rel_me_field(changeset, fields, raw_fields, struct)
2411 defp maybe_validate_rel_me_field(changeset, _), do: changeset
2413 @spec validate_rel_me_field(Changeset.t(), [Map.t()], [Map.t()], User.t()) :: Changeset.t()
2414 defp validate_rel_me_field(changeset, fields, raw_fields, %User{
2420 |> Enum.with_index()
2421 |> Enum.map(fn {%{"name" => name, "value" => value}, index} ->
2423 if is_nil(raw_fields) do
2426 Enum.at(raw_fields, index)["value"]
2429 if is_url(raw_value) do
2431 Pleroma.Web.Router.Helpers.redirect_url(
2432 Pleroma.Web.Endpoint,
2433 :redirector_with_meta,
2437 possible_urls = [ap_id, frontend_url]
2439 with "me" <- RelMe.maybe_put_rel_me(raw_value, possible_urls) do
2443 "verified_at" => DateTime.to_iso8601(DateTime.utc_now())
2447 Logger.error("Could not check for rel=me, #{inspect(e)}")
2448 %{"name" => name, "value" => value}
2451 %{"name" => name, "value" => value}
2455 put_change(changeset, :fields, fields)
2458 defp truncate_field(%{"name" => name, "value" => value}) do
2460 String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
2463 String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
2465 %{"name" => name, "value" => value}
2468 def admin_api_update(user, params) do
2475 |> update_and_set_cache()
2478 @doc "Signs user out of all applications"
2479 def global_sign_out(user) do
2480 OAuth.Authorization.delete_user_authorizations(user)
2481 OAuth.Token.delete_user_tokens(user)
2484 def mascot_update(user, url) do
2486 |> cast(%{mascot: url}, [:mascot])
2487 |> validate_required([:mascot])
2488 |> update_and_set_cache()
2491 def mastodon_settings_update(user, settings) do
2493 |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
2494 |> validate_required([:mastofe_settings])
2495 |> update_and_set_cache()
2498 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2499 def confirmation_changeset(user, set_confirmation: confirmed?) do
2504 confirmation_token: nil
2508 is_confirmed: false,
2509 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2513 cast(user, params, [:is_confirmed, :confirmation_token])
2516 @spec approval_changeset(Changeset.t(), keyword()) :: Changeset.t()
2517 def approval_changeset(user, set_approval: approved?) do
2518 cast(user, %{is_approved: approved?}, [:is_approved])
2521 @spec add_pinned_object_id(User.t(), String.t()) :: {:ok, User.t()} | {:error, term()}
2522 def add_pinned_object_id(%User{} = user, object_id) do
2523 if !user.pinned_objects[object_id] do
2524 params = %{pinned_objects: Map.put(user.pinned_objects, object_id, NaiveDateTime.utc_now())}
2527 |> cast(params, [:pinned_objects])
2528 |> validate_change(:pinned_objects, fn :pinned_objects, pinned_objects ->
2529 max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
2531 if Enum.count(pinned_objects) <= max_pinned_statuses do
2534 [pinned_objects: "You have already pinned the maximum number of statuses"]
2540 |> update_and_set_cache()
2543 @spec remove_pinned_object_id(User.t(), String.t()) :: {:ok, t()} | {:error, term()}
2544 def remove_pinned_object_id(%User{} = user, object_id) do
2547 %{pinned_objects: Map.delete(user.pinned_objects, object_id)},
2550 |> update_and_set_cache()
2553 def update_email_notifications(user, settings) do
2554 email_notifications =
2555 user.email_notifications
2556 |> Map.merge(settings)
2557 |> Map.take(["digest"])
2559 params = %{email_notifications: email_notifications}
2560 fields = [:email_notifications]
2563 |> cast(params, fields)
2564 |> validate_required(fields)
2565 |> update_and_set_cache()
2568 defp set_domain_blocks(user, domain_blocks) do
2569 params = %{domain_blocks: domain_blocks}
2572 |> cast(params, [:domain_blocks])
2573 |> validate_required([:domain_blocks])
2574 |> update_and_set_cache()
2577 def block_domain(user, domain_blocked) do
2578 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2581 def unblock_domain(user, domain_blocked) do
2582 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2585 @spec add_to_block(User.t(), User.t()) ::
2586 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2587 defp add_to_block(%User{} = user, %User{} = blocked) do
2588 with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do
2589 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2596 @spec remove_from_block(User.t(), User.t()) ::
2597 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2598 defp remove_from_block(%User{} = user, %User{} = blocked) do
2599 with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
2600 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2607 def set_invisible(user, invisible) do
2608 params = %{invisible: invisible}
2611 |> cast(params, [:invisible])
2612 |> validate_required([:invisible])
2613 |> update_and_set_cache()
2616 def sanitize_html(%User{} = user) do
2617 sanitize_html(user, nil)
2620 # User data that mastodon isn't filtering (treated as plaintext):
2623 def sanitize_html(%User{} = user, filter) do
2625 Enum.map(user.fields, fn %{"value" => value} = field ->
2626 Map.put(field, "value", HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly))
2630 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2631 |> Map.put(:fields, fields)
2634 def get_host(%User{ap_id: ap_id} = _user) do
2635 URI.parse(ap_id).host
2638 def update_last_active_at(%__MODULE__{local: true} = user) do
2640 |> cast(%{last_active_at: NaiveDateTime.utc_now()}, [:last_active_at])
2641 |> update_and_set_cache()
2644 def active_user_count(days \\ 30) do
2645 active_after = Timex.shift(NaiveDateTime.utc_now(), days: -days)
2648 |> where([u], u.last_active_at >= ^active_after)
2649 |> where([u], u.local == true)
2650 |> Repo.aggregate(:count)
2653 def update_last_status_at(user) do
2655 |> where(id: ^user.id)
2656 |> update([u], set: [last_status_at: fragment("NOW()")])
2658 |> Repo.update_all([])
2660 {1, [user]} -> set_cache(user)
2665 defp maybe_load_followed_hashtags(%User{followed_hashtags: follows} = user)
2666 when is_list(follows),
2669 defp maybe_load_followed_hashtags(%User{} = user) do
2670 followed_hashtags = HashtagFollow.get_by_user(user)
2671 %{user | followed_hashtags: followed_hashtags}
2674 def followed_hashtags(%User{followed_hashtags: follows})
2675 when is_list(follows),
2678 def followed_hashtags(%User{} = user) do
2681 |> maybe_load_followed_hashtags()
2684 user.followed_hashtags
2687 def follow_hashtag(%User{} = user, %Hashtag{} = hashtag) do
2688 Logger.debug("Follow hashtag #{hashtag.name} for user #{user.nickname}")
2689 user = maybe_load_followed_hashtags(user)
2691 with {:ok, _} <- HashtagFollow.new(user, hashtag),
2692 follows <- HashtagFollow.get_by_user(user),
2693 %User{} = user <- user |> Map.put(:followed_hashtags, follows) do
2699 def unfollow_hashtag(%User{} = user, %Hashtag{} = hashtag) do
2700 Logger.debug("Unfollow hashtag #{hashtag.name} for user #{user.nickname}")
2701 user = maybe_load_followed_hashtags(user)
2703 with {:ok, _} <- HashtagFollow.delete(user, hashtag),
2704 follows <- HashtagFollow.get_by_user(user),
2705 %User{} = user <- user |> Map.put(:followed_hashtags, follows) do
2711 def following_hashtag?(%User{} = user, %Hashtag{} = hashtag) do
2712 not is_nil(HashtagFollow.get(user, hashtag))