1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.User do
10 import Ecto, only: [assoc: 2]
14 alias Pleroma.Activity
16 alias Pleroma.Conversation.Participation
17 alias Pleroma.Delivery
19 alias Pleroma.FollowingRelationship
20 alias Pleroma.Formatter
23 alias Pleroma.Notification
25 alias Pleroma.Registration
27 alias Pleroma.RepoStreamer
29 alias Pleroma.UserRelationship
31 alias Pleroma.Web.ActivityPub.ActivityPub
32 alias Pleroma.Web.ActivityPub.Builder
33 alias Pleroma.Web.ActivityPub.ObjectValidators.Types
34 alias Pleroma.Web.ActivityPub.Pipeline
35 alias Pleroma.Web.ActivityPub.Utils
36 alias Pleroma.Web.CommonAPI
37 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
38 alias Pleroma.Web.OAuth
39 alias Pleroma.Web.RelMe
40 alias Pleroma.Workers.BackgroundWorker
44 @type t :: %__MODULE__{}
45 @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
46 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
48 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
49 @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])?)*$/
51 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
52 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
54 # AP ID user relationships (blocks, mutes etc.)
55 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
56 @user_relationships_config [
58 blocker_blocks: :blocked_users,
59 blockee_blocks: :blocker_users
62 muter_mutes: :muted_users,
63 mutee_mutes: :muter_users
66 reblog_muter_mutes: :reblog_muted_users,
67 reblog_mutee_mutes: :reblog_muter_users
70 notification_muter_mutes: :notification_muted_users,
71 notification_mutee_mutes: :notification_muter_users
73 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
74 inverse_subscription: [
75 subscribee_subscriptions: :subscriber_users,
76 subscriber_subscriptions: :subscribee_users
82 field(:email, :string)
84 field(:nickname, :string)
85 field(:password_hash, :string)
86 field(:password, :string, virtual: true)
87 field(:password_confirmation, :string, virtual: true)
89 field(:public_key, :string)
90 field(:ap_id, :string)
92 field(:local, :boolean, default: true)
93 field(:follower_address, :string)
94 field(:following_address, :string)
95 field(:search_rank, :float, virtual: true)
96 field(:search_type, :integer, virtual: true)
97 field(:tags, {:array, :string}, default: [])
98 field(:last_refreshed_at, :naive_datetime_usec)
99 field(:last_digest_emailed_at, :naive_datetime)
100 field(:banner, :map, default: %{})
101 field(:background, :map, default: %{})
102 field(:note_count, :integer, default: 0)
103 field(:follower_count, :integer, default: 0)
104 field(:following_count, :integer, default: 0)
105 field(:locked, :boolean, default: false)
106 field(:confirmation_pending, :boolean, default: false)
107 field(:password_reset_pending, :boolean, default: false)
108 field(:confirmation_token, :string, default: nil)
109 field(:default_scope, :string, default: "public")
110 field(:domain_blocks, {:array, :string}, default: [])
111 field(:deactivated, :boolean, default: false)
112 field(:no_rich_text, :boolean, default: false)
113 field(:ap_enabled, :boolean, default: false)
114 field(:is_moderator, :boolean, default: false)
115 field(:is_admin, :boolean, default: false)
116 field(:show_role, :boolean, default: true)
117 field(:settings, :map, default: nil)
118 field(:magic_key, :string, default: nil)
119 field(:uri, Types.Uri, default: nil)
120 field(:hide_followers_count, :boolean, default: false)
121 field(:hide_follows_count, :boolean, default: false)
122 field(:hide_followers, :boolean, default: false)
123 field(:hide_follows, :boolean, default: false)
124 field(:hide_favorites, :boolean, default: true)
125 field(:unread_conversation_count, :integer, default: 0)
126 field(:pinned_activities, {:array, :string}, default: [])
127 field(:email_notifications, :map, default: %{"digest" => false})
128 field(:mascot, :map, default: nil)
129 field(:emoji, :map, default: %{})
130 field(:pleroma_settings_store, :map, default: %{})
131 field(:fields, {:array, :map}, default: [])
132 field(:raw_fields, {:array, :map}, default: [])
133 field(:discoverable, :boolean, default: false)
134 field(:invisible, :boolean, default: false)
135 field(:allow_following_move, :boolean, default: true)
136 field(:skip_thread_containment, :boolean, default: false)
137 field(:actor_type, :string, default: "Person")
138 field(:also_known_as, {:array, :string}, default: [])
139 field(:inbox, :string)
140 field(:shared_inbox, :string)
143 :notification_settings,
144 Pleroma.User.NotificationSetting,
148 has_many(:notifications, Notification)
149 has_many(:registrations, Registration)
150 has_many(:deliveries, Delivery)
152 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
153 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
155 for {relationship_type,
157 {outgoing_relation, outgoing_relation_target},
158 {incoming_relation, incoming_relation_source}
159 ]} <- @user_relationships_config do
160 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
161 # :notification_muter_mutes, :subscribee_subscriptions
162 has_many(outgoing_relation, UserRelationship,
163 foreign_key: :source_id,
164 where: [relationship_type: relationship_type]
167 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
168 # :notification_mutee_mutes, :subscriber_subscriptions
169 has_many(incoming_relation, UserRelationship,
170 foreign_key: :target_id,
171 where: [relationship_type: relationship_type]
174 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
175 # :notification_muted_users, :subscriber_users
176 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
178 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
179 # :notification_muter_users, :subscribee_users
180 has_many(incoming_relation_source, through: [incoming_relation, :source])
183 # `:blocks` is deprecated (replaced with `blocked_users` relation)
184 field(:blocks, {:array, :string}, default: [])
185 # `:mutes` is deprecated (replaced with `muted_users` relation)
186 field(:mutes, {:array, :string}, default: [])
187 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
188 field(:muted_reblogs, {:array, :string}, default: [])
189 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
190 field(:muted_notifications, {:array, :string}, default: [])
191 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
192 field(:subscribers, {:array, :string}, default: [])
197 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
198 @user_relationships_config do
199 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
200 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
201 # `def subscriber_users/2`
202 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
203 target_users_query = assoc(user, unquote(outgoing_relation_target))
205 if restrict_deactivated? do
206 restrict_deactivated(target_users_query)
212 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
213 # `def notification_muted_users/2`, `def subscriber_users/2`
214 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
216 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
218 restrict_deactivated?
223 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
224 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
225 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
227 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
229 restrict_deactivated?
231 |> select([u], u.ap_id)
237 Dumps Flake Id to SQL-compatible format (16-byte UUID).
238 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
240 def binary_id(source_id) when is_binary(source_id) do
241 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
248 def binary_id(source_ids) when is_list(source_ids) do
249 Enum.map(source_ids, &binary_id/1)
252 def binary_id(%User{} = user), do: binary_id(user.id)
254 @doc "Returns status account"
255 @spec account_status(User.t()) :: account_status()
256 def account_status(%User{deactivated: true}), do: :deactivated
257 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
259 def account_status(%User{confirmation_pending: true}) do
260 case Config.get([:instance, :account_activation_required]) do
261 true -> :confirmation_pending
266 def account_status(%User{}), do: :active
268 @spec visible_for?(User.t(), User.t() | nil) :: boolean()
269 def visible_for?(user, for_user \\ nil)
271 def visible_for?(%User{invisible: true}, _), do: false
273 def visible_for?(%User{id: user_id}, %User{id: user_id}), do: true
275 def visible_for?(%User{local: local} = user, nil) do
281 if Config.get([:restrict_unauthenticated, :profiles, cfg_key]),
283 else: account_status(user) == :active
286 def visible_for?(%User{} = user, for_user) do
287 account_status(user) == :active || superuser?(for_user)
290 def visible_for?(_, _), do: false
292 @spec superuser?(User.t()) :: boolean()
293 def superuser?(%User{local: true, is_admin: true}), do: true
294 def superuser?(%User{local: true, is_moderator: true}), do: true
295 def superuser?(_), do: false
297 @spec invisible?(User.t()) :: boolean()
298 def invisible?(%User{invisible: true}), do: true
299 def invisible?(_), do: false
301 def avatar_url(user, options \\ []) do
303 %{"url" => [%{"href" => href} | _]} -> href
304 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
308 def banner_url(user, options \\ []) do
310 %{"url" => [%{"href" => href} | _]} -> href
311 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
315 # Should probably be renamed or removed
316 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
318 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
319 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
321 @spec ap_following(User.t()) :: String.t()
322 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
323 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
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 defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
351 defp fix_follower_address(%{nickname: nickname} = params),
352 do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
354 defp fix_follower_address(params), do: params
356 def remote_user_changeset(struct \\ %User{local: false}, params) do
357 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
358 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
361 case params[:name] do
362 name when is_binary(name) and byte_size(name) > 0 -> name
363 _ -> params[:nickname]
368 |> Map.put(:name, name)
369 |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
370 |> truncate_if_exists(:name, name_limit)
371 |> truncate_if_exists(:bio, bio_limit)
372 |> truncate_fields_param()
373 |> fix_follower_address()
398 :hide_followers_count,
409 |> validate_required([:name, :ap_id])
410 |> unique_constraint(:nickname)
411 |> validate_format(:nickname, @email_regex)
412 |> validate_length(:bio, max: bio_limit)
413 |> validate_length(:name, max: name_limit)
414 |> validate_fields(true)
417 def update_changeset(struct, params \\ %{}) do
418 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
419 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
438 :hide_followers_count,
441 :allow_following_move,
444 :skip_thread_containment,
447 :pleroma_settings_store,
453 |> unique_constraint(:nickname)
454 |> validate_format(:nickname, local_nickname_regex())
455 |> validate_length(:bio, max: bio_limit)
456 |> validate_length(:name, min: 1, max: name_limit)
459 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
460 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
461 |> put_change_if_present(:banner, &put_upload(&1, :banner))
462 |> put_change_if_present(:background, &put_upload(&1, :background))
463 |> put_change_if_present(
464 :pleroma_settings_store,
465 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
467 |> validate_fields(false)
470 defp put_fields(changeset) do
471 if raw_fields = get_change(changeset, :raw_fields) do
474 |> Enum.filter(fn %{"name" => n} -> n != "" end)
478 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
481 |> put_change(:raw_fields, raw_fields)
482 |> put_change(:fields, fields)
488 defp parse_fields(value) do
490 |> Formatter.linkify(mentions_format: :full)
494 defp put_emoji(changeset) do
495 bio = get_change(changeset, :bio)
496 name = get_change(changeset, :name)
499 emoji = Map.merge(Emoji.Formatter.get_emoji_map(bio), Emoji.Formatter.get_emoji_map(name))
500 put_change(changeset, :emoji, emoji)
506 defp put_change_if_present(changeset, map_field, value_function) do
507 if value = get_change(changeset, map_field) do
508 with {:ok, new_value} <- value_function.(value) do
509 put_change(changeset, map_field, new_value)
518 defp put_upload(value, type) do
519 with %Plug.Upload{} <- value,
520 {:ok, object} <- ActivityPub.upload(value, type: type) do
525 def update_as_admin_changeset(struct, params) do
527 |> update_changeset(params)
528 |> cast(params, [:email])
529 |> delete_change(:also_known_as)
530 |> unique_constraint(:email)
531 |> validate_format(:email, @email_regex)
534 @spec update_as_admin(%User{}, map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
535 def update_as_admin(user, params) do
536 params = Map.put(params, "password_confirmation", params["password"])
537 changeset = update_as_admin_changeset(user, params)
539 if params["password"] do
540 reset_password(user, changeset, params)
542 User.update_and_set_cache(changeset)
546 def password_update_changeset(struct, params) do
548 |> cast(params, [:password, :password_confirmation])
549 |> validate_required([:password, :password_confirmation])
550 |> validate_confirmation(:password)
551 |> put_password_hash()
552 |> put_change(:password_reset_pending, false)
555 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
556 def reset_password(%User{} = user, params) do
557 reset_password(user, user, params)
560 def reset_password(%User{id: user_id} = user, struct, params) do
563 |> Multi.update(:user, password_update_changeset(struct, params))
564 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
565 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
567 case Repo.transaction(multi) do
568 {:ok, %{user: user} = _} -> set_cache(user)
569 {:error, _, changeset, _} -> {:error, changeset}
573 def update_password_reset_pending(user, value) do
576 |> put_change(:password_reset_pending, value)
577 |> update_and_set_cache()
580 def force_password_reset_async(user) do
581 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
584 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
585 def force_password_reset(user), do: update_password_reset_pending(user, true)
587 def register_changeset(struct, params \\ %{}, opts \\ []) do
588 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
589 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
592 if is_nil(opts[:need_confirmation]) do
593 Pleroma.Config.get([:instance, :account_activation_required])
595 opts[:need_confirmation]
599 |> confirmation_changeset(need_confirmation: need_confirmation?)
600 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation, :emoji])
601 |> validate_required([:name, :nickname, :password, :password_confirmation])
602 |> validate_confirmation(:password)
603 |> unique_constraint(:email)
604 |> unique_constraint(:nickname)
605 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
606 |> validate_format(:nickname, local_nickname_regex())
607 |> validate_format(:email, @email_regex)
608 |> validate_length(:bio, max: bio_limit)
609 |> validate_length(:name, min: 1, max: name_limit)
610 |> maybe_validate_required_email(opts[:external])
613 |> unique_constraint(:ap_id)
614 |> put_following_and_follower_address()
617 def maybe_validate_required_email(changeset, true), do: changeset
619 def maybe_validate_required_email(changeset, _) do
620 if Pleroma.Config.get([:instance, :account_activation_required]) do
621 validate_required(changeset, [:email])
627 defp put_ap_id(changeset) do
628 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
629 put_change(changeset, :ap_id, ap_id)
632 defp put_following_and_follower_address(changeset) do
633 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
636 |> put_change(:follower_address, followers)
639 defp autofollow_users(user) do
640 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
643 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
646 follow_all(user, autofollowed_users)
649 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
650 def register(%Ecto.Changeset{} = changeset) do
651 with {:ok, user} <- Repo.insert(changeset) do
652 post_register_action(user)
656 def post_register_action(%User{} = user) do
657 with {:ok, user} <- autofollow_users(user),
658 {:ok, user} <- set_cache(user),
659 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
660 {:ok, _} <- try_send_confirmation_email(user) do
665 def try_send_confirmation_email(%User{} = user) do
666 if user.confirmation_pending &&
667 Pleroma.Config.get([:instance, :account_activation_required]) do
669 |> Pleroma.Emails.UserEmail.account_confirmation_email()
670 |> Pleroma.Emails.Mailer.deliver_async()
678 def try_send_confirmation_email(users) do
679 Enum.each(users, &try_send_confirmation_email/1)
682 def needs_update?(%User{local: true}), do: false
684 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
686 def needs_update?(%User{local: false} = user) do
687 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
690 def needs_update?(_), do: true
692 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
694 # "Locked" (self-locked) users demand explicit authorization of follow requests
695 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
696 follow(follower, followed, :follow_pending)
699 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
700 follow(follower, followed)
703 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
704 if not ap_enabled?(followed) do
705 follow(follower, followed)
711 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
712 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
713 def follow_all(follower, followeds) do
715 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
716 |> Enum.each(&follow(follower, &1, :follow_accept))
721 defdelegate following(user), to: FollowingRelationship
723 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
724 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
727 followed.deactivated ->
728 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
730 deny_follow_blocked and blocks?(followed, follower) ->
731 {:error, "Could not follow user: #{followed.nickname} blocked you."}
734 FollowingRelationship.follow(follower, followed, state)
736 {:ok, _} = update_follower_count(followed)
739 |> update_following_count()
744 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
745 {:error, "Not subscribed!"}
748 def unfollow(%User{} = follower, %User{} = followed) do
749 case get_follow_state(follower, followed) do
750 state when state in [:follow_pending, :follow_accept] ->
751 FollowingRelationship.unfollow(follower, followed)
752 {:ok, followed} = update_follower_count(followed)
756 |> update_following_count()
759 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
762 {:error, "Not subscribed!"}
766 defdelegate following?(follower, followed), to: FollowingRelationship
768 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
769 def get_follow_state(%User{} = follower, %User{} = following) do
770 following_relationship = FollowingRelationship.get(follower, following)
771 get_follow_state(follower, following, following_relationship)
774 def get_follow_state(
777 following_relationship
779 case {following_relationship, following.local} do
781 case Utils.fetch_latest_follow(follower, following) do
782 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
783 FollowingRelationship.state_to_enum(state)
789 {%{state: state}, _} ->
797 def locked?(%User{} = user) do
802 Repo.get_by(User, id: id)
805 def get_by_ap_id(ap_id) do
806 Repo.get_by(User, ap_id: ap_id)
809 def get_all_by_ap_id(ap_ids) do
810 from(u in __MODULE__,
811 where: u.ap_id in ^ap_ids
816 def get_all_by_ids(ids) do
817 from(u in __MODULE__, where: u.id in ^ids)
821 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
822 # of the ap_id and the domain and tries to get that user
823 def get_by_guessed_nickname(ap_id) do
824 domain = URI.parse(ap_id).host
825 name = List.last(String.split(ap_id, "/"))
826 nickname = "#{name}@#{domain}"
828 get_cached_by_nickname(nickname)
831 def set_cache({:ok, user}), do: set_cache(user)
832 def set_cache({:error, err}), do: {:error, err}
834 def set_cache(%User{} = user) do
835 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
836 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
837 Cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
841 def update_and_set_cache(struct, params) do
843 |> update_changeset(params)
844 |> update_and_set_cache()
847 def update_and_set_cache(changeset) do
848 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
853 def get_user_friends_ap_ids(user) do
854 from(u in User.get_friends_query(user), select: u.ap_id)
858 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
859 def get_cached_user_friends_ap_ids(user) do
860 Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
861 get_user_friends_ap_ids(user)
865 def invalidate_cache(user) do
866 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
867 Cachex.del(:user_cache, "nickname:#{user.nickname}")
868 Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
871 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
872 def get_cached_by_ap_id(ap_id) do
873 key = "ap_id:#{ap_id}"
875 with {:ok, nil} <- Cachex.get(:user_cache, key),
876 user when not is_nil(user) <- get_by_ap_id(ap_id),
877 {:ok, true} <- Cachex.put(:user_cache, key, user) do
885 def get_cached_by_id(id) do
889 Cachex.fetch!(:user_cache, key, fn _ ->
893 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
894 {:commit, user.ap_id}
900 get_cached_by_ap_id(ap_id)
903 def get_cached_by_nickname(nickname) do
904 key = "nickname:#{nickname}"
906 Cachex.fetch!(:user_cache, key, fn ->
907 case get_or_fetch_by_nickname(nickname) do
908 {:ok, user} -> {:commit, user}
909 {:error, _error} -> {:ignore, nil}
914 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
915 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
918 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
919 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
921 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
922 get_cached_by_nickname(nickname_or_id)
924 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
925 get_cached_by_nickname(nickname_or_id)
932 def get_by_nickname(nickname) do
933 Repo.get_by(User, nickname: nickname) ||
934 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
935 Repo.get_by(User, nickname: local_nickname(nickname))
939 def get_by_email(email), do: Repo.get_by(User, email: email)
941 def get_by_nickname_or_email(nickname_or_email) do
942 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
945 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
947 def get_or_fetch_by_nickname(nickname) do
948 with %User{} = user <- get_by_nickname(nickname) do
952 with [_nick, _domain] <- String.split(nickname, "@"),
953 {:ok, user} <- fetch_by_nickname(nickname) do
956 _e -> {:error, "not found " <> nickname}
961 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
962 def get_followers_query(%User{} = user, nil) do
963 User.Query.build(%{followers: user, deactivated: false})
966 def get_followers_query(user, page) do
968 |> get_followers_query(nil)
969 |> User.Query.paginate(page, 20)
972 @spec get_followers_query(User.t()) :: Ecto.Query.t()
973 def get_followers_query(user), do: get_followers_query(user, nil)
975 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
976 def get_followers(user, page \\ nil) do
978 |> get_followers_query(page)
982 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
983 def get_external_followers(user, page \\ nil) do
985 |> get_followers_query(page)
986 |> User.Query.build(%{external: true})
990 def get_followers_ids(user, page \\ nil) do
992 |> get_followers_query(page)
997 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
998 def get_friends_query(%User{} = user, nil) do
999 User.Query.build(%{friends: user, deactivated: false})
1002 def get_friends_query(user, page) do
1004 |> get_friends_query(nil)
1005 |> User.Query.paginate(page, 20)
1008 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1009 def get_friends_query(user), do: get_friends_query(user, nil)
1011 def get_friends(user, page \\ nil) do
1013 |> get_friends_query(page)
1017 def get_friends_ap_ids(user) do
1019 |> get_friends_query(nil)
1020 |> select([u], u.ap_id)
1024 def get_friends_ids(user, page \\ nil) do
1026 |> get_friends_query(page)
1027 |> select([u], u.id)
1031 defdelegate get_follow_requests(user), to: FollowingRelationship
1033 def increase_note_count(%User{} = user) do
1035 |> where(id: ^user.id)
1036 |> update([u], inc: [note_count: 1])
1038 |> Repo.update_all([])
1040 {1, [user]} -> set_cache(user)
1045 def decrease_note_count(%User{} = user) do
1047 |> where(id: ^user.id)
1050 note_count: fragment("greatest(0, note_count - 1)")
1054 |> Repo.update_all([])
1056 {1, [user]} -> set_cache(user)
1061 def update_note_count(%User{} = user, note_count \\ nil) do
1066 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1072 |> cast(%{note_count: note_count}, [:note_count])
1073 |> update_and_set_cache()
1076 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1077 def maybe_fetch_follow_information(user) do
1078 with {:ok, user} <- fetch_follow_information(user) do
1082 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1088 def fetch_follow_information(user) do
1089 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1091 |> follow_information_changeset(info)
1092 |> update_and_set_cache()
1096 defp follow_information_changeset(user, params) do
1103 :hide_followers_count,
1108 def update_follower_count(%User{} = user) do
1109 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
1110 follower_count_query =
1111 User.Query.build(%{followers: user, deactivated: false})
1112 |> select([u], %{count: count(u.id)})
1115 |> where(id: ^user.id)
1116 |> join(:inner, [u], s in subquery(follower_count_query))
1118 set: [follower_count: s.count]
1121 |> Repo.update_all([])
1123 {1, [user]} -> set_cache(user)
1127 {:ok, maybe_fetch_follow_information(user)}
1131 @spec update_following_count(User.t()) :: User.t()
1132 def update_following_count(%User{local: false} = user) do
1133 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1134 maybe_fetch_follow_information(user)
1140 def update_following_count(%User{local: true} = user) do
1141 following_count = FollowingRelationship.following_count(user)
1144 |> follow_information_changeset(%{following_count: following_count})
1148 def set_unread_conversation_count(%User{local: true} = user) do
1149 unread_query = Participation.unread_conversation_count_for_user(user)
1152 |> join(:inner, [u], p in subquery(unread_query))
1154 set: [unread_conversation_count: p.count]
1156 |> where([u], u.id == ^user.id)
1158 |> Repo.update_all([])
1160 {1, [user]} -> set_cache(user)
1165 def set_unread_conversation_count(user), do: {:ok, user}
1167 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1169 Participation.unread_conversation_count_for_user(user)
1170 |> where([p], p.conversation_id == ^conversation.id)
1173 |> join(:inner, [u], p in subquery(unread_query))
1175 inc: [unread_conversation_count: 1]
1177 |> where([u], u.id == ^user.id)
1178 |> where([u, p], p.count == 0)
1180 |> Repo.update_all([])
1182 {1, [user]} -> set_cache(user)
1187 def increment_unread_conversation_count(_, user), do: {:ok, user}
1189 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1190 def get_users_from_set(ap_ids, local_only \\ true) do
1191 criteria = %{ap_id: ap_ids, deactivated: false}
1192 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1194 User.Query.build(criteria)
1198 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1199 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1202 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1206 @spec mute(User.t(), User.t(), boolean()) ::
1207 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1208 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1209 add_to_mutes(muter, mutee, notifications?)
1212 def unmute(%User{} = muter, %User{} = mutee) do
1213 remove_from_mutes(muter, mutee)
1216 def subscribe(%User{} = subscriber, %User{} = target) do
1217 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1219 if blocks?(target, subscriber) and deny_follow_blocked do
1220 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1222 # Note: the relationship is inverse: subscriber acts as relationship target
1223 UserRelationship.create_inverse_subscription(target, subscriber)
1227 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1228 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1229 subscribe(subscriber, subscribee)
1233 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1234 # Note: the relationship is inverse: subscriber acts as relationship target
1235 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1238 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1239 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1240 unsubscribe(unsubscriber, user)
1244 def block(%User{} = blocker, %User{} = blocked) do
1245 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1247 if following?(blocker, blocked) do
1248 {:ok, blocker, _} = unfollow(blocker, blocked)
1254 # clear any requested follows as well
1256 case CommonAPI.reject_follow_request(blocked, blocker) do
1257 {:ok, %User{} = updated_blocked} -> updated_blocked
1261 unsubscribe(blocked, blocker)
1263 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1265 {:ok, blocker} = update_follower_count(blocker)
1266 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1267 add_to_block(blocker, blocked)
1270 # helper to handle the block given only an actor's AP id
1271 def block(%User{} = blocker, %{ap_id: ap_id}) do
1272 block(blocker, get_cached_by_ap_id(ap_id))
1275 def unblock(%User{} = blocker, %User{} = blocked) do
1276 remove_from_block(blocker, blocked)
1279 # helper to handle the block given only an actor's AP id
1280 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1281 unblock(blocker, get_cached_by_ap_id(ap_id))
1284 def mutes?(nil, _), do: false
1285 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1287 def mutes_user?(%User{} = user, %User{} = target) do
1288 UserRelationship.mute_exists?(user, target)
1291 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1292 def muted_notifications?(nil, _), do: false
1294 def muted_notifications?(%User{} = user, %User{} = target),
1295 do: UserRelationship.notification_mute_exists?(user, target)
1297 def blocks?(nil, _), do: false
1299 def blocks?(%User{} = user, %User{} = target) do
1300 blocks_user?(user, target) ||
1301 (blocks_domain?(user, target) and not User.following?(user, target))
1304 def blocks_user?(%User{} = user, %User{} = target) do
1305 UserRelationship.block_exists?(user, target)
1308 def blocks_user?(_, _), do: false
1310 def blocks_domain?(%User{} = user, %User{} = target) do
1311 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1312 %{host: host} = URI.parse(target.ap_id)
1313 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1316 def blocks_domain?(_, _), do: false
1318 def subscribed_to?(%User{} = user, %User{} = target) do
1319 # Note: the relationship is inverse: subscriber acts as relationship target
1320 UserRelationship.inverse_subscription_exists?(target, user)
1323 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1324 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1325 subscribed_to?(user, target)
1330 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1331 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1333 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1334 def outgoing_relationships_ap_ids(_user, []), do: %{}
1336 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1338 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1339 when is_list(relationship_types) do
1342 |> assoc(:outgoing_relationships)
1343 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1344 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1345 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1346 |> group_by([user_rel, u], user_rel.relationship_type)
1348 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1353 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1357 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1359 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1361 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1363 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1364 when is_list(relationship_types) do
1366 |> assoc(:incoming_relationships)
1367 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1368 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1369 |> maybe_filter_on_ap_id(ap_ids)
1370 |> select([user_rel, u], u.ap_id)
1375 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1376 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1379 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1381 def deactivate_async(user, status \\ true) do
1382 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1385 def deactivate(user, status \\ true)
1387 def deactivate(users, status) when is_list(users) do
1388 Repo.transaction(fn ->
1389 for user <- users, do: deactivate(user, status)
1393 def deactivate(%User{} = user, status) do
1394 with {:ok, user} <- set_activation_status(user, status) do
1397 |> Enum.filter(& &1.local)
1398 |> Enum.each(fn follower ->
1399 follower |> update_following_count() |> set_cache()
1402 # Only update local user counts, remote will be update during the next pull.
1405 |> Enum.filter(& &1.local)
1406 |> Enum.each(&update_follower_count/1)
1412 def update_notification_settings(%User{} = user, settings) do
1414 |> cast(%{notification_settings: settings}, [])
1415 |> cast_embed(:notification_settings)
1416 |> validate_required([:notification_settings])
1417 |> update_and_set_cache()
1420 def delete(users) when is_list(users) do
1421 for user <- users, do: delete(user)
1424 def delete(%User{} = user) do
1425 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1428 def perform(:force_password_reset, user), do: force_password_reset(user)
1430 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1431 def perform(:delete, %User{} = user) do
1432 # Remove all relationships
1435 |> Enum.each(fn follower ->
1436 ActivityPub.unfollow(follower, user)
1437 unfollow(follower, user)
1442 |> Enum.each(fn followed ->
1443 ActivityPub.unfollow(user, followed)
1444 unfollow(user, followed)
1447 delete_user_activities(user)
1448 invalidate_cache(user)
1452 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1454 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1455 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1456 when is_list(blocked_identifiers) do
1458 blocked_identifiers,
1459 fn blocked_identifier ->
1460 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1461 {:ok, _user_block} <- block(blocker, blocked),
1462 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1466 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1473 def perform(:follow_import, %User{} = follower, followed_identifiers)
1474 when is_list(followed_identifiers) do
1476 followed_identifiers,
1477 fn followed_identifier ->
1478 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1479 {:ok, follower} <- maybe_direct_follow(follower, followed),
1480 {:ok, _} <- ActivityPub.follow(follower, followed) do
1484 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1491 @spec external_users_query() :: Ecto.Query.t()
1492 def external_users_query do
1500 @spec external_users(keyword()) :: [User.t()]
1501 def external_users(opts \\ []) do
1503 external_users_query()
1504 |> select([u], struct(u, [:id, :ap_id]))
1508 do: where(query, [u], u.id > ^opts[:max_id]),
1513 do: limit(query, ^opts[:limit]),
1519 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1520 BackgroundWorker.enqueue("blocks_import", %{
1521 "blocker_id" => blocker.id,
1522 "blocked_identifiers" => blocked_identifiers
1526 def follow_import(%User{} = follower, followed_identifiers)
1527 when is_list(followed_identifiers) do
1528 BackgroundWorker.enqueue("follow_import", %{
1529 "follower_id" => follower.id,
1530 "followed_identifiers" => followed_identifiers
1534 def delete_user_activities(%User{ap_id: ap_id} = user) do
1536 |> Activity.Queries.by_actor()
1537 |> RepoStreamer.chunk_stream(50)
1538 |> Stream.each(fn activities ->
1539 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1544 defp delete_activity(%{data: %{"type" => "Create", "object" => object}}, user) do
1545 {:ok, delete_data, _} = Builder.delete(user, object)
1547 Pipeline.common_pipeline(delete_data, local: true)
1550 defp delete_activity(%{data: %{"type" => "Like"}} = activity, _user) do
1551 object = Object.normalize(activity)
1554 |> get_cached_by_ap_id()
1555 |> ActivityPub.unlike(object)
1558 defp delete_activity(%{data: %{"type" => "Announce"}} = activity, _user) do
1559 object = Object.normalize(activity)
1562 |> get_cached_by_ap_id()
1563 |> ActivityPub.unannounce(object)
1566 defp delete_activity(_activity, _user), do: "Doing nothing"
1568 def html_filter_policy(%User{no_rich_text: true}) do
1569 Pleroma.HTML.Scrubber.TwitterText
1572 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1574 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1576 def get_or_fetch_by_ap_id(ap_id) do
1577 user = get_cached_by_ap_id(ap_id)
1579 if !is_nil(user) and !needs_update?(user) do
1582 fetch_by_ap_id(ap_id)
1587 Creates an internal service actor by URI if missing.
1588 Optionally takes nickname for addressing.
1590 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1591 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1593 case get_cached_by_ap_id(uri) do
1595 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1596 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1600 %User{invisible: false} = user ->
1610 @spec set_invisible(User.t()) :: {:ok, User.t()}
1611 defp set_invisible(user) do
1613 |> change(%{invisible: true})
1614 |> update_and_set_cache()
1617 @spec create_service_actor(String.t(), String.t()) ::
1618 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1619 defp create_service_actor(uri, nickname) do
1625 follower_address: uri <> "/followers"
1628 |> unique_constraint(:nickname)
1633 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1636 |> :public_key.pem_decode()
1638 |> :public_key.pem_entry_decode()
1643 def public_key(_), do: {:error, "key not found"}
1645 def get_public_key_for_ap_id(ap_id) do
1646 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1647 {:ok, public_key} <- public_key(user) do
1654 def ap_enabled?(%User{local: true}), do: true
1655 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1656 def ap_enabled?(_), do: false
1658 @doc "Gets or fetch a user by uri or nickname."
1659 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1660 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1661 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1663 # wait a period of time and return newest version of the User structs
1664 # this is because we have synchronous follow APIs and need to simulate them
1665 # with an async handshake
1666 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1667 with %User{} = a <- get_cached_by_id(a.id),
1668 %User{} = b <- get_cached_by_id(b.id) do
1675 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1676 with :ok <- :timer.sleep(timeout),
1677 %User{} = a <- get_cached_by_id(a.id),
1678 %User{} = b <- get_cached_by_id(b.id) do
1685 def parse_bio(bio) when is_binary(bio) and bio != "" do
1687 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1691 def parse_bio(_), do: ""
1693 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1694 # TODO: get profile URLs other than user.ap_id
1695 profile_urls = [user.ap_id]
1698 |> CommonUtils.format_input("text/plain",
1699 mentions_format: :full,
1700 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1705 def parse_bio(_, _), do: ""
1707 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1708 Repo.transaction(fn ->
1709 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1713 def tag(nickname, tags) when is_binary(nickname),
1714 do: tag(get_by_nickname(nickname), tags)
1716 def tag(%User{} = user, tags),
1717 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1719 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1720 Repo.transaction(fn ->
1721 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1725 def untag(nickname, tags) when is_binary(nickname),
1726 do: untag(get_by_nickname(nickname), tags)
1728 def untag(%User{} = user, tags),
1729 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1731 defp update_tags(%User{} = user, new_tags) do
1732 {:ok, updated_user} =
1734 |> change(%{tags: new_tags})
1735 |> update_and_set_cache()
1740 defp normalize_tags(tags) do
1743 |> Enum.map(&String.downcase/1)
1746 defp local_nickname_regex do
1747 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1748 @extended_local_nickname_regex
1750 @strict_local_nickname_regex
1754 def local_nickname(nickname_or_mention) do
1757 |> String.split("@")
1761 def full_nickname(nickname_or_mention),
1762 do: String.trim_leading(nickname_or_mention, "@")
1764 def error_user(ap_id) do
1768 nickname: "erroruser@example.com",
1769 inserted_at: NaiveDateTime.utc_now()
1773 @spec all_superusers() :: [User.t()]
1774 def all_superusers do
1775 User.Query.build(%{super_users: true, local: true, deactivated: false})
1779 def muting_reblogs?(%User{} = user, %User{} = target) do
1780 UserRelationship.reblog_mute_exists?(user, target)
1783 def showing_reblogs?(%User{} = user, %User{} = target) do
1784 not muting_reblogs?(user, target)
1788 The function returns a query to get users with no activity for given interval of days.
1789 Inactive users are those who didn't read any notification, or had any activity where
1790 the user is the activity's actor, during `inactivity_threshold` days.
1791 Deactivated users will not appear in this list.
1795 iex> Pleroma.User.list_inactive_users()
1798 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1799 def list_inactive_users_query(inactivity_threshold \\ 7) do
1800 negative_inactivity_threshold = -inactivity_threshold
1801 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1802 # Subqueries are not supported in `where` clauses, join gets too complicated.
1803 has_read_notifications =
1804 from(n in Pleroma.Notification,
1805 where: n.seen == true,
1807 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1810 |> Pleroma.Repo.all()
1812 from(u in Pleroma.User,
1813 left_join: a in Pleroma.Activity,
1814 on: u.ap_id == a.actor,
1815 where: not is_nil(u.nickname),
1816 where: u.deactivated != ^true,
1817 where: u.id not in ^has_read_notifications,
1820 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1821 is_nil(max(a.inserted_at))
1826 Enable or disable email notifications for user
1830 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1831 Pleroma.User{email_notifications: %{"digest" => true}}
1833 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1834 Pleroma.User{email_notifications: %{"digest" => false}}
1836 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1837 {:ok, t()} | {:error, Ecto.Changeset.t()}
1838 def switch_email_notifications(user, type, status) do
1839 User.update_email_notifications(user, %{type => status})
1843 Set `last_digest_emailed_at` value for the user to current time
1845 @spec touch_last_digest_emailed_at(t()) :: t()
1846 def touch_last_digest_emailed_at(user) do
1847 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1849 {:ok, updated_user} =
1851 |> change(%{last_digest_emailed_at: now})
1852 |> update_and_set_cache()
1857 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1858 def toggle_confirmation(%User{} = user) do
1860 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1861 |> update_and_set_cache()
1864 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1865 def toggle_confirmation(users) do
1866 Enum.map(users, &toggle_confirmation/1)
1869 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1873 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1874 # use instance-default
1875 config = Pleroma.Config.get([:assets, :mascots])
1876 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1877 mascot = Keyword.get(config, default_mascot)
1880 "id" => "default-mascot",
1881 "url" => mascot[:url],
1882 "preview_url" => mascot[:url],
1884 "mime_type" => mascot[:mime_type]
1889 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1891 def ensure_keys_present(%User{} = user) do
1892 with {:ok, pem} <- Keys.generate_rsa_pem() do
1894 |> cast(%{keys: pem}, [:keys])
1895 |> validate_required([:keys])
1896 |> update_and_set_cache()
1900 def get_ap_ids_by_nicknames(nicknames) do
1902 where: u.nickname in ^nicknames,
1908 defdelegate search(query, opts \\ []), to: User.Search
1910 defp put_password_hash(
1911 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1913 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1916 defp put_password_hash(changeset), do: changeset
1918 def is_internal_user?(%User{nickname: nil}), do: true
1919 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1920 def is_internal_user?(_), do: false
1922 # A hack because user delete activities have a fake id for whatever reason
1923 # TODO: Get rid of this
1924 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1926 def get_delivered_users_by_object_id(object_id) do
1928 inner_join: delivery in assoc(u, :deliveries),
1929 where: delivery.object_id == ^object_id
1934 def change_email(user, email) do
1936 |> cast(%{email: email}, [:email])
1937 |> validate_required([:email])
1938 |> unique_constraint(:email)
1939 |> validate_format(:email, @email_regex)
1940 |> update_and_set_cache()
1943 # Internal function; public one is `deactivate/2`
1944 defp set_activation_status(user, deactivated) do
1946 |> cast(%{deactivated: deactivated}, [:deactivated])
1947 |> update_and_set_cache()
1950 def update_banner(user, banner) do
1952 |> cast(%{banner: banner}, [:banner])
1953 |> update_and_set_cache()
1956 def update_background(user, background) do
1958 |> cast(%{background: background}, [:background])
1959 |> update_and_set_cache()
1962 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1965 moderator: is_moderator
1969 def validate_fields(changeset, remote? \\ false) do
1970 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1971 limit = Pleroma.Config.get([:instance, limit_name], 0)
1974 |> validate_length(:fields, max: limit)
1975 |> validate_change(:fields, fn :fields, fields ->
1976 if Enum.all?(fields, &valid_field?/1) do
1984 defp valid_field?(%{"name" => name, "value" => value}) do
1985 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1986 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1988 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1989 String.length(value) <= value_limit
1992 defp valid_field?(_), do: false
1994 defp truncate_field(%{"name" => name, "value" => value}) do
1996 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1999 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
2001 %{"name" => name, "value" => value}
2004 def admin_api_update(user, params) do
2011 |> update_and_set_cache()
2014 @doc "Signs user out of all applications"
2015 def global_sign_out(user) do
2016 OAuth.Authorization.delete_user_authorizations(user)
2017 OAuth.Token.delete_user_tokens(user)
2020 def mascot_update(user, url) do
2022 |> cast(%{mascot: url}, [:mascot])
2023 |> validate_required([:mascot])
2024 |> update_and_set_cache()
2027 def mastodon_settings_update(user, settings) do
2029 |> cast(%{settings: settings}, [:settings])
2030 |> validate_required([:settings])
2031 |> update_and_set_cache()
2034 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2035 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
2037 if need_confirmation? do
2039 confirmation_pending: true,
2040 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2044 confirmation_pending: false,
2045 confirmation_token: nil
2049 cast(user, params, [:confirmation_pending, :confirmation_token])
2052 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2053 if id not in user.pinned_activities do
2054 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
2055 params = %{pinned_activities: user.pinned_activities ++ [id]}
2058 |> cast(params, [:pinned_activities])
2059 |> validate_length(:pinned_activities,
2060 max: max_pinned_statuses,
2061 message: "You have already pinned the maximum number of statuses"
2066 |> update_and_set_cache()
2069 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
2070 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
2073 |> cast(params, [:pinned_activities])
2074 |> update_and_set_cache()
2077 def update_email_notifications(user, settings) do
2078 email_notifications =
2079 user.email_notifications
2080 |> Map.merge(settings)
2081 |> Map.take(["digest"])
2083 params = %{email_notifications: email_notifications}
2084 fields = [:email_notifications]
2087 |> cast(params, fields)
2088 |> validate_required(fields)
2089 |> update_and_set_cache()
2092 defp set_domain_blocks(user, domain_blocks) do
2093 params = %{domain_blocks: domain_blocks}
2096 |> cast(params, [:domain_blocks])
2097 |> validate_required([:domain_blocks])
2098 |> update_and_set_cache()
2101 def block_domain(user, domain_blocked) do
2102 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2105 def unblock_domain(user, domain_blocked) do
2106 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2109 @spec add_to_block(User.t(), User.t()) ::
2110 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2111 defp add_to_block(%User{} = user, %User{} = blocked) do
2112 UserRelationship.create_block(user, blocked)
2115 @spec add_to_block(User.t(), User.t()) ::
2116 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2117 defp remove_from_block(%User{} = user, %User{} = blocked) do
2118 UserRelationship.delete_block(user, blocked)
2121 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2122 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2123 {:ok, user_notification_mute} <-
2124 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2126 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2130 defp remove_from_mutes(user, %User{} = muted_user) do
2131 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2132 {:ok, user_notification_mute} <-
2133 UserRelationship.delete_notification_mute(user, muted_user) do
2134 {:ok, [user_mute, user_notification_mute]}
2138 def set_invisible(user, invisible) do
2139 params = %{invisible: invisible}
2142 |> cast(params, [:invisible])
2143 |> validate_required([:invisible])
2144 |> update_and_set_cache()
2147 def sanitize_html(%User{} = user) do
2148 sanitize_html(user, nil)
2151 # User data that mastodon isn't filtering (treated as plaintext):
2154 def sanitize_html(%User{} = user, filter) do
2156 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2159 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2164 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2165 |> Map.put(:fields, fields)