1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 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
15 alias Pleroma.Conversation.Participation
16 alias Pleroma.Delivery
17 alias Pleroma.FollowingRelationship
19 alias Pleroma.Notification
21 alias Pleroma.Registration
23 alias Pleroma.RepoStreamer
25 alias Pleroma.UserRelationship
27 alias Pleroma.Web.ActivityPub.ActivityPub
28 alias Pleroma.Web.ActivityPub.Utils
29 alias Pleroma.Web.CommonAPI
30 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
31 alias Pleroma.Web.OAuth
32 alias Pleroma.Web.RelMe
33 alias Pleroma.Workers.BackgroundWorker
37 @type t :: %__MODULE__{}
39 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
41 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
42 @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])?)*$/
44 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
45 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
47 # AP ID user relationships (blocks, mutes etc.)
48 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
49 @user_relationships_config [
51 blocker_blocks: :blocked_users,
52 blockee_blocks: :blocker_users
55 muter_mutes: :muted_users,
56 mutee_mutes: :muter_users
59 reblog_muter_mutes: :reblog_muted_users,
60 reblog_mutee_mutes: :reblog_muter_users
63 notification_muter_mutes: :notification_muted_users,
64 notification_mutee_mutes: :notification_muter_users
66 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
67 inverse_subscription: [
68 subscribee_subscriptions: :subscriber_users,
69 subscriber_subscriptions: :subscribee_users
75 field(:email, :string)
77 field(:nickname, :string)
78 field(:password_hash, :string)
79 field(:password, :string, virtual: true)
80 field(:password_confirmation, :string, virtual: true)
82 field(:ap_id, :string)
84 field(:local, :boolean, default: true)
85 field(:follower_address, :string)
86 field(:following_address, :string)
87 field(:search_rank, :float, virtual: true)
88 field(:search_type, :integer, virtual: true)
89 field(:tags, {:array, :string}, default: [])
90 field(:last_refreshed_at, :naive_datetime_usec)
91 field(:last_digest_emailed_at, :naive_datetime)
92 field(:banner, :map, default: %{})
93 field(:background, :map, default: %{})
94 field(:source_data, :map, default: %{})
95 field(:note_count, :integer, default: 0)
96 field(:follower_count, :integer, default: 0)
97 field(:following_count, :integer, default: 0)
98 field(:locked, :boolean, default: false)
99 field(:confirmation_pending, :boolean, default: false)
100 field(:password_reset_pending, :boolean, default: false)
101 field(:confirmation_token, :string, default: nil)
102 field(:default_scope, :string, default: "public")
103 field(:domain_blocks, {:array, :string}, default: [])
104 field(:deactivated, :boolean, default: false)
105 field(:no_rich_text, :boolean, default: false)
106 field(:ap_enabled, :boolean, default: false)
107 field(:is_moderator, :boolean, default: false)
108 field(:is_admin, :boolean, default: false)
109 field(:show_role, :boolean, default: true)
110 field(:settings, :map, default: nil)
111 field(:magic_key, :string, default: nil)
112 field(:uri, :string, default: nil)
113 field(:hide_followers_count, :boolean, default: false)
114 field(:hide_follows_count, :boolean, default: false)
115 field(:hide_followers, :boolean, default: false)
116 field(:hide_follows, :boolean, default: false)
117 field(:hide_favorites, :boolean, default: true)
118 field(:unread_conversation_count, :integer, default: 0)
119 field(:pinned_activities, {:array, :string}, default: [])
120 field(:email_notifications, :map, default: %{"digest" => false})
121 field(:mascot, :map, default: nil)
122 field(:emoji, {:array, :map}, default: [])
123 field(:pleroma_settings_store, :map, default: %{})
124 field(:fields, {:array, :map}, default: [])
125 field(:raw_fields, {:array, :map}, default: [])
126 field(:discoverable, :boolean, default: false)
127 field(:invisible, :boolean, default: false)
128 field(:skip_thread_containment, :boolean, default: false)
130 field(:notification_settings, :map,
134 "non_follows" => true,
135 "non_followers" => true
139 has_many(:notifications, Notification)
140 has_many(:registrations, Registration)
141 has_many(:deliveries, Delivery)
143 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
144 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
146 for {relationship_type,
148 {outgoing_relation, outgoing_relation_target},
149 {incoming_relation, incoming_relation_source}
150 ]} <- @user_relationships_config do
151 # Definitions of `has_many :blocker_blocks`, `has_many :muter_mutes` etc.
152 has_many(outgoing_relation, UserRelationship,
153 foreign_key: :source_id,
154 where: [relationship_type: relationship_type]
157 # Definitions of `has_many :blockee_blocks`, `has_many :mutee_mutes` etc.
158 has_many(incoming_relation, UserRelationship,
159 foreign_key: :target_id,
160 where: [relationship_type: relationship_type]
163 # Definitions of `has_many :blocked_users`, `has_many :muted_users` etc.
164 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
166 # Definitions of `has_many :blocker_users`, `has_many :muter_users` etc.
167 has_many(incoming_relation_source, through: [incoming_relation, :source])
170 field(:info, :map, default: %{})
172 # `:blocks` is deprecated (replaced with `blocked_users` relation)
173 field(:blocks, {:array, :string}, default: [])
174 # `:mutes` is deprecated (replaced with `muted_users` relation)
175 field(:mutes, {:array, :string}, default: [])
176 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
177 field(:muted_reblogs, {:array, :string}, default: [])
178 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
179 field(:muted_notifications, {:array, :string}, default: [])
180 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
181 field(:subscribers, {:array, :string}, default: [])
186 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
187 @user_relationships_config do
188 # Definitions of `blocked_users_relation/1`, `muted_users_relation/1`, etc.
189 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
190 target_users_query = assoc(user, unquote(outgoing_relation_target))
192 if restrict_deactivated? do
193 restrict_deactivated(target_users_query)
199 # Definitions of `blocked_users/1`, `muted_users/1`, etc.
200 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
202 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
204 restrict_deactivated?
209 # Definitions of `blocked_users_ap_ids/1`, `muted_users_ap_ids/1`, etc.
210 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
212 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
214 restrict_deactivated?
216 |> select([u], u.ap_id)
221 @doc "Returns if the user should be allowed to authenticate"
222 def auth_active?(%User{deactivated: true}), do: false
224 def auth_active?(%User{confirmation_pending: true}),
225 do: !Pleroma.Config.get([:instance, :account_activation_required])
227 def auth_active?(%User{}), do: true
229 def visible_for?(user, for_user \\ nil)
231 def visible_for?(%User{invisible: true}, _), do: false
233 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
235 def visible_for?(%User{} = user, for_user) do
236 auth_active?(user) || superuser?(for_user)
239 def visible_for?(_, _), do: false
241 def superuser?(%User{local: true, is_admin: true}), do: true
242 def superuser?(%User{local: true, is_moderator: true}), do: true
243 def superuser?(_), do: false
245 def invisible?(%User{invisible: true}), do: true
246 def invisible?(_), do: false
248 def avatar_url(user, options \\ []) do
250 %{"url" => [%{"href" => href} | _]} -> href
251 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
255 def banner_url(user, options \\ []) do
257 %{"url" => [%{"href" => href} | _]} -> href
258 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
262 def profile_url(%User{source_data: %{"url" => url}}), do: url
263 def profile_url(%User{ap_id: ap_id}), do: ap_id
264 def profile_url(_), do: nil
266 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
268 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
269 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
271 @spec ap_following(User.t()) :: Sring.t()
272 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
273 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
275 def user_info(%User{} = user, args \\ %{}) do
276 following_count = Map.get(args, :following_count, user.following_count)
277 follower_count = Map.get(args, :follower_count, user.follower_count)
280 note_count: user.note_count,
282 confirmation_pending: user.confirmation_pending,
283 default_scope: user.default_scope,
284 follower_count: follower_count,
285 following_count: following_count
289 def follow_state(%User{} = user, %User{} = target) do
290 case Utils.fetch_latest_follow(user, target) do
291 %{data: %{"state" => state}} -> state
292 # Ideally this would be nil, but then Cachex does not commit the value
297 def get_cached_follow_state(user, target) do
298 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
299 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
302 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
303 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
304 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
307 def set_info_cache(user, args) do
308 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
311 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
312 def restrict_deactivated(query) do
313 from(u in query, where: u.deactivated != ^true)
316 defdelegate following_count(user), to: FollowingRelationship
318 defp truncate_fields_param(params) do
319 if Map.has_key?(params, :fields) do
320 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
326 defp truncate_if_exists(params, key, max_length) do
327 if Map.has_key?(params, key) and is_binary(params[key]) do
328 {value, _chopped} = String.split_at(params[key], max_length)
329 Map.put(params, key, value)
335 def remote_user_creation(params) do
336 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
337 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
341 |> Map.put(:info, params[:info] || %{})
342 |> truncate_if_exists(:name, name_limit)
343 |> truncate_if_exists(:bio, bio_limit)
344 |> truncate_fields_param()
364 :hide_followers_count,
373 |> validate_required([:name, :ap_id])
374 |> unique_constraint(:nickname)
375 |> validate_format(:nickname, @email_regex)
376 |> validate_length(:bio, max: bio_limit)
377 |> validate_length(:name, max: name_limit)
378 |> validate_fields(true)
380 case params[:source_data] do
381 %{"followers" => followers, "following" => following} ->
383 |> put_change(:follower_address, followers)
384 |> put_change(:following_address, following)
387 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
388 put_change(changeset, :follower_address, followers)
392 def update_changeset(struct, params \\ %{}) do
393 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
394 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
409 :hide_followers_count,
414 :skip_thread_containment,
417 :pleroma_settings_store,
421 |> unique_constraint(:nickname)
422 |> validate_format(:nickname, local_nickname_regex())
423 |> validate_length(:bio, max: bio_limit)
424 |> validate_length(:name, min: 1, max: name_limit)
425 |> validate_fields(false)
428 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
429 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
430 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
432 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
434 params = if remote?, do: truncate_fields_param(params), else: params
457 :hide_followers_count,
461 |> unique_constraint(:nickname)
462 |> validate_format(:nickname, local_nickname_regex())
463 |> validate_length(:bio, max: bio_limit)
464 |> validate_length(:name, max: name_limit)
465 |> validate_fields(remote?)
468 def password_update_changeset(struct, params) do
470 |> cast(params, [:password, :password_confirmation])
471 |> validate_required([:password, :password_confirmation])
472 |> validate_confirmation(:password)
473 |> put_password_hash()
474 |> put_change(:password_reset_pending, false)
477 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
478 def reset_password(%User{id: user_id} = user, data) do
481 |> Multi.update(:user, password_update_changeset(user, data))
482 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
483 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
485 case Repo.transaction(multi) do
486 {:ok, %{user: user} = _} -> set_cache(user)
487 {:error, _, changeset, _} -> {:error, changeset}
491 def update_password_reset_pending(user, value) do
494 |> put_change(:password_reset_pending, value)
495 |> update_and_set_cache()
498 def force_password_reset_async(user) do
499 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
502 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
503 def force_password_reset(user), do: update_password_reset_pending(user, true)
505 def register_changeset(struct, params \\ %{}, opts \\ []) do
506 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
507 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
510 if is_nil(opts[:need_confirmation]) do
511 Pleroma.Config.get([:instance, :account_activation_required])
513 opts[:need_confirmation]
517 |> confirmation_changeset(need_confirmation: need_confirmation?)
518 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
519 |> validate_required([:name, :nickname, :password, :password_confirmation])
520 |> validate_confirmation(:password)
521 |> unique_constraint(:email)
522 |> unique_constraint(:nickname)
523 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
524 |> validate_format(:nickname, local_nickname_regex())
525 |> validate_format(:email, @email_regex)
526 |> validate_length(:bio, max: bio_limit)
527 |> validate_length(:name, min: 1, max: name_limit)
528 |> maybe_validate_required_email(opts[:external])
531 |> unique_constraint(:ap_id)
532 |> put_following_and_follower_address()
535 def maybe_validate_required_email(changeset, true), do: changeset
536 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
538 defp put_ap_id(changeset) do
539 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
540 put_change(changeset, :ap_id, ap_id)
543 defp put_following_and_follower_address(changeset) do
544 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
547 |> put_change(:follower_address, followers)
550 defp autofollow_users(user) do
551 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
554 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
557 follow_all(user, autofollowed_users)
560 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
561 def register(%Ecto.Changeset{} = changeset) do
562 with {:ok, user} <- Repo.insert(changeset) do
563 post_register_action(user)
567 def post_register_action(%User{} = user) do
568 with {:ok, user} <- autofollow_users(user),
569 {:ok, user} <- set_cache(user),
570 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
571 {:ok, _} <- try_send_confirmation_email(user) do
576 def try_send_confirmation_email(%User{} = user) do
577 if user.confirmation_pending &&
578 Pleroma.Config.get([:instance, :account_activation_required]) do
580 |> Pleroma.Emails.UserEmail.account_confirmation_email()
581 |> Pleroma.Emails.Mailer.deliver_async()
589 def needs_update?(%User{local: true}), do: false
591 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
593 def needs_update?(%User{local: false} = user) do
594 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
597 def needs_update?(_), do: true
599 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
600 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
601 follow(follower, followed, "pending")
604 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
605 follow(follower, followed)
608 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
609 if not ap_enabled?(followed) do
610 follow(follower, followed)
616 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
617 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
618 def follow_all(follower, followeds) do
620 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
621 |> Enum.each(&follow(follower, &1, "accept"))
626 defdelegate following(user), to: FollowingRelationship
628 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
629 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
632 followed.deactivated ->
633 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
635 deny_follow_blocked and blocks?(followed, follower) ->
636 {:error, "Could not follow user: #{followed.nickname} blocked you."}
639 FollowingRelationship.follow(follower, followed, state)
641 {:ok, _} = update_follower_count(followed)
644 |> update_following_count()
649 def unfollow(%User{} = follower, %User{} = followed) do
650 if following?(follower, followed) and follower.ap_id != followed.ap_id do
651 FollowingRelationship.unfollow(follower, followed)
653 {:ok, followed} = update_follower_count(followed)
657 |> update_following_count()
660 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
662 {:error, "Not subscribed!"}
666 defdelegate following?(follower, followed), to: FollowingRelationship
668 def locked?(%User{} = user) do
673 Repo.get_by(User, id: id)
676 def get_by_ap_id(ap_id) do
677 Repo.get_by(User, ap_id: ap_id)
680 def get_all_by_ap_id(ap_ids) do
681 from(u in __MODULE__,
682 where: u.ap_id in ^ap_ids
687 def get_all_by_ids(ids) do
688 from(u in __MODULE__, where: u.id in ^ids)
692 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
693 # of the ap_id and the domain and tries to get that user
694 def get_by_guessed_nickname(ap_id) do
695 domain = URI.parse(ap_id).host
696 name = List.last(String.split(ap_id, "/"))
697 nickname = "#{name}@#{domain}"
699 get_cached_by_nickname(nickname)
702 def set_cache({:ok, user}), do: set_cache(user)
703 def set_cache({:error, err}), do: {:error, err}
705 def set_cache(%User{} = user) do
706 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
707 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
708 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
712 def update_and_set_cache(struct, params) do
714 |> update_changeset(params)
715 |> update_and_set_cache()
718 def update_and_set_cache(changeset) do
719 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
724 def invalidate_cache(user) do
725 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
726 Cachex.del(:user_cache, "nickname:#{user.nickname}")
727 Cachex.del(:user_cache, "user_info:#{user.id}")
730 def get_cached_by_ap_id(ap_id) do
731 key = "ap_id:#{ap_id}"
732 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
735 def get_cached_by_id(id) do
739 Cachex.fetch!(:user_cache, key, fn _ ->
743 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
744 {:commit, user.ap_id}
750 get_cached_by_ap_id(ap_id)
753 def get_cached_by_nickname(nickname) do
754 key = "nickname:#{nickname}"
756 Cachex.fetch!(:user_cache, key, fn ->
757 case get_or_fetch_by_nickname(nickname) do
758 {:ok, user} -> {:commit, user}
759 {:error, _error} -> {:ignore, nil}
764 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
765 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
768 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
769 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
771 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
772 get_cached_by_nickname(nickname_or_id)
774 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
775 get_cached_by_nickname(nickname_or_id)
782 def get_by_nickname(nickname) do
783 Repo.get_by(User, nickname: nickname) ||
784 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
785 Repo.get_by(User, nickname: local_nickname(nickname))
789 def get_by_email(email), do: Repo.get_by(User, email: email)
791 def get_by_nickname_or_email(nickname_or_email) do
792 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
795 def get_cached_user_info(user) do
796 key = "user_info:#{user.id}"
797 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
800 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
802 def get_or_fetch_by_nickname(nickname) do
803 with %User{} = user <- get_by_nickname(nickname) do
807 with [_nick, _domain] <- String.split(nickname, "@"),
808 {:ok, user} <- fetch_by_nickname(nickname) do
809 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
810 fetch_initial_posts(user)
815 _e -> {:error, "not found " <> nickname}
820 @doc "Fetch some posts when the user has just been federated with"
821 def fetch_initial_posts(user) do
822 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
825 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
826 def get_followers_query(%User{} = user, nil) do
827 User.Query.build(%{followers: user, deactivated: false})
830 def get_followers_query(user, page) do
832 |> get_followers_query(nil)
833 |> User.Query.paginate(page, 20)
836 @spec get_followers_query(User.t()) :: Ecto.Query.t()
837 def get_followers_query(user), do: get_followers_query(user, nil)
839 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
840 def get_followers(user, page \\ nil) do
842 |> get_followers_query(page)
846 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
847 def get_external_followers(user, page \\ nil) do
849 |> get_followers_query(page)
850 |> User.Query.build(%{external: true})
854 def get_followers_ids(user, page \\ nil) do
856 |> get_followers_query(page)
861 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
862 def get_friends_query(%User{} = user, nil) do
863 User.Query.build(%{friends: user, deactivated: false})
866 def get_friends_query(user, page) do
868 |> get_friends_query(nil)
869 |> User.Query.paginate(page, 20)
872 @spec get_friends_query(User.t()) :: Ecto.Query.t()
873 def get_friends_query(user), do: get_friends_query(user, nil)
875 def get_friends(user, page \\ nil) do
877 |> get_friends_query(page)
881 def get_friends_ids(user, page \\ nil) do
883 |> get_friends_query(page)
888 defdelegate get_follow_requests(user), to: FollowingRelationship
890 def increase_note_count(%User{} = user) do
892 |> where(id: ^user.id)
893 |> update([u], inc: [note_count: 1])
895 |> Repo.update_all([])
897 {1, [user]} -> set_cache(user)
902 def decrease_note_count(%User{} = user) do
904 |> where(id: ^user.id)
907 note_count: fragment("greatest(0, note_count - 1)")
911 |> Repo.update_all([])
913 {1, [user]} -> set_cache(user)
918 def update_note_count(%User{} = user, note_count \\ nil) do
923 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
929 |> cast(%{note_count: note_count}, [:note_count])
930 |> update_and_set_cache()
933 @spec maybe_fetch_follow_information(User.t()) :: User.t()
934 def maybe_fetch_follow_information(user) do
935 with {:ok, user} <- fetch_follow_information(user) do
939 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
945 def fetch_follow_information(user) do
946 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
948 |> follow_information_changeset(info)
949 |> update_and_set_cache()
953 defp follow_information_changeset(user, params) do
960 :hide_followers_count,
965 def update_follower_count(%User{} = user) do
966 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
967 follower_count_query =
968 User.Query.build(%{followers: user, deactivated: false})
969 |> select([u], %{count: count(u.id)})
972 |> where(id: ^user.id)
973 |> join(:inner, [u], s in subquery(follower_count_query))
975 set: [follower_count: s.count]
978 |> Repo.update_all([])
980 {1, [user]} -> set_cache(user)
984 {:ok, maybe_fetch_follow_information(user)}
988 @spec update_following_count(User.t()) :: User.t()
989 def update_following_count(%User{local: false} = user) do
990 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
991 maybe_fetch_follow_information(user)
997 def update_following_count(%User{local: true} = user) do
998 following_count = FollowingRelationship.following_count(user)
1001 |> follow_information_changeset(%{following_count: following_count})
1005 def set_unread_conversation_count(%User{local: true} = user) do
1006 unread_query = Participation.unread_conversation_count_for_user(user)
1009 |> join(:inner, [u], p in subquery(unread_query))
1011 set: [unread_conversation_count: p.count]
1013 |> where([u], u.id == ^user.id)
1015 |> Repo.update_all([])
1017 {1, [user]} -> set_cache(user)
1022 def set_unread_conversation_count(user), do: {:ok, user}
1024 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1026 Participation.unread_conversation_count_for_user(user)
1027 |> where([p], p.conversation_id == ^conversation.id)
1030 |> join(:inner, [u], p in subquery(unread_query))
1032 inc: [unread_conversation_count: 1]
1034 |> where([u], u.id == ^user.id)
1035 |> where([u, p], p.count == 0)
1037 |> Repo.update_all([])
1039 {1, [user]} -> set_cache(user)
1044 def increment_unread_conversation_count(_, user), do: {:ok, user}
1046 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1047 def get_users_from_set(ap_ids, local_only \\ true) do
1048 criteria = %{ap_id: ap_ids, deactivated: false}
1049 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1051 User.Query.build(criteria)
1055 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1056 def get_recipients_from_activity(%Activity{recipients: to}) do
1057 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1061 @spec mute(User.t(), User.t(), boolean()) ::
1062 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1063 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1064 add_to_mutes(muter, mutee, notifications?)
1067 def unmute(%User{} = muter, %User{} = mutee) do
1068 remove_from_mutes(muter, mutee)
1071 def subscribe(%User{} = subscriber, %User{} = target) do
1072 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1074 if blocks?(target, subscriber) and deny_follow_blocked do
1075 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1077 # Note: the relationship is inverse: subscriber acts as relationship target
1078 UserRelationship.create_inverse_subscription(target, subscriber)
1082 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1083 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1084 subscribe(subscriber, subscribee)
1088 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1089 # Note: the relationship is inverse: subscriber acts as relationship target
1090 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1093 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1094 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1095 unsubscribe(unsubscriber, user)
1099 def block(%User{} = blocker, %User{} = blocked) do
1100 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1102 if following?(blocker, blocked) do
1103 {:ok, blocker, _} = unfollow(blocker, blocked)
1109 # clear any requested follows as well
1111 case CommonAPI.reject_follow_request(blocked, blocker) do
1112 {:ok, %User{} = updated_blocked} -> updated_blocked
1116 unsubscribe(blocked, blocker)
1118 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1120 {:ok, blocker} = update_follower_count(blocker)
1121 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1122 add_to_block(blocker, blocked)
1125 # helper to handle the block given only an actor's AP id
1126 def block(%User{} = blocker, %{ap_id: ap_id}) do
1127 block(blocker, get_cached_by_ap_id(ap_id))
1130 def unblock(%User{} = blocker, %User{} = blocked) do
1131 remove_from_block(blocker, blocked)
1134 # helper to handle the block given only an actor's AP id
1135 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1136 unblock(blocker, get_cached_by_ap_id(ap_id))
1139 def mutes?(nil, _), do: false
1140 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1142 def mutes_user?(%User{} = user, %User{} = target) do
1143 UserRelationship.mute_exists?(user, target)
1146 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1147 def muted_notifications?(nil, _), do: false
1149 def muted_notifications?(%User{} = user, %User{} = target),
1150 do: UserRelationship.notification_mute_exists?(user, target)
1152 def blocks?(nil, _), do: false
1154 def blocks?(%User{} = user, %User{} = target) do
1155 blocks_user?(user, target) || blocks_domain?(user, target)
1158 def blocks_user?(%User{} = user, %User{} = target) do
1159 UserRelationship.block_exists?(user, target)
1162 def blocks_user?(_, _), do: false
1164 def blocks_domain?(%User{} = user, %User{} = target) do
1165 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1166 %{host: host} = URI.parse(target.ap_id)
1167 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1170 def blocks_domain?(_, _), do: false
1172 def subscribed_to?(%User{} = user, %User{} = target) do
1173 # Note: the relationship is inverse: subscriber acts as relationship target
1174 UserRelationship.inverse_subscription_exists?(target, user)
1177 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1178 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1179 subscribed_to?(user, target)
1184 Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
1185 E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1187 @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1188 def outgoing_relations_ap_ids(_, []), do: %{}
1190 def outgoing_relations_ap_ids(%User{} = user, relationship_types)
1191 when is_list(relationship_types) do
1194 |> assoc(:outgoing_relationships)
1195 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1196 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1197 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1198 |> group_by([user_rel, u], user_rel.relationship_type)
1200 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1205 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1209 def deactivate_async(user, status \\ true) do
1210 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1213 def deactivate(user, status \\ true)
1215 def deactivate(users, status) when is_list(users) do
1216 Repo.transaction(fn ->
1217 for user <- users, do: deactivate(user, status)
1221 def deactivate(%User{} = user, status) do
1222 with {:ok, user} <- set_activation_status(user, status) do
1225 |> Enum.filter(& &1.local)
1226 |> Enum.each(fn follower ->
1227 follower |> update_following_count() |> set_cache()
1230 # Only update local user counts, remote will be update during the next pull.
1233 |> Enum.filter(& &1.local)
1234 |> Enum.each(&update_follower_count/1)
1240 def update_notification_settings(%User{} = user, settings) do
1243 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1246 notification_settings =
1247 user.notification_settings
1248 |> Map.merge(settings)
1249 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1251 params = %{notification_settings: notification_settings}
1254 |> cast(params, [:notification_settings])
1255 |> validate_required([:notification_settings])
1256 |> update_and_set_cache()
1259 def delete(users) when is_list(users) do
1260 for user <- users, do: delete(user)
1263 def delete(%User{} = user) do
1264 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1267 def perform(:force_password_reset, user), do: force_password_reset(user)
1269 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1270 def perform(:delete, %User{} = user) do
1271 {:ok, _user} = ActivityPub.delete(user)
1273 # Remove all relationships
1276 |> Enum.each(fn follower ->
1277 ActivityPub.unfollow(follower, user)
1278 unfollow(follower, user)
1283 |> Enum.each(fn followed ->
1284 ActivityPub.unfollow(user, followed)
1285 unfollow(user, followed)
1288 delete_user_activities(user)
1289 invalidate_cache(user)
1293 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1294 def perform(:fetch_initial_posts, %User{} = user) do
1295 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1297 # Insert all the posts in reverse order, so they're in the right order on the timeline
1298 user.source_data["outbox"]
1299 |> Utils.fetch_ordered_collection(pages)
1301 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1304 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1306 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1307 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1308 when is_list(blocked_identifiers) do
1310 blocked_identifiers,
1311 fn blocked_identifier ->
1312 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1313 {:ok, _user_block} <- block(blocker, blocked),
1314 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1318 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1325 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1326 def perform(:follow_import, %User{} = follower, followed_identifiers)
1327 when is_list(followed_identifiers) do
1329 followed_identifiers,
1330 fn followed_identifier ->
1331 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1332 {:ok, follower} <- maybe_direct_follow(follower, followed),
1333 {:ok, _} <- ActivityPub.follow(follower, followed) do
1337 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1344 @spec external_users_query() :: Ecto.Query.t()
1345 def external_users_query do
1353 @spec external_users(keyword()) :: [User.t()]
1354 def external_users(opts \\ []) do
1356 external_users_query()
1357 |> select([u], struct(u, [:id, :ap_id, :info]))
1361 do: where(query, [u], u.id > ^opts[:max_id]),
1366 do: limit(query, ^opts[:limit]),
1372 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1373 BackgroundWorker.enqueue("blocks_import", %{
1374 "blocker_id" => blocker.id,
1375 "blocked_identifiers" => blocked_identifiers
1379 def follow_import(%User{} = follower, followed_identifiers)
1380 when is_list(followed_identifiers) do
1381 BackgroundWorker.enqueue("follow_import", %{
1382 "follower_id" => follower.id,
1383 "followed_identifiers" => followed_identifiers
1387 def delete_user_activities(%User{ap_id: ap_id}) do
1389 |> Activity.Queries.by_actor()
1390 |> RepoStreamer.chunk_stream(50)
1391 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1395 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1397 |> Object.normalize()
1398 |> ActivityPub.delete()
1401 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1402 object = Object.normalize(activity)
1405 |> get_cached_by_ap_id()
1406 |> ActivityPub.unlike(object)
1409 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1410 object = Object.normalize(activity)
1413 |> get_cached_by_ap_id()
1414 |> ActivityPub.unannounce(object)
1417 defp delete_activity(_activity), do: "Doing nothing"
1419 def html_filter_policy(%User{no_rich_text: true}) do
1420 Pleroma.HTML.Scrubber.TwitterText
1423 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1425 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1427 def get_or_fetch_by_ap_id(ap_id) do
1428 user = get_cached_by_ap_id(ap_id)
1430 if !is_nil(user) and !needs_update?(user) do
1433 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1434 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1436 resp = fetch_by_ap_id(ap_id)
1438 if should_fetch_initial do
1439 with {:ok, %User{} = user} <- resp do
1440 fetch_initial_posts(user)
1449 Creates an internal service actor by URI if missing.
1450 Optionally takes nickname for addressing.
1452 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1453 with user when is_nil(user) <- get_cached_by_ap_id(uri) do
1460 follower_address: uri <> "/followers"
1469 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1472 |> :public_key.pem_decode()
1474 |> :public_key.pem_entry_decode()
1479 def public_key(_), do: {:error, "not found key"}
1481 def get_public_key_for_ap_id(ap_id) do
1482 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1483 {:ok, public_key} <- public_key(user) do
1490 defp blank?(""), do: nil
1491 defp blank?(n), do: n
1493 def insert_or_update_user(data) do
1495 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1496 |> remote_user_creation()
1497 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1501 def ap_enabled?(%User{local: true}), do: true
1502 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1503 def ap_enabled?(_), do: false
1505 @doc "Gets or fetch a user by uri or nickname."
1506 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1507 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1508 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1510 # wait a period of time and return newest version of the User structs
1511 # this is because we have synchronous follow APIs and need to simulate them
1512 # with an async handshake
1513 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1514 with %User{} = a <- get_cached_by_id(a.id),
1515 %User{} = b <- get_cached_by_id(b.id) do
1522 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1523 with :ok <- :timer.sleep(timeout),
1524 %User{} = a <- get_cached_by_id(a.id),
1525 %User{} = b <- get_cached_by_id(b.id) do
1532 def parse_bio(bio) when is_binary(bio) and bio != "" do
1534 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1538 def parse_bio(_), do: ""
1540 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1541 # TODO: get profile URLs other than user.ap_id
1542 profile_urls = [user.ap_id]
1545 |> CommonUtils.format_input("text/plain",
1546 mentions_format: :full,
1547 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1552 def parse_bio(_, _), do: ""
1554 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1555 Repo.transaction(fn ->
1556 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1560 def tag(nickname, tags) when is_binary(nickname),
1561 do: tag(get_by_nickname(nickname), tags)
1563 def tag(%User{} = user, tags),
1564 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1566 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1567 Repo.transaction(fn ->
1568 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1572 def untag(nickname, tags) when is_binary(nickname),
1573 do: untag(get_by_nickname(nickname), tags)
1575 def untag(%User{} = user, tags),
1576 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1578 defp update_tags(%User{} = user, new_tags) do
1579 {:ok, updated_user} =
1581 |> change(%{tags: new_tags})
1582 |> update_and_set_cache()
1587 defp normalize_tags(tags) do
1590 |> Enum.map(&String.downcase/1)
1593 defp local_nickname_regex do
1594 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1595 @extended_local_nickname_regex
1597 @strict_local_nickname_regex
1601 def local_nickname(nickname_or_mention) do
1604 |> String.split("@")
1608 def full_nickname(nickname_or_mention),
1609 do: String.trim_leading(nickname_or_mention, "@")
1611 def error_user(ap_id) do
1615 nickname: "erroruser@example.com",
1616 inserted_at: NaiveDateTime.utc_now()
1620 @spec all_superusers() :: [User.t()]
1621 def all_superusers do
1622 User.Query.build(%{super_users: true, local: true, deactivated: false})
1626 def showing_reblogs?(%User{} = user, %User{} = target) do
1627 not UserRelationship.reblog_mute_exists?(user, target)
1631 The function returns a query to get users with no activity for given interval of days.
1632 Inactive users are those who didn't read any notification, or had any activity where
1633 the user is the activity's actor, during `inactivity_threshold` days.
1634 Deactivated users will not appear in this list.
1638 iex> Pleroma.User.list_inactive_users()
1641 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1642 def list_inactive_users_query(inactivity_threshold \\ 7) do
1643 negative_inactivity_threshold = -inactivity_threshold
1644 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1645 # Subqueries are not supported in `where` clauses, join gets too complicated.
1646 has_read_notifications =
1647 from(n in Pleroma.Notification,
1648 where: n.seen == true,
1650 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1653 |> Pleroma.Repo.all()
1655 from(u in Pleroma.User,
1656 left_join: a in Pleroma.Activity,
1657 on: u.ap_id == a.actor,
1658 where: not is_nil(u.nickname),
1659 where: u.deactivated != ^true,
1660 where: u.id not in ^has_read_notifications,
1663 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1664 is_nil(max(a.inserted_at))
1669 Enable or disable email notifications for user
1673 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1674 Pleroma.User{email_notifications: %{"digest" => true}}
1676 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1677 Pleroma.User{email_notifications: %{"digest" => false}}
1679 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1680 {:ok, t()} | {:error, Ecto.Changeset.t()}
1681 def switch_email_notifications(user, type, status) do
1682 User.update_email_notifications(user, %{type => status})
1686 Set `last_digest_emailed_at` value for the user to current time
1688 @spec touch_last_digest_emailed_at(t()) :: t()
1689 def touch_last_digest_emailed_at(user) do
1690 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1692 {:ok, updated_user} =
1694 |> change(%{last_digest_emailed_at: now})
1695 |> update_and_set_cache()
1700 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1701 def toggle_confirmation(%User{} = user) do
1703 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1704 |> update_and_set_cache()
1707 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1711 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1712 # use instance-default
1713 config = Pleroma.Config.get([:assets, :mascots])
1714 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1715 mascot = Keyword.get(config, default_mascot)
1718 "id" => "default-mascot",
1719 "url" => mascot[:url],
1720 "preview_url" => mascot[:url],
1722 "mime_type" => mascot[:mime_type]
1727 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1729 def ensure_keys_present(%User{} = user) do
1730 with {:ok, pem} <- Keys.generate_rsa_pem() do
1732 |> cast(%{keys: pem}, [:keys])
1733 |> validate_required([:keys])
1734 |> update_and_set_cache()
1738 def get_ap_ids_by_nicknames(nicknames) do
1740 where: u.nickname in ^nicknames,
1746 defdelegate search(query, opts \\ []), to: User.Search
1748 defp put_password_hash(
1749 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1751 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1754 defp put_password_hash(changeset), do: changeset
1756 def is_internal_user?(%User{nickname: nil}), do: true
1757 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1758 def is_internal_user?(_), do: false
1760 # A hack because user delete activities have a fake id for whatever reason
1761 # TODO: Get rid of this
1762 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1764 def get_delivered_users_by_object_id(object_id) do
1766 inner_join: delivery in assoc(u, :deliveries),
1767 where: delivery.object_id == ^object_id
1772 def change_email(user, email) do
1774 |> cast(%{email: email}, [:email])
1775 |> validate_required([:email])
1776 |> unique_constraint(:email)
1777 |> validate_format(:email, @email_regex)
1778 |> update_and_set_cache()
1781 # Internal function; public one is `deactivate/2`
1782 defp set_activation_status(user, deactivated) do
1784 |> cast(%{deactivated: deactivated}, [:deactivated])
1785 |> update_and_set_cache()
1788 def update_banner(user, banner) do
1790 |> cast(%{banner: banner}, [:banner])
1791 |> update_and_set_cache()
1794 def update_background(user, background) do
1796 |> cast(%{background: background}, [:background])
1797 |> update_and_set_cache()
1800 def update_source_data(user, source_data) do
1802 |> cast(%{source_data: source_data}, [:source_data])
1803 |> update_and_set_cache()
1806 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1809 moderator: is_moderator
1813 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1814 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1815 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1816 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1819 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1820 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1824 def fields(%{fields: nil}), do: []
1826 def fields(%{fields: fields}), do: fields
1828 def validate_fields(changeset, remote? \\ false) do
1829 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1830 limit = Pleroma.Config.get([:instance, limit_name], 0)
1833 |> validate_length(:fields, max: limit)
1834 |> validate_change(:fields, fn :fields, fields ->
1835 if Enum.all?(fields, &valid_field?/1) do
1843 defp valid_field?(%{"name" => name, "value" => value}) do
1844 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1845 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1847 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1848 String.length(value) <= value_limit
1851 defp valid_field?(_), do: false
1853 defp truncate_field(%{"name" => name, "value" => value}) do
1855 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1858 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1860 %{"name" => name, "value" => value}
1863 def admin_api_update(user, params) do
1870 |> update_and_set_cache()
1873 def mascot_update(user, url) do
1875 |> cast(%{mascot: url}, [:mascot])
1876 |> validate_required([:mascot])
1877 |> update_and_set_cache()
1880 def mastodon_settings_update(user, settings) do
1882 |> cast(%{settings: settings}, [:settings])
1883 |> validate_required([:settings])
1884 |> update_and_set_cache()
1887 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1888 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1890 if need_confirmation? do
1892 confirmation_pending: true,
1893 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1897 confirmation_pending: false,
1898 confirmation_token: nil
1902 cast(user, params, [:confirmation_pending, :confirmation_token])
1905 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1906 if id not in user.pinned_activities do
1907 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1908 params = %{pinned_activities: user.pinned_activities ++ [id]}
1911 |> cast(params, [:pinned_activities])
1912 |> validate_length(:pinned_activities,
1913 max: max_pinned_statuses,
1914 message: "You have already pinned the maximum number of statuses"
1919 |> update_and_set_cache()
1922 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1923 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1926 |> cast(params, [:pinned_activities])
1927 |> update_and_set_cache()
1930 def update_email_notifications(user, settings) do
1931 email_notifications =
1932 user.email_notifications
1933 |> Map.merge(settings)
1934 |> Map.take(["digest"])
1936 params = %{email_notifications: email_notifications}
1937 fields = [:email_notifications]
1940 |> cast(params, fields)
1941 |> validate_required(fields)
1942 |> update_and_set_cache()
1945 defp set_domain_blocks(user, domain_blocks) do
1946 params = %{domain_blocks: domain_blocks}
1949 |> cast(params, [:domain_blocks])
1950 |> validate_required([:domain_blocks])
1951 |> update_and_set_cache()
1954 def block_domain(user, domain_blocked) do
1955 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1958 def unblock_domain(user, domain_blocked) do
1959 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1962 @spec add_to_block(User.t(), User.t()) ::
1963 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
1964 defp add_to_block(%User{} = user, %User{} = blocked) do
1965 UserRelationship.create_block(user, blocked)
1968 @spec add_to_block(User.t(), User.t()) ::
1969 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
1970 defp remove_from_block(%User{} = user, %User{} = blocked) do
1971 UserRelationship.delete_block(user, blocked)
1974 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
1975 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
1976 {:ok, user_notification_mute} <-
1977 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
1979 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
1983 defp remove_from_mutes(user, %User{} = muted_user) do
1984 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
1985 {:ok, user_notification_mute} <-
1986 UserRelationship.delete_notification_mute(user, muted_user) do
1987 {:ok, [user_mute, user_notification_mute]}
1991 def set_invisible(user, invisible) do
1992 params = %{invisible: invisible}
1995 |> cast(params, [:invisible])
1996 |> validate_required([:invisible])
1997 |> update_and_set_cache()