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, actor: actor}) do
1205 query = User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1211 @spec mute(User.t(), User.t(), boolean()) ::
1212 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1213 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1214 add_to_mutes(muter, mutee, notifications?)
1217 def unmute(%User{} = muter, %User{} = mutee) do
1218 remove_from_mutes(muter, mutee)
1221 def subscribe(%User{} = subscriber, %User{} = target) do
1222 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1224 if blocks?(target, subscriber) and deny_follow_blocked do
1225 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1227 # Note: the relationship is inverse: subscriber acts as relationship target
1228 UserRelationship.create_inverse_subscription(target, subscriber)
1232 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1233 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1234 subscribe(subscriber, subscribee)
1238 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1239 # Note: the relationship is inverse: subscriber acts as relationship target
1240 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1243 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1244 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1245 unsubscribe(unsubscriber, user)
1249 def block(%User{} = blocker, %User{} = blocked) do
1250 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1252 if following?(blocker, blocked) do
1253 {:ok, blocker, _} = unfollow(blocker, blocked)
1259 # clear any requested follows as well
1261 case CommonAPI.reject_follow_request(blocked, blocker) do
1262 {:ok, %User{} = updated_blocked} -> updated_blocked
1266 unsubscribe(blocked, blocker)
1268 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1270 {:ok, blocker} = update_follower_count(blocker)
1271 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1272 add_to_block(blocker, blocked)
1275 # helper to handle the block given only an actor's AP id
1276 def block(%User{} = blocker, %{ap_id: ap_id}) do
1277 block(blocker, get_cached_by_ap_id(ap_id))
1280 def unblock(%User{} = blocker, %User{} = blocked) do
1281 remove_from_block(blocker, blocked)
1284 # helper to handle the block given only an actor's AP id
1285 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1286 unblock(blocker, get_cached_by_ap_id(ap_id))
1289 def mutes?(nil, _), do: false
1290 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1292 def mutes_user?(%User{} = user, %User{} = target) do
1293 UserRelationship.mute_exists?(user, target)
1296 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1297 def muted_notifications?(nil, _), do: false
1299 def muted_notifications?(%User{} = user, %User{} = target),
1300 do: UserRelationship.notification_mute_exists?(user, target)
1302 def blocks?(nil, _), do: false
1304 def blocks?(%User{} = user, %User{} = target) do
1305 blocks_user?(user, target) ||
1306 (blocks_domain?(user, target) and not User.following?(user, target))
1309 def blocks_user?(%User{} = user, %User{} = target) do
1310 UserRelationship.block_exists?(user, target)
1313 def blocks_user?(_, _), do: false
1315 def blocks_domain?(%User{} = user, %User{} = target) do
1316 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1317 %{host: host} = URI.parse(target.ap_id)
1318 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1321 def blocks_domain?(_, _), do: false
1323 def subscribed_to?(%User{} = user, %User{} = target) do
1324 # Note: the relationship is inverse: subscriber acts as relationship target
1325 UserRelationship.inverse_subscription_exists?(target, user)
1328 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1329 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1330 subscribed_to?(user, target)
1335 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1336 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1338 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1339 def outgoing_relationships_ap_ids(_user, []), do: %{}
1341 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1343 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1344 when is_list(relationship_types) do
1347 |> assoc(:outgoing_relationships)
1348 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1349 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1350 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1351 |> group_by([user_rel, u], user_rel.relationship_type)
1353 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1358 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1362 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1364 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1366 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1368 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1369 when is_list(relationship_types) do
1371 |> assoc(:incoming_relationships)
1372 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1373 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1374 |> maybe_filter_on_ap_id(ap_ids)
1375 |> select([user_rel, u], u.ap_id)
1380 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1381 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1384 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1386 def deactivate_async(user, status \\ true) do
1387 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1390 def deactivate(user, status \\ true)
1392 def deactivate(users, status) when is_list(users) do
1393 Repo.transaction(fn ->
1394 for user <- users, do: deactivate(user, status)
1398 def deactivate(%User{} = user, status) do
1399 with {:ok, user} <- set_activation_status(user, status) do
1402 |> Enum.filter(& &1.local)
1403 |> Enum.each(fn follower ->
1404 follower |> update_following_count() |> set_cache()
1407 # Only update local user counts, remote will be update during the next pull.
1410 |> Enum.filter(& &1.local)
1411 |> Enum.each(&update_follower_count/1)
1417 def update_notification_settings(%User{} = user, settings) do
1419 |> cast(%{notification_settings: settings}, [])
1420 |> cast_embed(:notification_settings)
1421 |> validate_required([:notification_settings])
1422 |> update_and_set_cache()
1425 def delete(users) when is_list(users) do
1426 for user <- users, do: delete(user)
1429 def delete(%User{} = user) do
1430 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1433 def perform(:force_password_reset, user), do: force_password_reset(user)
1435 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1436 def perform(:delete, %User{} = user) do
1437 {:ok, _user} = ActivityPub.delete(user)
1439 # Remove all relationships
1442 |> Enum.each(fn follower ->
1443 ActivityPub.unfollow(follower, user)
1444 unfollow(follower, user)
1449 |> Enum.each(fn followed ->
1450 ActivityPub.unfollow(user, followed)
1451 unfollow(user, followed)
1454 delete_user_activities(user)
1458 |> change(%{deactivated: true, email: nil})
1459 |> update_and_set_cache()
1461 invalidate_cache(user)
1466 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1468 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1469 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1470 when is_list(blocked_identifiers) do
1472 blocked_identifiers,
1473 fn blocked_identifier ->
1474 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1475 {:ok, _user_block} <- block(blocker, blocked),
1476 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1480 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1487 def perform(:follow_import, %User{} = follower, followed_identifiers)
1488 when is_list(followed_identifiers) do
1490 followed_identifiers,
1491 fn followed_identifier ->
1492 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1493 {:ok, follower} <- maybe_direct_follow(follower, followed),
1494 {:ok, _} <- ActivityPub.follow(follower, followed) do
1498 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1505 @spec external_users_query() :: Ecto.Query.t()
1506 def external_users_query do
1514 @spec external_users(keyword()) :: [User.t()]
1515 def external_users(opts \\ []) do
1517 external_users_query()
1518 |> select([u], struct(u, [:id, :ap_id]))
1522 do: where(query, [u], u.id > ^opts[:max_id]),
1527 do: limit(query, ^opts[:limit]),
1533 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1534 BackgroundWorker.enqueue("blocks_import", %{
1535 "blocker_id" => blocker.id,
1536 "blocked_identifiers" => blocked_identifiers
1540 def follow_import(%User{} = follower, followed_identifiers)
1541 when is_list(followed_identifiers) do
1542 BackgroundWorker.enqueue("follow_import", %{
1543 "follower_id" => follower.id,
1544 "followed_identifiers" => followed_identifiers
1548 def delete_user_activities(%User{ap_id: ap_id}) do
1550 |> Activity.Queries.by_actor()
1551 |> RepoStreamer.chunk_stream(50)
1552 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1556 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1558 |> Object.normalize()
1559 |> ActivityPub.delete()
1562 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1563 object = Object.normalize(activity)
1566 |> get_cached_by_ap_id()
1567 |> ActivityPub.unlike(object)
1570 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1571 object = Object.normalize(activity)
1574 |> get_cached_by_ap_id()
1575 |> ActivityPub.unannounce(object)
1578 defp delete_activity(_activity), do: "Doing nothing"
1580 def html_filter_policy(%User{no_rich_text: true}) do
1581 Pleroma.HTML.Scrubber.TwitterText
1584 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1586 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1588 def get_or_fetch_by_ap_id(ap_id) do
1589 user = get_cached_by_ap_id(ap_id)
1591 if !is_nil(user) and !needs_update?(user) do
1594 fetch_by_ap_id(ap_id)
1599 Creates an internal service actor by URI if missing.
1600 Optionally takes nickname for addressing.
1602 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1603 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1605 case get_cached_by_ap_id(uri) do
1607 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1608 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1612 %User{invisible: false} = user ->
1622 @spec set_invisible(User.t()) :: {:ok, User.t()}
1623 defp set_invisible(user) do
1625 |> change(%{invisible: true})
1626 |> update_and_set_cache()
1629 @spec create_service_actor(String.t(), String.t()) ::
1630 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1631 defp create_service_actor(uri, nickname) do
1637 follower_address: uri <> "/followers"
1640 |> unique_constraint(:nickname)
1646 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1649 |> :public_key.pem_decode()
1651 |> :public_key.pem_entry_decode()
1656 def public_key(_), do: {:error, "not found key"}
1658 def get_public_key_for_ap_id(ap_id) do
1659 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1660 {:ok, public_key} <- public_key(user) do
1667 defp blank?(""), do: nil
1668 defp blank?(n), do: n
1670 def insert_or_update_user(data) do
1672 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1673 |> remote_user_creation()
1674 |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
1678 def ap_enabled?(%User{local: true}), do: true
1679 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1680 def ap_enabled?(_), do: false
1682 @doc "Gets or fetch a user by uri or nickname."
1683 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1684 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1685 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1687 # wait a period of time and return newest version of the User structs
1688 # this is because we have synchronous follow APIs and need to simulate them
1689 # with an async handshake
1690 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1691 with %User{} = a <- get_cached_by_id(a.id),
1692 %User{} = b <- get_cached_by_id(b.id) do
1699 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1700 with :ok <- :timer.sleep(timeout),
1701 %User{} = a <- get_cached_by_id(a.id),
1702 %User{} = b <- get_cached_by_id(b.id) do
1709 def parse_bio(bio) when is_binary(bio) and bio != "" do
1711 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1715 def parse_bio(_), do: ""
1717 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1718 # TODO: get profile URLs other than user.ap_id
1719 profile_urls = [user.ap_id]
1722 |> CommonUtils.format_input("text/plain",
1723 mentions_format: :full,
1724 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1729 def parse_bio(_, _), do: ""
1731 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1732 Repo.transaction(fn ->
1733 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1737 def tag(nickname, tags) when is_binary(nickname),
1738 do: tag(get_by_nickname(nickname), tags)
1740 def tag(%User{} = user, tags),
1741 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1743 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1744 Repo.transaction(fn ->
1745 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1749 def untag(nickname, tags) when is_binary(nickname),
1750 do: untag(get_by_nickname(nickname), tags)
1752 def untag(%User{} = user, tags),
1753 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1755 defp update_tags(%User{} = user, new_tags) do
1756 {:ok, updated_user} =
1758 |> change(%{tags: new_tags})
1759 |> update_and_set_cache()
1764 defp normalize_tags(tags) do
1767 |> Enum.map(&String.downcase/1)
1770 defp local_nickname_regex do
1771 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1772 @extended_local_nickname_regex
1774 @strict_local_nickname_regex
1778 def local_nickname(nickname_or_mention) do
1781 |> String.split("@")
1785 def full_nickname(nickname_or_mention),
1786 do: String.trim_leading(nickname_or_mention, "@")
1788 def error_user(ap_id) do
1792 nickname: "erroruser@example.com",
1793 inserted_at: NaiveDateTime.utc_now()
1797 @spec all_superusers() :: [User.t()]
1798 def all_superusers do
1799 User.Query.build(%{super_users: true, local: true, deactivated: false})
1803 def showing_reblogs?(%User{} = user, %User{} = target) do
1804 not UserRelationship.reblog_mute_exists?(user, target)
1808 The function returns a query to get users with no activity for given interval of days.
1809 Inactive users are those who didn't read any notification, or had any activity where
1810 the user is the activity's actor, during `inactivity_threshold` days.
1811 Deactivated users will not appear in this list.
1815 iex> Pleroma.User.list_inactive_users()
1818 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1819 def list_inactive_users_query(inactivity_threshold \\ 7) do
1820 negative_inactivity_threshold = -inactivity_threshold
1821 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1822 # Subqueries are not supported in `where` clauses, join gets too complicated.
1823 has_read_notifications =
1824 from(n in Pleroma.Notification,
1825 where: n.seen == true,
1827 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1830 |> Pleroma.Repo.all()
1832 from(u in Pleroma.User,
1833 left_join: a in Pleroma.Activity,
1834 on: u.ap_id == a.actor,
1835 where: not is_nil(u.nickname),
1836 where: u.deactivated != ^true,
1837 where: u.id not in ^has_read_notifications,
1840 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1841 is_nil(max(a.inserted_at))
1846 Enable or disable email notifications for user
1850 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1851 Pleroma.User{email_notifications: %{"digest" => true}}
1853 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1854 Pleroma.User{email_notifications: %{"digest" => false}}
1856 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1857 {:ok, t()} | {:error, Ecto.Changeset.t()}
1858 def switch_email_notifications(user, type, status) do
1859 User.update_email_notifications(user, %{type => status})
1863 Set `last_digest_emailed_at` value for the user to current time
1865 @spec touch_last_digest_emailed_at(t()) :: t()
1866 def touch_last_digest_emailed_at(user) do
1867 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1869 {:ok, updated_user} =
1871 |> change(%{last_digest_emailed_at: now})
1872 |> update_and_set_cache()
1877 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1878 def toggle_confirmation(%User{} = user) do
1880 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1881 |> update_and_set_cache()
1884 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1885 def toggle_confirmation(users) do
1886 Enum.map(users, &toggle_confirmation/1)
1889 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1893 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1894 # use instance-default
1895 config = Pleroma.Config.get([:assets, :mascots])
1896 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1897 mascot = Keyword.get(config, default_mascot)
1900 "id" => "default-mascot",
1901 "url" => mascot[:url],
1902 "preview_url" => mascot[:url],
1904 "mime_type" => mascot[:mime_type]
1909 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1911 def ensure_keys_present(%User{} = user) do
1912 with {:ok, pem} <- Keys.generate_rsa_pem() do
1914 |> cast(%{keys: pem}, [:keys])
1915 |> validate_required([:keys])
1916 |> update_and_set_cache()
1920 def get_ap_ids_by_nicknames(nicknames) do
1922 where: u.nickname in ^nicknames,
1928 defdelegate search(query, opts \\ []), to: User.Search
1930 defp put_password_hash(
1931 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1933 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1936 defp put_password_hash(changeset), do: changeset
1938 def is_internal_user?(%User{nickname: nil}), do: true
1939 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1940 def is_internal_user?(_), do: false
1942 # A hack because user delete activities have a fake id for whatever reason
1943 # TODO: Get rid of this
1944 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1946 def get_delivered_users_by_object_id(object_id) do
1948 inner_join: delivery in assoc(u, :deliveries),
1949 where: delivery.object_id == ^object_id
1954 def change_email(user, email) do
1956 |> cast(%{email: email}, [:email])
1957 |> validate_required([:email])
1958 |> unique_constraint(:email)
1959 |> validate_format(:email, @email_regex)
1960 |> update_and_set_cache()
1963 # Internal function; public one is `deactivate/2`
1964 defp set_activation_status(user, deactivated) do
1966 |> cast(%{deactivated: deactivated}, [:deactivated])
1967 |> update_and_set_cache()
1970 def update_banner(user, banner) do
1972 |> cast(%{banner: banner}, [:banner])
1973 |> update_and_set_cache()
1976 def update_background(user, background) do
1978 |> cast(%{background: background}, [:background])
1979 |> update_and_set_cache()
1982 def update_source_data(user, source_data) do
1984 |> cast(%{source_data: source_data}, [:source_data])
1985 |> update_and_set_cache()
1988 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1991 moderator: is_moderator
1995 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1996 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1997 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1998 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
2001 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
2002 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
2006 def fields(%{fields: nil}), do: []
2008 def fields(%{fields: fields}), do: fields
2010 def validate_fields(changeset, remote? \\ false) do
2011 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2012 limit = Pleroma.Config.get([:instance, limit_name], 0)
2015 |> validate_length(:fields, max: limit)
2016 |> validate_change(:fields, fn :fields, fields ->
2017 if Enum.all?(fields, &valid_field?/1) do
2025 defp valid_field?(%{"name" => name, "value" => value}) do
2026 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
2027 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
2029 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2030 String.length(value) <= value_limit
2033 defp valid_field?(_), do: false
2035 defp truncate_field(%{"name" => name, "value" => value}) do
2037 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
2040 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
2042 %{"name" => name, "value" => value}
2045 def admin_api_update(user, params) do
2052 |> update_and_set_cache()
2055 @doc "Signs user out of all applications"
2056 def global_sign_out(user) do
2057 OAuth.Authorization.delete_user_authorizations(user)
2058 OAuth.Token.delete_user_tokens(user)
2061 def mascot_update(user, url) do
2063 |> cast(%{mascot: url}, [:mascot])
2064 |> validate_required([:mascot])
2065 |> update_and_set_cache()
2068 def mastodon_settings_update(user, settings) do
2070 |> cast(%{settings: settings}, [:settings])
2071 |> validate_required([:settings])
2072 |> update_and_set_cache()
2075 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2076 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2078 if need_confirmation? do
2080 confirmation_pending: true,
2081 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2085 confirmation_pending: false,
2086 confirmation_token: nil
2090 cast(user, params, [:confirmation_pending, :confirmation_token])
2093 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2094 if id not in user.pinned_activities do
2095 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
2096 params = %{pinned_activities: user.pinned_activities ++ [id]}
2099 |> cast(params, [:pinned_activities])
2100 |> validate_length(:pinned_activities,
2101 max: max_pinned_statuses,
2102 message: "You have already pinned the maximum number of statuses"
2107 |> update_and_set_cache()
2110 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2111 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2114 |> cast(params, [:pinned_activities])
2115 |> update_and_set_cache()
2118 def update_email_notifications(user, settings) do
2119 email_notifications =
2120 user.email_notifications
2121 |> Map.merge(settings)
2122 |> Map.take(["digest"])
2124 params = %{email_notifications: email_notifications}
2125 fields = [:email_notifications]
2128 |> cast(params, fields)
2129 |> validate_required(fields)
2130 |> update_and_set_cache()
2133 defp set_domain_blocks(user, domain_blocks) do
2134 params = %{domain_blocks: domain_blocks}
2137 |> cast(params, [:domain_blocks])
2138 |> validate_required([:domain_blocks])
2139 |> update_and_set_cache()
2142 def block_domain(user, domain_blocked) do
2143 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2146 def unblock_domain(user, domain_blocked) do
2147 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2150 @spec add_to_block(User.t(), User.t()) ::
2151 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2152 defp add_to_block(%User{} = user, %User{} = blocked) do
2153 UserRelationship.create_block(user, blocked)
2156 @spec add_to_block(User.t(), User.t()) ::
2157 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2158 defp remove_from_block(%User{} = user, %User{} = blocked) do
2159 UserRelationship.delete_block(user, blocked)
2162 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2163 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2164 {:ok, user_notification_mute} <-
2165 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2167 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2171 defp remove_from_mutes(user, %User{} = muted_user) do
2172 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2173 {:ok, user_notification_mute} <-
2174 UserRelationship.delete_notification_mute(user, muted_user) do
2175 {:ok, [user_mute, user_notification_mute]}
2179 def set_invisible(user, invisible) do
2180 params = %{invisible: invisible}
2183 |> cast(params, [:invisible])
2184 |> validate_required([:invisible])
2185 |> update_and_set_cache()
2188 def sanitize_html(%User{} = user) do
2189 sanitize_html(user, nil)
2192 # User data that mastodon isn't filtering (treated as plaintext):
2195 def sanitize_html(%User{} = user, filter) do
2199 |> Enum.map(fn %{"name" => name, "value" => value} ->
2202 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2207 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2208 |> Map.put(:fields, fields)