1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.User do
10 import Ecto, only: [assoc: 2]
14 alias Pleroma.Activity
16 alias Pleroma.Conversation.Participation
17 alias Pleroma.Delivery
18 alias Pleroma.FollowingRelationship
19 alias Pleroma.Formatter
22 alias Pleroma.Notification
24 alias Pleroma.Registration
26 alias Pleroma.RepoStreamer
28 alias Pleroma.UserRelationship
30 alias Pleroma.Web.ActivityPub.ActivityPub
31 alias Pleroma.Web.ActivityPub.Utils
32 alias Pleroma.Web.CommonAPI
33 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
34 alias Pleroma.Web.OAuth
35 alias Pleroma.Web.RelMe
36 alias Pleroma.Workers.BackgroundWorker
40 @type t :: %__MODULE__{}
41 @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
42 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
44 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
45 @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])?)*$/
47 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
48 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
50 # AP ID user relationships (blocks, mutes etc.)
51 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
52 @user_relationships_config [
54 blocker_blocks: :blocked_users,
55 blockee_blocks: :blocker_users
58 muter_mutes: :muted_users,
59 mutee_mutes: :muter_users
62 reblog_muter_mutes: :reblog_muted_users,
63 reblog_mutee_mutes: :reblog_muter_users
66 notification_muter_mutes: :notification_muted_users,
67 notification_mutee_mutes: :notification_muter_users
69 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
70 inverse_subscription: [
71 subscribee_subscriptions: :subscriber_users,
72 subscriber_subscriptions: :subscribee_users
78 field(:email, :string)
80 field(:nickname, :string)
81 field(:password_hash, :string)
82 field(:password, :string, virtual: true)
83 field(:password_confirmation, :string, virtual: true)
85 field(:ap_id, :string)
87 field(:local, :boolean, default: true)
88 field(:follower_address, :string)
89 field(:following_address, :string)
90 field(:search_rank, :float, virtual: true)
91 field(:search_type, :integer, virtual: true)
92 field(:tags, {:array, :string}, default: [])
93 field(:last_refreshed_at, :naive_datetime_usec)
94 field(:last_digest_emailed_at, :naive_datetime)
95 field(:banner, :map, default: %{})
96 field(:background, :map, default: %{})
97 field(:source_data, :map, default: %{})
98 field(:note_count, :integer, default: 0)
99 field(:follower_count, :integer, default: 0)
100 field(:following_count, :integer, default: 0)
101 field(:locked, :boolean, default: false)
102 field(:confirmation_pending, :boolean, default: false)
103 field(:password_reset_pending, :boolean, default: false)
104 field(:confirmation_token, :string, default: nil)
105 field(:default_scope, :string, default: "public")
106 field(:domain_blocks, {:array, :string}, default: [])
107 field(:deactivated, :boolean, default: false)
108 field(:no_rich_text, :boolean, default: false)
109 field(:ap_enabled, :boolean, default: false)
110 field(:is_moderator, :boolean, default: false)
111 field(:is_admin, :boolean, default: false)
112 field(:show_role, :boolean, default: true)
113 field(:settings, :map, default: nil)
114 field(:magic_key, :string, default: nil)
115 field(:uri, :string, default: nil)
116 field(:hide_followers_count, :boolean, default: false)
117 field(:hide_follows_count, :boolean, default: false)
118 field(:hide_followers, :boolean, default: false)
119 field(:hide_follows, :boolean, default: false)
120 field(:hide_favorites, :boolean, default: true)
121 field(:unread_conversation_count, :integer, default: 0)
122 field(:pinned_activities, {:array, :string}, default: [])
123 field(:email_notifications, :map, default: %{"digest" => false})
124 field(:mascot, :map, default: nil)
125 field(:emoji, {:array, :map}, default: [])
126 field(:pleroma_settings_store, :map, default: %{})
127 field(:fields, {:array, :map}, default: [])
128 field(:raw_fields, {:array, :map}, default: [])
129 field(:discoverable, :boolean, default: false)
130 field(:invisible, :boolean, default: false)
131 field(:allow_following_move, :boolean, default: true)
132 field(:skip_thread_containment, :boolean, default: false)
133 field(:actor_type, :string, default: "Person")
134 field(:also_known_as, {:array, :string}, default: [])
137 :notification_settings,
138 Pleroma.User.NotificationSetting,
142 has_many(:notifications, Notification)
143 has_many(:registrations, Registration)
144 has_many(:deliveries, Delivery)
146 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
147 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
149 for {relationship_type,
151 {outgoing_relation, outgoing_relation_target},
152 {incoming_relation, incoming_relation_source}
153 ]} <- @user_relationships_config do
154 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
155 # :notification_muter_mutes, :subscribee_subscriptions
156 has_many(outgoing_relation, UserRelationship,
157 foreign_key: :source_id,
158 where: [relationship_type: relationship_type]
161 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
162 # :notification_mutee_mutes, :subscriber_subscriptions
163 has_many(incoming_relation, UserRelationship,
164 foreign_key: :target_id,
165 where: [relationship_type: relationship_type]
168 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
169 # :notification_muted_users, :subscriber_users
170 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
172 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
173 # :notification_muter_users, :subscribee_users
174 has_many(incoming_relation_source, through: [incoming_relation, :source])
177 # `:blocks` is deprecated (replaced with `blocked_users` relation)
178 field(:blocks, {:array, :string}, default: [])
179 # `:mutes` is deprecated (replaced with `muted_users` relation)
180 field(:mutes, {:array, :string}, default: [])
181 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
182 field(:muted_reblogs, {:array, :string}, default: [])
183 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
184 field(:muted_notifications, {:array, :string}, default: [])
185 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
186 field(:subscribers, {:array, :string}, default: [])
191 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
192 @user_relationships_config do
193 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
194 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
195 # `def subscriber_users/2`
196 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
197 target_users_query = assoc(user, unquote(outgoing_relation_target))
199 if restrict_deactivated? do
200 restrict_deactivated(target_users_query)
206 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
207 # `def notification_muted_users/2`, `def subscriber_users/2`
208 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
210 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
212 restrict_deactivated?
217 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
218 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
219 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
221 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
223 restrict_deactivated?
225 |> select([u], u.ap_id)
231 Dumps Flake Id to SQL-compatible format (16-byte UUID).
232 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
234 def binary_id(source_id) when is_binary(source_id) do
235 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
242 def binary_id(source_ids) when is_list(source_ids) do
243 Enum.map(source_ids, &binary_id/1)
246 def binary_id(%User{} = user), do: binary_id(user.id)
248 @doc "Returns status account"
249 @spec account_status(User.t()) :: account_status()
250 def account_status(%User{deactivated: true}), do: :deactivated
251 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
253 def account_status(%User{confirmation_pending: true}) do
254 case Config.get([:instance, :account_activation_required]) do
255 true -> :confirmation_pending
260 def account_status(%User{}), do: :active
262 @spec visible_for?(User.t(), User.t() | nil) :: boolean()
263 def visible_for?(user, for_user \\ nil)
265 def visible_for?(%User{invisible: true}, _), do: false
267 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
269 def visible_for?(%User{} = user, for_user) do
270 account_status(user) == :active || superuser?(for_user)
273 def visible_for?(_, _), do: false
275 @spec superuser?(User.t()) :: boolean()
276 def superuser?(%User{local: true, is_admin: true}), do: true
277 def superuser?(%User{local: true, is_moderator: true}), do: true
278 def superuser?(_), do: false
280 @spec invisible?(User.t()) :: boolean()
281 def invisible?(%User{invisible: true}), do: true
282 def invisible?(_), do: false
284 def avatar_url(user, options \\ []) do
286 %{"url" => [%{"href" => href} | _]} -> href
287 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
291 def banner_url(user, options \\ []) do
293 %{"url" => [%{"href" => href} | _]} -> href
294 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
298 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
300 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
301 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
303 @spec ap_following(User.t()) :: String.t()
304 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
305 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
307 def follow_state(%User{} = user, %User{} = target) do
308 case Utils.fetch_latest_follow(user, target) do
309 %{data: %{"state" => state}} -> state
310 # Ideally this would be nil, but then Cachex does not commit the value
315 def get_cached_follow_state(user, target) do
316 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
317 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
320 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
321 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
322 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
325 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
326 def restrict_deactivated(query) do
327 from(u in query, where: u.deactivated != ^true)
330 defdelegate following_count(user), to: FollowingRelationship
332 defp truncate_fields_param(params) do
333 if Map.has_key?(params, :fields) do
334 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
340 defp truncate_if_exists(params, key, max_length) do
341 if Map.has_key?(params, key) and is_binary(params[key]) do
342 {value, _chopped} = String.split_at(params[key], max_length)
343 Map.put(params, key, value)
349 def remote_user_creation(params) do
350 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
351 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
355 |> truncate_if_exists(:name, name_limit)
356 |> truncate_if_exists(:bio, bio_limit)
357 |> truncate_fields_param()
377 :hide_followers_count,
388 |> validate_required([:name, :ap_id])
389 |> unique_constraint(:nickname)
390 |> validate_format(:nickname, @email_regex)
391 |> validate_length(:bio, max: bio_limit)
392 |> validate_length(:name, max: name_limit)
393 |> validate_fields(true)
395 case params[:source_data] do
396 %{"followers" => followers, "following" => following} ->
398 |> put_change(:follower_address, followers)
399 |> put_change(:following_address, following)
402 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
403 put_change(changeset, :follower_address, followers)
407 def update_changeset(struct, params \\ %{}) do
408 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
409 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
424 :hide_followers_count,
427 :allow_following_move,
430 :skip_thread_containment,
433 :pleroma_settings_store,
439 |> unique_constraint(:nickname)
440 |> validate_format(:nickname, local_nickname_regex())
441 |> validate_length(:bio, max: bio_limit)
442 |> validate_length(:name, min: 1, max: name_limit)
444 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
445 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
446 |> put_change_if_present(:banner, &put_upload(&1, :banner))
447 |> put_change_if_present(:background, &put_upload(&1, :background))
448 |> put_change_if_present(
449 :pleroma_settings_store,
450 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
452 |> validate_fields(false)
455 defp put_fields(changeset) do
456 if raw_fields = get_change(changeset, :raw_fields) do
459 |> Enum.filter(fn %{"name" => n} -> n != "" end)
463 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
466 |> put_change(:raw_fields, raw_fields)
467 |> put_change(:fields, fields)
473 defp parse_fields(value) do
475 |> Formatter.linkify(mentions_format: :full)
479 defp put_change_if_present(changeset, map_field, value_function) do
480 if value = get_change(changeset, map_field) do
481 with {:ok, new_value} <- value_function.(value) do
482 put_change(changeset, map_field, new_value)
491 defp put_upload(value, type) do
492 with %Plug.Upload{} <- value,
493 {:ok, object} <- ActivityPub.upload(value, type: type) do
498 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
499 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
500 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
502 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
507 |> truncate_fields_param()
508 |> truncate_if_exists(:name, name_limit)
509 |> truncate_if_exists(:bio, bio_limit)
534 :allow_following_move,
536 :hide_followers_count,
542 |> unique_constraint(:nickname)
543 |> validate_format(:nickname, local_nickname_regex())
544 |> validate_length(:bio, max: bio_limit)
545 |> validate_length(:name, max: name_limit)
546 |> validate_fields(remote?)
549 def update_as_admin_changeset(struct, params) do
551 |> update_changeset(params)
552 |> cast(params, [:email])
553 |> delete_change(:also_known_as)
554 |> unique_constraint(:email)
555 |> validate_format(:email, @email_regex)
558 @spec update_as_admin(%User{}, map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
559 def update_as_admin(user, params) do
560 params = Map.put(params, "password_confirmation", params["password"])
561 changeset = update_as_admin_changeset(user, params)
563 if params["password"] do
564 reset_password(user, changeset, params)
566 User.update_and_set_cache(changeset)
570 def password_update_changeset(struct, params) do
572 |> cast(params, [:password, :password_confirmation])
573 |> validate_required([:password, :password_confirmation])
574 |> validate_confirmation(:password)
575 |> put_password_hash()
576 |> put_change(:password_reset_pending, false)
579 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
580 def reset_password(%User{} = user, params) do
581 reset_password(user, user, params)
584 def reset_password(%User{id: user_id} = user, struct, params) do
587 |> Multi.update(:user, password_update_changeset(struct, params))
588 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
589 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
591 case Repo.transaction(multi) do
592 {:ok, %{user: user} = _} -> set_cache(user)
593 {:error, _, changeset, _} -> {:error, changeset}
597 def update_password_reset_pending(user, value) do
600 |> put_change(:password_reset_pending, value)
601 |> update_and_set_cache()
604 def force_password_reset_async(user) do
605 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
608 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
609 def force_password_reset(user), do: update_password_reset_pending(user, true)
611 def register_changeset(struct, params \\ %{}, opts \\ []) do
612 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
613 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
616 if is_nil(opts[:need_confirmation]) do
617 Pleroma.Config.get([:instance, :account_activation_required])
619 opts[:need_confirmation]
623 |> confirmation_changeset(need_confirmation: need_confirmation?)
624 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
625 |> validate_required([:name, :nickname, :password, :password_confirmation])
626 |> validate_confirmation(:password)
627 |> unique_constraint(:email)
628 |> unique_constraint(:nickname)
629 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
630 |> validate_format(:nickname, local_nickname_regex())
631 |> validate_format(:email, @email_regex)
632 |> validate_length(:bio, max: bio_limit)
633 |> validate_length(:name, min: 1, max: name_limit)
634 |> maybe_validate_required_email(opts[:external])
637 |> unique_constraint(:ap_id)
638 |> put_following_and_follower_address()
641 def maybe_validate_required_email(changeset, true), do: changeset
643 def maybe_validate_required_email(changeset, _) do
644 if Pleroma.Config.get([:instance, :account_activation_required]) do
645 validate_required(changeset, [:email])
651 defp put_ap_id(changeset) do
652 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
653 put_change(changeset, :ap_id, ap_id)
656 defp put_following_and_follower_address(changeset) do
657 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
660 |> put_change(:follower_address, followers)
663 defp autofollow_users(user) do
664 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
667 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
670 follow_all(user, autofollowed_users)
673 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
674 def register(%Ecto.Changeset{} = changeset) do
675 with {:ok, user} <- Repo.insert(changeset) do
676 post_register_action(user)
680 def post_register_action(%User{} = user) do
681 with {:ok, user} <- autofollow_users(user),
682 {:ok, user} <- set_cache(user),
683 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
684 {:ok, _} <- try_send_confirmation_email(user) do
689 def try_send_confirmation_email(%User{} = user) do
690 if user.confirmation_pending &&
691 Pleroma.Config.get([:instance, :account_activation_required]) do
693 |> Pleroma.Emails.UserEmail.account_confirmation_email()
694 |> Pleroma.Emails.Mailer.deliver_async()
702 def try_send_confirmation_email(users) do
703 Enum.each(users, &try_send_confirmation_email/1)
706 def needs_update?(%User{local: true}), do: false
708 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
710 def needs_update?(%User{local: false} = user) do
711 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
714 def needs_update?(_), do: true
716 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
718 # "Locked" (self-locked) users demand explicit authorization of follow requests
719 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
720 follow(follower, followed, :follow_pending)
723 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
724 follow(follower, followed)
727 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
728 if not ap_enabled?(followed) do
729 follow(follower, followed)
735 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
736 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
737 def follow_all(follower, followeds) do
739 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
740 |> Enum.each(&follow(follower, &1, :follow_accept))
745 defdelegate following(user), to: FollowingRelationship
747 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
748 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
751 followed.deactivated ->
752 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
754 deny_follow_blocked and blocks?(followed, follower) ->
755 {:error, "Could not follow user: #{followed.nickname} blocked you."}
758 FollowingRelationship.follow(follower, followed, state)
760 {:ok, _} = update_follower_count(followed)
763 |> update_following_count()
768 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
769 {:error, "Not subscribed!"}
772 def unfollow(%User{} = follower, %User{} = followed) do
773 case get_follow_state(follower, followed) do
774 state when state in [:follow_pending, :follow_accept] ->
775 FollowingRelationship.unfollow(follower, followed)
776 {:ok, followed} = update_follower_count(followed)
780 |> update_following_count()
783 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
786 {:error, "Not subscribed!"}
790 defdelegate following?(follower, followed), to: FollowingRelationship
792 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
793 def get_follow_state(%User{} = follower, %User{} = following) do
794 following_relationship = FollowingRelationship.get(follower, following)
796 case {following_relationship, following.local} do
798 case Utils.fetch_latest_follow(follower, following) do
799 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
800 FollowingRelationship.state_to_enum(state)
806 {%{state: state}, _} ->
814 def locked?(%User{} = user) do
819 Repo.get_by(User, id: id)
822 def get_by_ap_id(ap_id) do
823 Repo.get_by(User, ap_id: ap_id)
826 def get_all_by_ap_id(ap_ids) do
827 from(u in __MODULE__,
828 where: u.ap_id in ^ap_ids
833 def get_all_by_ids(ids) do
834 from(u in __MODULE__, where: u.id in ^ids)
838 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
839 # of the ap_id and the domain and tries to get that user
840 def get_by_guessed_nickname(ap_id) do
841 domain = URI.parse(ap_id).host
842 name = List.last(String.split(ap_id, "/"))
843 nickname = "#{name}@#{domain}"
845 get_cached_by_nickname(nickname)
848 def set_cache({:ok, user}), do: set_cache(user)
849 def set_cache({:error, err}), do: {:error, err}
851 def set_cache(%User{} = user) do
852 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
853 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
857 def update_and_set_cache(struct, params) do
859 |> update_changeset(params)
860 |> update_and_set_cache()
863 def update_and_set_cache(changeset) do
864 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
869 def invalidate_cache(user) do
870 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
871 Cachex.del(:user_cache, "nickname:#{user.nickname}")
874 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
875 def get_cached_by_ap_id(ap_id) do
876 key = "ap_id:#{ap_id}"
878 with {:ok, nil} <- Cachex.get(:user_cache, key),
879 user when not is_nil(user) <- get_by_ap_id(ap_id),
880 {:ok, true} <- Cachex.put(:user_cache, key, user) do
888 def get_cached_by_id(id) do
892 Cachex.fetch!(:user_cache, key, fn _ ->
896 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
897 {:commit, user.ap_id}
903 get_cached_by_ap_id(ap_id)
906 def get_cached_by_nickname(nickname) do
907 key = "nickname:#{nickname}"
909 Cachex.fetch!(:user_cache, key, fn ->
910 case get_or_fetch_by_nickname(nickname) do
911 {:ok, user} -> {:commit, user}
912 {:error, _error} -> {:ignore, nil}
917 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
918 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
921 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
922 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
924 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
925 get_cached_by_nickname(nickname_or_id)
927 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
928 get_cached_by_nickname(nickname_or_id)
935 def get_by_nickname(nickname) do
936 Repo.get_by(User, nickname: nickname) ||
937 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
938 Repo.get_by(User, nickname: local_nickname(nickname))
942 def get_by_email(email), do: Repo.get_by(User, email: email)
944 def get_by_nickname_or_email(nickname_or_email) do
945 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
948 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
950 def get_or_fetch_by_nickname(nickname) do
951 with %User{} = user <- get_by_nickname(nickname) do
955 with [_nick, _domain] <- String.split(nickname, "@"),
956 {:ok, user} <- fetch_by_nickname(nickname) do
959 _e -> {:error, "not found " <> nickname}
964 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
965 def get_followers_query(%User{} = user, nil) do
966 User.Query.build(%{followers: user, deactivated: false})
969 def get_followers_query(user, page) do
971 |> get_followers_query(nil)
972 |> User.Query.paginate(page, 20)
975 @spec get_followers_query(User.t()) :: Ecto.Query.t()
976 def get_followers_query(user), do: get_followers_query(user, nil)
978 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
979 def get_followers(user, page \\ nil) do
981 |> get_followers_query(page)
985 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
986 def get_external_followers(user, page \\ nil) do
988 |> get_followers_query(page)
989 |> User.Query.build(%{external: true})
993 def get_followers_ids(user, page \\ nil) do
995 |> get_followers_query(page)
1000 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1001 def get_friends_query(%User{} = user, nil) do
1002 User.Query.build(%{friends: user, deactivated: false})
1005 def get_friends_query(user, page) do
1007 |> get_friends_query(nil)
1008 |> User.Query.paginate(page, 20)
1011 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1012 def get_friends_query(user), do: get_friends_query(user, nil)
1014 def get_friends(user, page \\ nil) do
1016 |> get_friends_query(page)
1020 def get_friends_ap_ids(user) do
1022 |> get_friends_query(nil)
1023 |> select([u], u.ap_id)
1027 def get_friends_ids(user, page \\ nil) do
1029 |> get_friends_query(page)
1030 |> select([u], u.id)
1034 defdelegate get_follow_requests(user), to: FollowingRelationship
1036 def increase_note_count(%User{} = user) do
1038 |> where(id: ^user.id)
1039 |> update([u], inc: [note_count: 1])
1041 |> Repo.update_all([])
1043 {1, [user]} -> set_cache(user)
1048 def decrease_note_count(%User{} = user) do
1050 |> where(id: ^user.id)
1053 note_count: fragment("greatest(0, note_count - 1)")
1057 |> Repo.update_all([])
1059 {1, [user]} -> set_cache(user)
1064 def update_note_count(%User{} = user, note_count \\ nil) do
1069 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1075 |> cast(%{note_count: note_count}, [:note_count])
1076 |> update_and_set_cache()
1079 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1080 def maybe_fetch_follow_information(user) do
1081 with {:ok, user} <- fetch_follow_information(user) do
1085 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1091 def fetch_follow_information(user) do
1092 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1094 |> follow_information_changeset(info)
1095 |> update_and_set_cache()
1099 defp follow_information_changeset(user, params) do
1106 :hide_followers_count,
1111 def update_follower_count(%User{} = user) do
1112 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
1113 follower_count_query =
1114 User.Query.build(%{followers: user, deactivated: false})
1115 |> select([u], %{count: count(u.id)})
1118 |> where(id: ^user.id)
1119 |> join(:inner, [u], s in subquery(follower_count_query))
1121 set: [follower_count: s.count]
1124 |> Repo.update_all([])
1126 {1, [user]} -> set_cache(user)
1130 {:ok, maybe_fetch_follow_information(user)}
1134 @spec update_following_count(User.t()) :: User.t()
1135 def update_following_count(%User{local: false} = user) do
1136 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1137 maybe_fetch_follow_information(user)
1143 def update_following_count(%User{local: true} = user) do
1144 following_count = FollowingRelationship.following_count(user)
1147 |> follow_information_changeset(%{following_count: following_count})
1151 def set_unread_conversation_count(%User{local: true} = user) do
1152 unread_query = Participation.unread_conversation_count_for_user(user)
1155 |> join(:inner, [u], p in subquery(unread_query))
1157 set: [unread_conversation_count: p.count]
1159 |> where([u], u.id == ^user.id)
1161 |> Repo.update_all([])
1163 {1, [user]} -> set_cache(user)
1168 def set_unread_conversation_count(user), do: {:ok, user}
1170 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1172 Participation.unread_conversation_count_for_user(user)
1173 |> where([p], p.conversation_id == ^conversation.id)
1176 |> join(:inner, [u], p in subquery(unread_query))
1178 inc: [unread_conversation_count: 1]
1180 |> where([u], u.id == ^user.id)
1181 |> where([u, p], p.count == 0)
1183 |> Repo.update_all([])
1185 {1, [user]} -> set_cache(user)
1190 def increment_unread_conversation_count(_, user), do: {:ok, user}
1192 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1193 def get_users_from_set(ap_ids, local_only \\ true) do
1194 criteria = %{ap_id: ap_ids, deactivated: false}
1195 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1197 User.Query.build(criteria)
1201 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1202 def get_recipients_from_activity(%Activity{recipients: to}) do
1203 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1207 @spec mute(User.t(), User.t(), boolean()) ::
1208 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1209 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1210 add_to_mutes(muter, mutee, notifications?)
1213 def unmute(%User{} = muter, %User{} = mutee) do
1214 remove_from_mutes(muter, mutee)
1217 def subscribe(%User{} = subscriber, %User{} = target) do
1218 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1220 if blocks?(target, subscriber) and deny_follow_blocked do
1221 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1223 # Note: the relationship is inverse: subscriber acts as relationship target
1224 UserRelationship.create_inverse_subscription(target, subscriber)
1228 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1229 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1230 subscribe(subscriber, subscribee)
1234 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1235 # Note: the relationship is inverse: subscriber acts as relationship target
1236 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1239 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1240 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1241 unsubscribe(unsubscriber, user)
1245 def block(%User{} = blocker, %User{} = blocked) do
1246 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1248 if following?(blocker, blocked) do
1249 {:ok, blocker, _} = unfollow(blocker, blocked)
1255 # clear any requested follows as well
1257 case CommonAPI.reject_follow_request(blocked, blocker) do
1258 {:ok, %User{} = updated_blocked} -> updated_blocked
1262 unsubscribe(blocked, blocker)
1264 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1266 {:ok, blocker} = update_follower_count(blocker)
1267 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1268 add_to_block(blocker, blocked)
1271 # helper to handle the block given only an actor's AP id
1272 def block(%User{} = blocker, %{ap_id: ap_id}) do
1273 block(blocker, get_cached_by_ap_id(ap_id))
1276 def unblock(%User{} = blocker, %User{} = blocked) do
1277 remove_from_block(blocker, blocked)
1280 # helper to handle the block given only an actor's AP id
1281 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1282 unblock(blocker, get_cached_by_ap_id(ap_id))
1285 def mutes?(nil, _), do: false
1286 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1288 def mutes_user?(%User{} = user, %User{} = target) do
1289 UserRelationship.mute_exists?(user, target)
1292 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1293 def muted_notifications?(nil, _), do: false
1295 def muted_notifications?(%User{} = user, %User{} = target),
1296 do: UserRelationship.notification_mute_exists?(user, target)
1298 def blocks?(nil, _), do: false
1300 def blocks?(%User{} = user, %User{} = target) do
1301 blocks_user?(user, target) ||
1302 (blocks_domain?(user, target) and not User.following?(user, target))
1305 def blocks_user?(%User{} = user, %User{} = target) do
1306 UserRelationship.block_exists?(user, target)
1309 def blocks_user?(_, _), do: false
1311 def blocks_domain?(%User{} = user, %User{} = target) do
1312 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1313 %{host: host} = URI.parse(target.ap_id)
1314 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1317 def blocks_domain?(_, _), do: false
1319 def subscribed_to?(%User{} = user, %User{} = target) do
1320 # Note: the relationship is inverse: subscriber acts as relationship target
1321 UserRelationship.inverse_subscription_exists?(target, user)
1324 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1325 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1326 subscribed_to?(user, target)
1331 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1332 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1334 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1335 def outgoing_relationships_ap_ids(_user, []), do: %{}
1337 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1339 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1340 when is_list(relationship_types) do
1343 |> assoc(:outgoing_relationships)
1344 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1345 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1346 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1347 |> group_by([user_rel, u], user_rel.relationship_type)
1349 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1354 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1358 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1360 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1362 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1364 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1365 when is_list(relationship_types) do
1367 |> assoc(:incoming_relationships)
1368 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1369 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1370 |> maybe_filter_on_ap_id(ap_ids)
1371 |> select([user_rel, u], u.ap_id)
1376 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1377 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1380 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1382 def deactivate_async(user, status \\ true) do
1383 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1386 def deactivate(user, status \\ true)
1388 def deactivate(users, status) when is_list(users) do
1389 Repo.transaction(fn ->
1390 for user <- users, do: deactivate(user, status)
1394 def deactivate(%User{} = user, status) do
1395 with {:ok, user} <- set_activation_status(user, status) do
1398 |> Enum.filter(& &1.local)
1399 |> Enum.each(fn follower ->
1400 follower |> update_following_count() |> set_cache()
1403 # Only update local user counts, remote will be update during the next pull.
1406 |> Enum.filter(& &1.local)
1407 |> Enum.each(&update_follower_count/1)
1413 def update_notification_settings(%User{} = user, settings) do
1415 |> cast(%{notification_settings: settings}, [])
1416 |> cast_embed(:notification_settings)
1417 |> validate_required([:notification_settings])
1418 |> update_and_set_cache()
1421 def delete(users) when is_list(users) do
1422 for user <- users, do: delete(user)
1425 def delete(%User{} = user) do
1426 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1429 def perform(:force_password_reset, user), do: force_password_reset(user)
1431 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1432 def perform(:delete, %User{} = user) do
1433 {:ok, _user} = ActivityPub.delete(user)
1435 # Remove all relationships
1438 |> Enum.each(fn follower ->
1439 ActivityPub.unfollow(follower, user)
1440 unfollow(follower, user)
1445 |> Enum.each(fn followed ->
1446 ActivityPub.unfollow(user, followed)
1447 unfollow(user, followed)
1450 delete_user_activities(user)
1454 |> change(%{deactivated: true, email: nil})
1455 |> update_and_set_cache()
1457 invalidate_cache(user)
1462 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1464 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1465 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1466 when is_list(blocked_identifiers) do
1468 blocked_identifiers,
1469 fn blocked_identifier ->
1470 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1471 {:ok, _user_block} <- block(blocker, blocked),
1472 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1476 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1483 def perform(:follow_import, %User{} = follower, followed_identifiers)
1484 when is_list(followed_identifiers) do
1486 followed_identifiers,
1487 fn followed_identifier ->
1488 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1489 {:ok, follower} <- maybe_direct_follow(follower, followed),
1490 {:ok, _} <- ActivityPub.follow(follower, followed) do
1494 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1501 @spec external_users_query() :: Ecto.Query.t()
1502 def external_users_query do
1510 @spec external_users(keyword()) :: [User.t()]
1511 def external_users(opts \\ []) do
1513 external_users_query()
1514 |> select([u], struct(u, [:id, :ap_id]))
1518 do: where(query, [u], u.id > ^opts[:max_id]),
1523 do: limit(query, ^opts[:limit]),
1529 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1530 BackgroundWorker.enqueue("blocks_import", %{
1531 "blocker_id" => blocker.id,
1532 "blocked_identifiers" => blocked_identifiers
1536 def follow_import(%User{} = follower, followed_identifiers)
1537 when is_list(followed_identifiers) do
1538 BackgroundWorker.enqueue("follow_import", %{
1539 "follower_id" => follower.id,
1540 "followed_identifiers" => followed_identifiers
1544 def delete_user_activities(%User{ap_id: ap_id}) do
1546 |> Activity.Queries.by_actor()
1547 |> RepoStreamer.chunk_stream(50)
1548 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1552 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1554 |> Object.normalize()
1555 |> ActivityPub.delete()
1558 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1559 object = Object.normalize(activity)
1562 |> get_cached_by_ap_id()
1563 |> ActivityPub.unlike(object)
1566 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1567 object = Object.normalize(activity)
1570 |> get_cached_by_ap_id()
1571 |> ActivityPub.unannounce(object)
1574 defp delete_activity(_activity), do: "Doing nothing"
1576 def html_filter_policy(%User{no_rich_text: true}) do
1577 Pleroma.HTML.Scrubber.TwitterText
1580 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1582 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1584 def get_or_fetch_by_ap_id(ap_id) do
1585 user = get_cached_by_ap_id(ap_id)
1587 if !is_nil(user) and !needs_update?(user) do
1590 fetch_by_ap_id(ap_id)
1595 Creates an internal service actor by URI if missing.
1596 Optionally takes nickname for addressing.
1598 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1599 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1601 case get_cached_by_ap_id(uri) do
1603 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1604 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1608 %User{invisible: false} = user ->
1618 @spec set_invisible(User.t()) :: {:ok, User.t()}
1619 defp set_invisible(user) do
1621 |> change(%{invisible: true})
1622 |> update_and_set_cache()
1625 @spec create_service_actor(String.t(), String.t()) ::
1626 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1627 defp create_service_actor(uri, nickname) do
1633 follower_address: uri <> "/followers"
1636 |> unique_constraint(:nickname)
1642 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1645 |> :public_key.pem_decode()
1647 |> :public_key.pem_entry_decode()
1652 def public_key(_), do: {:error, "not found key"}
1654 def get_public_key_for_ap_id(ap_id) do
1655 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1656 {:ok, public_key} <- public_key(user) do
1663 defp blank?(""), do: nil
1664 defp blank?(n), do: n
1666 def insert_or_update_user(data) do
1668 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1669 |> remote_user_creation()
1670 |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
1674 def ap_enabled?(%User{local: true}), do: true
1675 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1676 def ap_enabled?(_), do: false
1678 @doc "Gets or fetch a user by uri or nickname."
1679 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1680 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1681 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1683 # wait a period of time and return newest version of the User structs
1684 # this is because we have synchronous follow APIs and need to simulate them
1685 # with an async handshake
1686 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1687 with %User{} = a <- get_cached_by_id(a.id),
1688 %User{} = b <- get_cached_by_id(b.id) do
1695 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1696 with :ok <- :timer.sleep(timeout),
1697 %User{} = a <- get_cached_by_id(a.id),
1698 %User{} = b <- get_cached_by_id(b.id) do
1705 def parse_bio(bio) when is_binary(bio) and bio != "" do
1707 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1711 def parse_bio(_), do: ""
1713 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1714 # TODO: get profile URLs other than user.ap_id
1715 profile_urls = [user.ap_id]
1718 |> CommonUtils.format_input("text/plain",
1719 mentions_format: :full,
1720 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1725 def parse_bio(_, _), do: ""
1727 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1728 Repo.transaction(fn ->
1729 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1733 def tag(nickname, tags) when is_binary(nickname),
1734 do: tag(get_by_nickname(nickname), tags)
1736 def tag(%User{} = user, tags),
1737 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1739 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1740 Repo.transaction(fn ->
1741 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1745 def untag(nickname, tags) when is_binary(nickname),
1746 do: untag(get_by_nickname(nickname), tags)
1748 def untag(%User{} = user, tags),
1749 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1751 defp update_tags(%User{} = user, new_tags) do
1752 {:ok, updated_user} =
1754 |> change(%{tags: new_tags})
1755 |> update_and_set_cache()
1760 defp normalize_tags(tags) do
1763 |> Enum.map(&String.downcase/1)
1766 defp local_nickname_regex do
1767 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1768 @extended_local_nickname_regex
1770 @strict_local_nickname_regex
1774 def local_nickname(nickname_or_mention) do
1777 |> String.split("@")
1781 def full_nickname(nickname_or_mention),
1782 do: String.trim_leading(nickname_or_mention, "@")
1784 def error_user(ap_id) do
1788 nickname: "erroruser@example.com",
1789 inserted_at: NaiveDateTime.utc_now()
1793 @spec all_superusers() :: [User.t()]
1794 def all_superusers do
1795 User.Query.build(%{super_users: true, local: true, deactivated: false})
1799 def showing_reblogs?(%User{} = user, %User{} = target) do
1800 not UserRelationship.reblog_mute_exists?(user, target)
1804 The function returns a query to get users with no activity for given interval of days.
1805 Inactive users are those who didn't read any notification, or had any activity where
1806 the user is the activity's actor, during `inactivity_threshold` days.
1807 Deactivated users will not appear in this list.
1811 iex> Pleroma.User.list_inactive_users()
1814 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1815 def list_inactive_users_query(inactivity_threshold \\ 7) do
1816 negative_inactivity_threshold = -inactivity_threshold
1817 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1818 # Subqueries are not supported in `where` clauses, join gets too complicated.
1819 has_read_notifications =
1820 from(n in Pleroma.Notification,
1821 where: n.seen == true,
1823 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1826 |> Pleroma.Repo.all()
1828 from(u in Pleroma.User,
1829 left_join: a in Pleroma.Activity,
1830 on: u.ap_id == a.actor,
1831 where: not is_nil(u.nickname),
1832 where: u.deactivated != ^true,
1833 where: u.id not in ^has_read_notifications,
1836 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1837 is_nil(max(a.inserted_at))
1842 Enable or disable email notifications for user
1846 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1847 Pleroma.User{email_notifications: %{"digest" => true}}
1849 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1850 Pleroma.User{email_notifications: %{"digest" => false}}
1852 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1853 {:ok, t()} | {:error, Ecto.Changeset.t()}
1854 def switch_email_notifications(user, type, status) do
1855 User.update_email_notifications(user, %{type => status})
1859 Set `last_digest_emailed_at` value for the user to current time
1861 @spec touch_last_digest_emailed_at(t()) :: t()
1862 def touch_last_digest_emailed_at(user) do
1863 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1865 {:ok, updated_user} =
1867 |> change(%{last_digest_emailed_at: now})
1868 |> update_and_set_cache()
1873 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1874 def toggle_confirmation(%User{} = user) do
1876 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1877 |> update_and_set_cache()
1880 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1881 def toggle_confirmation(users) do
1882 Enum.map(users, &toggle_confirmation/1)
1885 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1889 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1890 # use instance-default
1891 config = Pleroma.Config.get([:assets, :mascots])
1892 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1893 mascot = Keyword.get(config, default_mascot)
1896 "id" => "default-mascot",
1897 "url" => mascot[:url],
1898 "preview_url" => mascot[:url],
1900 "mime_type" => mascot[:mime_type]
1905 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1907 def ensure_keys_present(%User{} = user) do
1908 with {:ok, pem} <- Keys.generate_rsa_pem() do
1910 |> cast(%{keys: pem}, [:keys])
1911 |> validate_required([:keys])
1912 |> update_and_set_cache()
1916 def get_ap_ids_by_nicknames(nicknames) do
1918 where: u.nickname in ^nicknames,
1924 defdelegate search(query, opts \\ []), to: User.Search
1926 defp put_password_hash(
1927 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1929 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1932 defp put_password_hash(changeset), do: changeset
1934 def is_internal_user?(%User{nickname: nil}), do: true
1935 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1936 def is_internal_user?(_), do: false
1938 # A hack because user delete activities have a fake id for whatever reason
1939 # TODO: Get rid of this
1940 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1942 def get_delivered_users_by_object_id(object_id) do
1944 inner_join: delivery in assoc(u, :deliveries),
1945 where: delivery.object_id == ^object_id
1950 def change_email(user, email) do
1952 |> cast(%{email: email}, [:email])
1953 |> validate_required([:email])
1954 |> unique_constraint(:email)
1955 |> validate_format(:email, @email_regex)
1956 |> update_and_set_cache()
1959 # Internal function; public one is `deactivate/2`
1960 defp set_activation_status(user, deactivated) do
1962 |> cast(%{deactivated: deactivated}, [:deactivated])
1963 |> update_and_set_cache()
1966 def update_banner(user, banner) do
1968 |> cast(%{banner: banner}, [:banner])
1969 |> update_and_set_cache()
1972 def update_background(user, background) do
1974 |> cast(%{background: background}, [:background])
1975 |> update_and_set_cache()
1978 def update_source_data(user, source_data) do
1980 |> cast(%{source_data: source_data}, [:source_data])
1981 |> update_and_set_cache()
1984 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1987 moderator: is_moderator
1991 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1992 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1993 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1994 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1997 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1998 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
2002 def fields(%{fields: nil}), do: []
2004 def fields(%{fields: fields}), do: fields
2006 def validate_fields(changeset, remote? \\ false) do
2007 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2008 limit = Pleroma.Config.get([:instance, limit_name], 0)
2011 |> validate_length(:fields, max: limit)
2012 |> validate_change(:fields, fn :fields, fields ->
2013 if Enum.all?(fields, &valid_field?/1) do
2021 defp valid_field?(%{"name" => name, "value" => value}) do
2022 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
2023 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
2025 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2026 String.length(value) <= value_limit
2029 defp valid_field?(_), do: false
2031 defp truncate_field(%{"name" => name, "value" => value}) do
2033 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
2036 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
2038 %{"name" => name, "value" => value}
2041 def admin_api_update(user, params) do
2048 |> update_and_set_cache()
2051 @doc "Signs user out of all applications"
2052 def global_sign_out(user) do
2053 OAuth.Authorization.delete_user_authorizations(user)
2054 OAuth.Token.delete_user_tokens(user)
2057 def mascot_update(user, url) do
2059 |> cast(%{mascot: url}, [:mascot])
2060 |> validate_required([:mascot])
2061 |> update_and_set_cache()
2064 def mastodon_settings_update(user, settings) do
2066 |> cast(%{settings: settings}, [:settings])
2067 |> validate_required([:settings])
2068 |> update_and_set_cache()
2071 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2072 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2074 if need_confirmation? do
2076 confirmation_pending: true,
2077 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2081 confirmation_pending: false,
2082 confirmation_token: nil
2086 cast(user, params, [:confirmation_pending, :confirmation_token])
2089 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2090 if id not in user.pinned_activities do
2091 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
2092 params = %{pinned_activities: user.pinned_activities ++ [id]}
2095 |> cast(params, [:pinned_activities])
2096 |> validate_length(:pinned_activities,
2097 max: max_pinned_statuses,
2098 message: "You have already pinned the maximum number of statuses"
2103 |> update_and_set_cache()
2106 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2107 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2110 |> cast(params, [:pinned_activities])
2111 |> update_and_set_cache()
2114 def update_email_notifications(user, settings) do
2115 email_notifications =
2116 user.email_notifications
2117 |> Map.merge(settings)
2118 |> Map.take(["digest"])
2120 params = %{email_notifications: email_notifications}
2121 fields = [:email_notifications]
2124 |> cast(params, fields)
2125 |> validate_required(fields)
2126 |> update_and_set_cache()
2129 defp set_domain_blocks(user, domain_blocks) do
2130 params = %{domain_blocks: domain_blocks}
2133 |> cast(params, [:domain_blocks])
2134 |> validate_required([:domain_blocks])
2135 |> update_and_set_cache()
2138 def block_domain(user, domain_blocked) do
2139 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2142 def unblock_domain(user, domain_blocked) do
2143 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2146 @spec add_to_block(User.t(), User.t()) ::
2147 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2148 defp add_to_block(%User{} = user, %User{} = blocked) do
2149 UserRelationship.create_block(user, blocked)
2152 @spec add_to_block(User.t(), User.t()) ::
2153 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2154 defp remove_from_block(%User{} = user, %User{} = blocked) do
2155 UserRelationship.delete_block(user, blocked)
2158 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2159 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2160 {:ok, user_notification_mute} <-
2161 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2163 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2167 defp remove_from_mutes(user, %User{} = muted_user) do
2168 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2169 {:ok, user_notification_mute} <-
2170 UserRelationship.delete_notification_mute(user, muted_user) do
2171 {:ok, [user_mute, user_notification_mute]}
2175 def set_invisible(user, invisible) do
2176 params = %{invisible: invisible}
2179 |> cast(params, [:invisible])
2180 |> validate_required([:invisible])
2181 |> update_and_set_cache()
2184 def sanitize_html(%User{} = user) do
2185 sanitize_html(user, nil)
2188 # User data that mastodon isn't filtering (treated as plaintext):
2191 def sanitize_html(%User{} = user, filter) do
2195 |> Enum.map(fn %{"name" => name, "value" => value} ->
2198 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2203 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2204 |> Map.put(:fields, fields)