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 # Should be filled in only for remote users
98 field(:following_count, :integer, default: nil)
99 field(:locked, :boolean, default: false)
100 field(:confirmation_pending, :boolean, default: false)
101 field(:password_reset_pending, :boolean, default: false)
102 field(:confirmation_token, :string, default: nil)
103 field(:default_scope, :string, default: "public")
104 field(:domain_blocks, {:array, :string}, default: [])
105 field(:deactivated, :boolean, default: false)
106 field(:no_rich_text, :boolean, default: false)
107 field(:ap_enabled, :boolean, default: false)
108 field(:is_moderator, :boolean, default: false)
109 field(:is_admin, :boolean, default: false)
110 field(:show_role, :boolean, default: true)
111 field(:settings, :map, default: nil)
112 field(:magic_key, :string, default: nil)
113 field(:uri, :string, default: nil)
114 field(:hide_followers_count, :boolean, default: false)
115 field(:hide_follows_count, :boolean, default: false)
116 field(:hide_followers, :boolean, default: false)
117 field(:hide_follows, :boolean, default: false)
118 field(:hide_favorites, :boolean, default: true)
119 field(:unread_conversation_count, :integer, default: 0)
120 field(:pinned_activities, {:array, :string}, default: [])
121 field(:email_notifications, :map, default: %{"digest" => false})
122 field(:mascot, :map, default: nil)
123 field(:emoji, {:array, :map}, default: [])
124 field(:pleroma_settings_store, :map, default: %{})
125 field(:fields, {:array, :map}, default: [])
126 field(:raw_fields, {:array, :map}, default: [])
127 field(:discoverable, :boolean, default: false)
128 field(:invisible, :boolean, default: false)
129 field(:skip_thread_containment, :boolean, default: false)
131 field(:notification_settings, :map,
135 "non_follows" => true,
136 "non_followers" => true
140 has_many(:notifications, Notification)
141 has_many(:registrations, Registration)
142 has_many(:deliveries, Delivery)
144 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
145 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
147 for {relationship_type,
149 {outgoing_relation, outgoing_relation_target},
150 {incoming_relation, incoming_relation_source}
151 ]} <- @user_relationships_config do
152 # Definitions of `has_many :blocker_blocks`, `has_many :muter_mutes` etc.
153 has_many(outgoing_relation, UserRelationship,
154 foreign_key: :source_id,
155 where: [relationship_type: relationship_type]
158 # Definitions of `has_many :blockee_blocks`, `has_many :mutee_mutes` etc.
159 has_many(incoming_relation, UserRelationship,
160 foreign_key: :target_id,
161 where: [relationship_type: relationship_type]
164 # Definitions of `has_many :blocked_users`, `has_many :muted_users` etc.
165 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
167 # Definitions of `has_many :blocker_users`, `has_many :muter_users` etc.
168 has_many(incoming_relation_source, through: [incoming_relation, :source])
171 field(:info, :map, default: %{})
173 # `:blocks` is deprecated (replaced with `blocked_users` relation)
174 field(:blocks, {:array, :string}, default: [])
175 # `:mutes` is deprecated (replaced with `muted_users` relation)
176 field(:mutes, {:array, :string}, default: [])
177 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
178 field(:muted_reblogs, {:array, :string}, default: [])
179 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
180 field(:muted_notifications, {:array, :string}, default: [])
181 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
182 field(:subscribers, {:array, :string}, default: [])
187 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
188 @user_relationships_config do
189 # Definitions of `blocked_users_relation/1`, `muted_users_relation/1`, etc.
190 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
191 target_users_query = assoc(user, unquote(outgoing_relation_target))
193 if restrict_deactivated? do
194 restrict_deactivated(target_users_query)
200 # Definitions of `blocked_users/1`, `muted_users/1`, etc.
201 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
203 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
205 restrict_deactivated?
210 # Definitions of `blocked_users_ap_ids/1`, `muted_users_ap_ids/1`, etc.
211 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
213 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
215 restrict_deactivated?
217 |> select([u], u.ap_id)
222 @doc "Returns if the user should be allowed to authenticate"
223 def auth_active?(%User{deactivated: true}), do: false
225 def auth_active?(%User{confirmation_pending: true}),
226 do: !Pleroma.Config.get([:instance, :account_activation_required])
228 def auth_active?(%User{}), do: true
230 def visible_for?(user, for_user \\ nil)
232 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
234 def visible_for?(%User{} = user, for_user) do
235 auth_active?(user) || superuser?(for_user)
238 def visible_for?(_, _), do: false
240 def superuser?(%User{local: true, is_admin: true}), do: true
241 def superuser?(%User{local: true, is_moderator: true}), do: true
242 def superuser?(_), do: false
244 def invisible?(%User{invisible: true}), do: true
245 def invisible?(_), do: false
247 def avatar_url(user, options \\ []) do
249 %{"url" => [%{"href" => href} | _]} -> href
250 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
254 def banner_url(user, options \\ []) do
256 %{"url" => [%{"href" => href} | _]} -> href
257 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
261 def profile_url(%User{source_data: %{"url" => url}}), do: url
262 def profile_url(%User{ap_id: ap_id}), do: ap_id
263 def profile_url(_), do: nil
265 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
267 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
268 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
270 @spec ap_following(User.t()) :: Sring.t()
271 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
272 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
274 def user_info(%User{} = user, args \\ %{}) do
276 Map.get(args, :following_count, user.following_count || following_count(user))
278 follower_count = Map.get(args, :follower_count, user.follower_count)
281 note_count: user.note_count,
283 confirmation_pending: user.confirmation_pending,
284 default_scope: user.default_scope
286 |> Map.put(:following_count, following_count)
287 |> Map.put(:follower_count, follower_count)
290 def follow_state(%User{} = user, %User{} = target) do
291 case Utils.fetch_latest_follow(user, target) do
292 %{data: %{"state" => state}} -> state
293 # Ideally this would be nil, but then Cachex does not commit the value
298 def get_cached_follow_state(user, target) do
299 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
300 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
303 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
304 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
305 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
308 def set_info_cache(user, args) do
309 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
312 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
313 def restrict_deactivated(query) do
314 from(u in query, where: u.deactivated != ^true)
317 defdelegate following_count(user), to: FollowingRelationship
319 defp truncate_fields_param(params) do
320 if Map.has_key?(params, :fields) do
321 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
327 defp truncate_if_exists(params, key, max_length) do
328 if Map.has_key?(params, key) and is_binary(params[key]) do
329 {value, _chopped} = String.split_at(params[key], max_length)
330 Map.put(params, key, value)
336 def remote_user_creation(params) do
337 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
338 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
342 |> Map.put(:info, params[:info] || %{})
343 |> truncate_if_exists(:name, name_limit)
344 |> truncate_if_exists(:bio, bio_limit)
345 |> truncate_fields_param()
365 :hide_followers_count,
374 |> validate_required([:name, :ap_id])
375 |> unique_constraint(:nickname)
376 |> validate_format(:nickname, @email_regex)
377 |> validate_length(:bio, max: bio_limit)
378 |> validate_length(:name, max: name_limit)
379 |> validate_fields(true)
381 case params[:source_data] do
382 %{"followers" => followers, "following" => following} ->
384 |> put_change(:follower_address, followers)
385 |> put_change(:following_address, following)
388 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
389 put_change(changeset, :follower_address, followers)
393 def update_changeset(struct, params \\ %{}) do
394 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
395 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
410 :hide_followers_count,
415 :skip_thread_containment,
418 :pleroma_settings_store,
422 |> unique_constraint(:nickname)
423 |> validate_format(:nickname, local_nickname_regex())
424 |> validate_length(:bio, max: bio_limit)
425 |> validate_length(:name, min: 1, max: name_limit)
426 |> validate_fields(false)
429 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
430 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
431 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
433 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
435 params = if remote?, do: truncate_fields_param(params), else: params
458 :hide_followers_count,
462 |> unique_constraint(:nickname)
463 |> validate_format(:nickname, local_nickname_regex())
464 |> validate_length(:bio, max: bio_limit)
465 |> validate_length(:name, max: name_limit)
466 |> validate_fields(remote?)
469 def password_update_changeset(struct, params) do
471 |> cast(params, [:password, :password_confirmation])
472 |> validate_required([:password, :password_confirmation])
473 |> validate_confirmation(:password)
474 |> put_password_hash()
475 |> put_change(:password_reset_pending, false)
478 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
479 def reset_password(%User{id: user_id} = user, data) do
482 |> Multi.update(:user, password_update_changeset(user, data))
483 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
484 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
486 case Repo.transaction(multi) do
487 {:ok, %{user: user} = _} -> set_cache(user)
488 {:error, _, changeset, _} -> {:error, changeset}
492 def update_password_reset_pending(user, value) do
495 |> put_change(:password_reset_pending, value)
496 |> update_and_set_cache()
499 def force_password_reset_async(user) do
500 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
503 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
504 def force_password_reset(user), do: update_password_reset_pending(user, true)
506 def register_changeset(struct, params \\ %{}, opts \\ []) do
507 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
508 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
511 if is_nil(opts[:need_confirmation]) do
512 Pleroma.Config.get([:instance, :account_activation_required])
514 opts[:need_confirmation]
518 |> confirmation_changeset(need_confirmation: need_confirmation?)
519 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
520 |> validate_required([:name, :nickname, :password, :password_confirmation])
521 |> validate_confirmation(:password)
522 |> unique_constraint(:email)
523 |> unique_constraint(:nickname)
524 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
525 |> validate_format(:nickname, local_nickname_regex())
526 |> validate_format(:email, @email_regex)
527 |> validate_length(:bio, max: bio_limit)
528 |> validate_length(:name, min: 1, max: name_limit)
529 |> maybe_validate_required_email(opts[:external])
532 |> unique_constraint(:ap_id)
533 |> put_following_and_follower_address()
536 def maybe_validate_required_email(changeset, true), do: changeset
537 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
539 defp put_ap_id(changeset) do
540 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
541 put_change(changeset, :ap_id, ap_id)
544 defp put_following_and_follower_address(changeset) do
545 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
548 |> put_change(:follower_address, followers)
551 defp autofollow_users(user) do
552 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
555 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
558 follow_all(user, autofollowed_users)
561 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
562 def register(%Ecto.Changeset{} = changeset) do
563 with {:ok, user} <- Repo.insert(changeset) do
564 post_register_action(user)
568 def post_register_action(%User{} = user) do
569 with {:ok, user} <- autofollow_users(user),
570 {:ok, user} <- set_cache(user),
571 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
572 {:ok, _} <- try_send_confirmation_email(user) do
577 def try_send_confirmation_email(%User{} = user) do
578 if user.confirmation_pending &&
579 Pleroma.Config.get([:instance, :account_activation_required]) do
581 |> Pleroma.Emails.UserEmail.account_confirmation_email()
582 |> Pleroma.Emails.Mailer.deliver_async()
590 def needs_update?(%User{local: true}), do: false
592 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
594 def needs_update?(%User{local: false} = user) do
595 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
598 def needs_update?(_), do: true
600 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
601 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
602 follow(follower, followed, "pending")
605 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
606 follow(follower, followed)
609 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
610 if not ap_enabled?(followed) do
611 follow(follower, followed)
617 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
618 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
619 def follow_all(follower, followeds) do
621 Enum.reject(followeds, fn followed ->
622 blocks?(follower, followed) || blocks?(followed, follower)
625 Enum.each(followeds, &follow(follower, &1, "accept"))
627 Enum.each(followeds, &update_follower_count/1)
632 defdelegate following(user), to: FollowingRelationship
634 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
635 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
638 followed.deactivated ->
639 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
641 deny_follow_blocked and blocks?(followed, follower) ->
642 {:error, "Could not follow user: #{followed.nickname} blocked you."}
645 FollowingRelationship.follow(follower, followed, state)
647 follower = maybe_update_following_count(follower)
649 {:ok, _} = update_follower_count(followed)
655 def unfollow(%User{} = follower, %User{} = followed) do
656 if following?(follower, followed) and follower.ap_id != followed.ap_id do
657 FollowingRelationship.unfollow(follower, followed)
659 follower = maybe_update_following_count(follower)
661 {:ok, followed} = update_follower_count(followed)
665 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
667 {:error, "Not subscribed!"}
671 defdelegate following?(follower, followed), to: FollowingRelationship
673 def locked?(%User{} = user) do
678 Repo.get_by(User, id: id)
681 def get_by_ap_id(ap_id) do
682 Repo.get_by(User, ap_id: ap_id)
685 def get_all_by_ap_id(ap_ids) do
686 from(u in __MODULE__,
687 where: u.ap_id in ^ap_ids
692 def get_all_by_ids(ids) do
693 from(u in __MODULE__, where: u.id in ^ids)
697 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
698 # of the ap_id and the domain and tries to get that user
699 def get_by_guessed_nickname(ap_id) do
700 domain = URI.parse(ap_id).host
701 name = List.last(String.split(ap_id, "/"))
702 nickname = "#{name}@#{domain}"
704 get_cached_by_nickname(nickname)
707 def set_cache({:ok, user}), do: set_cache(user)
708 def set_cache({:error, err}), do: {:error, err}
710 def set_cache(%User{} = user) do
711 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
712 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
713 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
717 def update_and_set_cache(struct, params) do
719 |> update_changeset(params)
720 |> update_and_set_cache()
723 def update_and_set_cache(changeset) do
724 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
729 def invalidate_cache(user) do
730 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
731 Cachex.del(:user_cache, "nickname:#{user.nickname}")
732 Cachex.del(:user_cache, "user_info:#{user.id}")
735 def get_cached_by_ap_id(ap_id) do
736 key = "ap_id:#{ap_id}"
737 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
740 def get_cached_by_id(id) do
744 Cachex.fetch!(:user_cache, key, fn _ ->
748 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
749 {:commit, user.ap_id}
755 get_cached_by_ap_id(ap_id)
758 def get_cached_by_nickname(nickname) do
759 key = "nickname:#{nickname}"
761 Cachex.fetch!(:user_cache, key, fn ->
762 case get_or_fetch_by_nickname(nickname) do
763 {:ok, user} -> {:commit, user}
764 {:error, _error} -> {:ignore, nil}
769 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
770 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
773 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
774 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
776 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
777 get_cached_by_nickname(nickname_or_id)
779 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
780 get_cached_by_nickname(nickname_or_id)
787 def get_by_nickname(nickname) do
788 Repo.get_by(User, nickname: nickname) ||
789 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
790 Repo.get_by(User, nickname: local_nickname(nickname))
794 def get_by_email(email), do: Repo.get_by(User, email: email)
796 def get_by_nickname_or_email(nickname_or_email) do
797 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
800 def get_cached_user_info(user) do
801 key = "user_info:#{user.id}"
802 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
805 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
807 def get_or_fetch_by_nickname(nickname) do
808 with %User{} = user <- get_by_nickname(nickname) do
812 with [_nick, _domain] <- String.split(nickname, "@"),
813 {:ok, user} <- fetch_by_nickname(nickname) do
814 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
815 fetch_initial_posts(user)
820 _e -> {:error, "not found " <> nickname}
825 @doc "Fetch some posts when the user has just been federated with"
826 def fetch_initial_posts(user) do
827 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
830 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
831 def get_followers_query(%User{} = user, nil) do
832 User.Query.build(%{followers: user, deactivated: false})
835 def get_followers_query(user, page) do
837 |> get_followers_query(nil)
838 |> User.Query.paginate(page, 20)
841 @spec get_followers_query(User.t()) :: Ecto.Query.t()
842 def get_followers_query(user), do: get_followers_query(user, nil)
844 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
845 def get_followers(user, page \\ nil) do
847 |> get_followers_query(page)
851 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
852 def get_external_followers(user, page \\ nil) do
854 |> get_followers_query(page)
855 |> User.Query.build(%{external: true})
859 def get_followers_ids(user, page \\ nil) do
861 |> get_followers_query(page)
866 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
867 def get_friends_query(%User{} = user, nil) do
868 User.Query.build(%{friends: user, deactivated: false})
871 def get_friends_query(user, page) do
873 |> get_friends_query(nil)
874 |> User.Query.paginate(page, 20)
877 @spec get_friends_query(User.t()) :: Ecto.Query.t()
878 def get_friends_query(user), do: get_friends_query(user, nil)
880 def get_friends(user, page \\ nil) do
882 |> get_friends_query(page)
886 def get_friends_ids(user, page \\ nil) do
888 |> get_friends_query(page)
893 defdelegate get_follow_requests(user), to: FollowingRelationship
895 def increase_note_count(%User{} = user) do
897 |> where(id: ^user.id)
898 |> update([u], inc: [note_count: 1])
900 |> Repo.update_all([])
902 {1, [user]} -> set_cache(user)
907 def decrease_note_count(%User{} = user) do
909 |> where(id: ^user.id)
912 note_count: fragment("greatest(0, note_count - 1)")
916 |> Repo.update_all([])
918 {1, [user]} -> set_cache(user)
923 def update_note_count(%User{} = user, note_count \\ nil) do
928 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
934 |> cast(%{note_count: note_count}, [:note_count])
935 |> update_and_set_cache()
938 @spec maybe_fetch_follow_information(User.t()) :: User.t()
939 def maybe_fetch_follow_information(user) do
940 with {:ok, user} <- fetch_follow_information(user) do
944 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
950 def fetch_follow_information(user) do
951 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
953 |> follow_information_changeset(info)
954 |> update_and_set_cache()
958 defp follow_information_changeset(user, params) do
965 :hide_followers_count,
970 def update_follower_count(%User{} = user) do
971 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
972 follower_count_query =
973 User.Query.build(%{followers: user, deactivated: false})
974 |> select([u], %{count: count(u.id)})
977 |> where(id: ^user.id)
978 |> join(:inner, [u], s in subquery(follower_count_query))
980 set: [follower_count: s.count]
983 |> Repo.update_all([])
985 {1, [user]} -> set_cache(user)
989 {:ok, maybe_fetch_follow_information(user)}
993 @spec maybe_update_following_count(User.t()) :: User.t()
994 def maybe_update_following_count(%User{local: false} = user) do
995 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
996 maybe_fetch_follow_information(user)
1002 def maybe_update_following_count(user), do: user
1004 def set_unread_conversation_count(%User{local: true} = user) do
1005 unread_query = Participation.unread_conversation_count_for_user(user)
1008 |> join(:inner, [u], p in subquery(unread_query))
1010 set: [unread_conversation_count: p.count]
1012 |> where([u], u.id == ^user.id)
1014 |> Repo.update_all([])
1016 {1, [user]} -> set_cache(user)
1021 def set_unread_conversation_count(user), do: {:ok, user}
1023 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1025 Participation.unread_conversation_count_for_user(user)
1026 |> where([p], p.conversation_id == ^conversation.id)
1029 |> join(:inner, [u], p in subquery(unread_query))
1031 inc: [unread_conversation_count: 1]
1033 |> where([u], u.id == ^user.id)
1034 |> where([u, p], p.count == 0)
1036 |> Repo.update_all([])
1038 {1, [user]} -> set_cache(user)
1043 def increment_unread_conversation_count(_, user), do: {:ok, user}
1045 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1046 def get_users_from_set(ap_ids, local_only \\ true) do
1047 criteria = %{ap_id: ap_ids, deactivated: false}
1048 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1050 User.Query.build(criteria)
1054 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1055 def get_recipients_from_activity(%Activity{recipients: to}) do
1056 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1060 @spec mute(User.t(), User.t(), boolean()) ::
1061 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1062 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1063 add_to_mutes(muter, mutee, notifications?)
1066 def unmute(%User{} = muter, %User{} = mutee) do
1067 remove_from_mutes(muter, mutee)
1070 def subscribe(%User{} = subscriber, %User{} = target) do
1071 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1073 if blocks?(target, subscriber) and deny_follow_blocked do
1074 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1076 # Note: the relationship is inverse: subscriber acts as relationship target
1077 UserRelationship.create_inverse_subscription(target, subscriber)
1081 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1082 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1083 subscribe(subscriber, subscribee)
1087 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1088 # Note: the relationship is inverse: subscriber acts as relationship target
1089 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1092 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1093 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1094 unsubscribe(unsubscriber, user)
1098 def block(%User{} = blocker, %User{} = blocked) do
1099 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1101 if following?(blocker, blocked) do
1102 {:ok, blocker, _} = unfollow(blocker, blocked)
1108 # clear any requested follows as well
1110 case CommonAPI.reject_follow_request(blocked, blocker) do
1111 {:ok, %User{} = updated_blocked} -> updated_blocked
1115 unsubscribe(blocked, blocker)
1117 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1119 {:ok, blocker} = update_follower_count(blocker)
1120 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1121 add_to_block(blocker, blocked)
1124 # helper to handle the block given only an actor's AP id
1125 def block(%User{} = blocker, %{ap_id: ap_id}) do
1126 block(blocker, get_cached_by_ap_id(ap_id))
1129 def unblock(%User{} = blocker, %User{} = blocked) do
1130 remove_from_block(blocker, blocked)
1133 # helper to handle the block given only an actor's AP id
1134 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1135 unblock(blocker, get_cached_by_ap_id(ap_id))
1138 def mutes?(nil, _), do: false
1139 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1141 def mutes_user?(%User{} = user, %User{} = target) do
1142 UserRelationship.mute_exists?(user, target)
1145 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1146 def muted_notifications?(nil, _), do: false
1148 def muted_notifications?(%User{} = user, %User{} = target),
1149 do: UserRelationship.notification_mute_exists?(user, target)
1151 def blocks?(nil, _), do: false
1153 def blocks?(%User{} = user, %User{} = target) do
1154 blocks_user?(user, target) || blocks_domain?(user, target)
1157 def blocks_user?(%User{} = user, %User{} = target) do
1158 UserRelationship.block_exists?(user, target)
1161 def blocks_user?(_, _), do: false
1163 def blocks_domain?(%User{} = user, %User{} = target) do
1164 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1165 %{host: host} = URI.parse(target.ap_id)
1166 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1169 def blocks_domain?(_, _), do: false
1171 def subscribed_to?(%User{} = user, %User{} = target) do
1172 # Note: the relationship is inverse: subscriber acts as relationship target
1173 UserRelationship.inverse_subscription_exists?(target, user)
1176 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1177 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1178 subscribed_to?(user, target)
1183 Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
1184 E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1186 @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1187 def outgoing_relations_ap_ids(_, []), do: %{}
1189 def outgoing_relations_ap_ids(%User{} = user, relationship_types)
1190 when is_list(relationship_types) do
1193 |> assoc(:outgoing_relationships)
1194 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1195 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1196 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1197 |> group_by([user_rel, u], user_rel.relationship_type)
1199 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1204 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1208 def deactivate_async(user, status \\ true) do
1209 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1212 def deactivate(user, status \\ true)
1214 def deactivate(users, status) when is_list(users) do
1215 Repo.transaction(fn ->
1216 for user <- users, do: deactivate(user, status)
1220 def deactivate(%User{} = user, status) do
1221 with {:ok, user} <- set_activation_status(user, status) do
1222 Enum.each(get_followers(user), &invalidate_cache/1)
1224 # Only update local user counts, remote will be update during the next pull.
1227 |> Enum.filter(& &1.local)
1228 |> Enum.each(&update_follower_count/1)
1234 def update_notification_settings(%User{} = user, settings) do
1237 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1240 notification_settings =
1241 user.notification_settings
1242 |> Map.merge(settings)
1243 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1245 params = %{notification_settings: notification_settings}
1248 |> cast(params, [:notification_settings])
1249 |> validate_required([:notification_settings])
1250 |> update_and_set_cache()
1253 def delete(users) when is_list(users) do
1254 for user <- users, do: delete(user)
1257 def delete(%User{} = user) do
1258 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1261 def perform(:force_password_reset, user), do: force_password_reset(user)
1263 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1264 def perform(:delete, %User{} = user) do
1265 {:ok, _user} = ActivityPub.delete(user)
1267 # Remove all relationships
1270 |> Enum.each(fn follower ->
1271 ActivityPub.unfollow(follower, user)
1272 unfollow(follower, user)
1277 |> Enum.each(fn followed ->
1278 ActivityPub.unfollow(user, followed)
1279 unfollow(user, followed)
1282 delete_user_activities(user)
1283 invalidate_cache(user)
1287 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1288 def perform(:fetch_initial_posts, %User{} = user) do
1289 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1291 # Insert all the posts in reverse order, so they're in the right order on the timeline
1292 user.source_data["outbox"]
1293 |> Utils.fetch_ordered_collection(pages)
1295 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1298 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1300 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1301 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1302 when is_list(blocked_identifiers) do
1304 blocked_identifiers,
1305 fn blocked_identifier ->
1306 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1307 {:ok, _user_block} <- block(blocker, blocked),
1308 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1312 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1319 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1320 def perform(:follow_import, %User{} = follower, followed_identifiers)
1321 when is_list(followed_identifiers) do
1323 followed_identifiers,
1324 fn followed_identifier ->
1325 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1326 {:ok, follower} <- maybe_direct_follow(follower, followed),
1327 {:ok, _} <- ActivityPub.follow(follower, followed) do
1331 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1338 @spec external_users_query() :: Ecto.Query.t()
1339 def external_users_query do
1347 @spec external_users(keyword()) :: [User.t()]
1348 def external_users(opts \\ []) do
1350 external_users_query()
1351 |> select([u], struct(u, [:id, :ap_id, :info]))
1355 do: where(query, [u], u.id > ^opts[:max_id]),
1360 do: limit(query, ^opts[:limit]),
1366 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1367 BackgroundWorker.enqueue("blocks_import", %{
1368 "blocker_id" => blocker.id,
1369 "blocked_identifiers" => blocked_identifiers
1373 def follow_import(%User{} = follower, followed_identifiers)
1374 when is_list(followed_identifiers) do
1375 BackgroundWorker.enqueue("follow_import", %{
1376 "follower_id" => follower.id,
1377 "followed_identifiers" => followed_identifiers
1381 def delete_user_activities(%User{ap_id: ap_id}) do
1383 |> Activity.Queries.by_actor()
1384 |> RepoStreamer.chunk_stream(50)
1385 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1389 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1391 |> Object.normalize()
1392 |> ActivityPub.delete()
1395 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1396 object = Object.normalize(activity)
1399 |> get_cached_by_ap_id()
1400 |> ActivityPub.unlike(object)
1403 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1404 object = Object.normalize(activity)
1407 |> get_cached_by_ap_id()
1408 |> ActivityPub.unannounce(object)
1411 defp delete_activity(_activity), do: "Doing nothing"
1413 def html_filter_policy(%User{no_rich_text: true}) do
1414 Pleroma.HTML.Scrubber.TwitterText
1417 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1419 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1421 def get_or_fetch_by_ap_id(ap_id) do
1422 user = get_cached_by_ap_id(ap_id)
1424 if !is_nil(user) and !needs_update?(user) do
1427 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1428 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1430 resp = fetch_by_ap_id(ap_id)
1432 if should_fetch_initial do
1433 with {:ok, %User{} = user} <- resp do
1434 fetch_initial_posts(user)
1442 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1443 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1444 with %User{} = user <- get_cached_by_ap_id(uri) do
1450 |> cast(%{}, [:ap_id, :nickname, :local])
1451 |> put_change(:ap_id, uri)
1452 |> put_change(:nickname, nickname)
1453 |> put_change(:local, true)
1454 |> put_change(:follower_address, uri <> "/followers")
1462 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1465 |> :public_key.pem_decode()
1467 |> :public_key.pem_entry_decode()
1472 def public_key(_), do: {:error, "not found key"}
1474 def get_public_key_for_ap_id(ap_id) do
1475 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1476 {:ok, public_key} <- public_key(user) do
1483 defp blank?(""), do: nil
1484 defp blank?(n), do: n
1486 def insert_or_update_user(data) do
1488 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1489 |> remote_user_creation()
1490 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1494 def ap_enabled?(%User{local: true}), do: true
1495 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1496 def ap_enabled?(_), do: false
1498 @doc "Gets or fetch a user by uri or nickname."
1499 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1500 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1501 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1503 # wait a period of time and return newest version of the User structs
1504 # this is because we have synchronous follow APIs and need to simulate them
1505 # with an async handshake
1506 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1507 with %User{} = a <- get_cached_by_id(a.id),
1508 %User{} = b <- get_cached_by_id(b.id) do
1515 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1516 with :ok <- :timer.sleep(timeout),
1517 %User{} = a <- get_cached_by_id(a.id),
1518 %User{} = b <- get_cached_by_id(b.id) do
1525 def parse_bio(bio) when is_binary(bio) and bio != "" do
1527 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1531 def parse_bio(_), do: ""
1533 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1534 # TODO: get profile URLs other than user.ap_id
1535 profile_urls = [user.ap_id]
1538 |> CommonUtils.format_input("text/plain",
1539 mentions_format: :full,
1540 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1545 def parse_bio(_, _), do: ""
1547 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1548 Repo.transaction(fn ->
1549 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1553 def tag(nickname, tags) when is_binary(nickname),
1554 do: tag(get_by_nickname(nickname), tags)
1556 def tag(%User{} = user, tags),
1557 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1559 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1560 Repo.transaction(fn ->
1561 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1565 def untag(nickname, tags) when is_binary(nickname),
1566 do: untag(get_by_nickname(nickname), tags)
1568 def untag(%User{} = user, tags),
1569 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1571 defp update_tags(%User{} = user, new_tags) do
1572 {:ok, updated_user} =
1574 |> change(%{tags: new_tags})
1575 |> update_and_set_cache()
1580 defp normalize_tags(tags) do
1583 |> Enum.map(&String.downcase/1)
1586 defp local_nickname_regex do
1587 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1588 @extended_local_nickname_regex
1590 @strict_local_nickname_regex
1594 def local_nickname(nickname_or_mention) do
1597 |> String.split("@")
1601 def full_nickname(nickname_or_mention),
1602 do: String.trim_leading(nickname_or_mention, "@")
1604 def error_user(ap_id) do
1608 nickname: "erroruser@example.com",
1609 inserted_at: NaiveDateTime.utc_now()
1613 @spec all_superusers() :: [User.t()]
1614 def all_superusers do
1615 User.Query.build(%{super_users: true, local: true, deactivated: false})
1619 def showing_reblogs?(%User{} = user, %User{} = target) do
1620 not UserRelationship.reblog_mute_exists?(user, target)
1624 The function returns a query to get users with no activity for given interval of days.
1625 Inactive users are those who didn't read any notification, or had any activity where
1626 the user is the activity's actor, during `inactivity_threshold` days.
1627 Deactivated users will not appear in this list.
1631 iex> Pleroma.User.list_inactive_users()
1634 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1635 def list_inactive_users_query(inactivity_threshold \\ 7) do
1636 negative_inactivity_threshold = -inactivity_threshold
1637 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1638 # Subqueries are not supported in `where` clauses, join gets too complicated.
1639 has_read_notifications =
1640 from(n in Pleroma.Notification,
1641 where: n.seen == true,
1643 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1646 |> Pleroma.Repo.all()
1648 from(u in Pleroma.User,
1649 left_join: a in Pleroma.Activity,
1650 on: u.ap_id == a.actor,
1651 where: not is_nil(u.nickname),
1652 where: u.deactivated != ^true,
1653 where: u.id not in ^has_read_notifications,
1656 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1657 is_nil(max(a.inserted_at))
1662 Enable or disable email notifications for user
1666 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1667 Pleroma.User{email_notifications: %{"digest" => true}}
1669 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1670 Pleroma.User{email_notifications: %{"digest" => false}}
1672 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1673 {:ok, t()} | {:error, Ecto.Changeset.t()}
1674 def switch_email_notifications(user, type, status) do
1675 User.update_email_notifications(user, %{type => status})
1679 Set `last_digest_emailed_at` value for the user to current time
1681 @spec touch_last_digest_emailed_at(t()) :: t()
1682 def touch_last_digest_emailed_at(user) do
1683 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1685 {:ok, updated_user} =
1687 |> change(%{last_digest_emailed_at: now})
1688 |> update_and_set_cache()
1693 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1694 def toggle_confirmation(%User{} = user) do
1696 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1697 |> update_and_set_cache()
1700 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1704 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1705 # use instance-default
1706 config = Pleroma.Config.get([:assets, :mascots])
1707 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1708 mascot = Keyword.get(config, default_mascot)
1711 "id" => "default-mascot",
1712 "url" => mascot[:url],
1713 "preview_url" => mascot[:url],
1715 "mime_type" => mascot[:mime_type]
1720 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1722 def ensure_keys_present(%User{} = user) do
1723 with {:ok, pem} <- Keys.generate_rsa_pem() do
1725 |> cast(%{keys: pem}, [:keys])
1726 |> validate_required([:keys])
1727 |> update_and_set_cache()
1731 def get_ap_ids_by_nicknames(nicknames) do
1733 where: u.nickname in ^nicknames,
1739 defdelegate search(query, opts \\ []), to: User.Search
1741 defp put_password_hash(
1742 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1744 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1747 defp put_password_hash(changeset), do: changeset
1749 def is_internal_user?(%User{nickname: nil}), do: true
1750 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1751 def is_internal_user?(_), do: false
1753 # A hack because user delete activities have a fake id for whatever reason
1754 # TODO: Get rid of this
1755 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1757 def get_delivered_users_by_object_id(object_id) do
1759 inner_join: delivery in assoc(u, :deliveries),
1760 where: delivery.object_id == ^object_id
1765 def change_email(user, email) do
1767 |> cast(%{email: email}, [:email])
1768 |> validate_required([:email])
1769 |> unique_constraint(:email)
1770 |> validate_format(:email, @email_regex)
1771 |> update_and_set_cache()
1774 # Internal function; public one is `deactivate/2`
1775 defp set_activation_status(user, deactivated) do
1777 |> cast(%{deactivated: deactivated}, [:deactivated])
1778 |> update_and_set_cache()
1781 def update_banner(user, banner) do
1783 |> cast(%{banner: banner}, [:banner])
1784 |> update_and_set_cache()
1787 def update_background(user, background) do
1789 |> cast(%{background: background}, [:background])
1790 |> update_and_set_cache()
1793 def update_source_data(user, source_data) do
1795 |> cast(%{source_data: source_data}, [:source_data])
1796 |> update_and_set_cache()
1799 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1802 moderator: is_moderator
1806 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1807 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1808 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1809 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1812 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1813 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1817 def fields(%{fields: nil}), do: []
1819 def fields(%{fields: fields}), do: fields
1821 def validate_fields(changeset, remote? \\ false) do
1822 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1823 limit = Pleroma.Config.get([:instance, limit_name], 0)
1826 |> validate_length(:fields, max: limit)
1827 |> validate_change(:fields, fn :fields, fields ->
1828 if Enum.all?(fields, &valid_field?/1) do
1836 defp valid_field?(%{"name" => name, "value" => value}) do
1837 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1838 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1840 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1841 String.length(value) <= value_limit
1844 defp valid_field?(_), do: false
1846 defp truncate_field(%{"name" => name, "value" => value}) do
1848 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1851 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1853 %{"name" => name, "value" => value}
1856 def admin_api_update(user, params) do
1863 |> update_and_set_cache()
1866 def mascot_update(user, url) do
1868 |> cast(%{mascot: url}, [:mascot])
1869 |> validate_required([:mascot])
1870 |> update_and_set_cache()
1873 def mastodon_settings_update(user, settings) do
1875 |> cast(%{settings: settings}, [:settings])
1876 |> validate_required([:settings])
1877 |> update_and_set_cache()
1880 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1881 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1883 if need_confirmation? do
1885 confirmation_pending: true,
1886 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1890 confirmation_pending: false,
1891 confirmation_token: nil
1895 cast(user, params, [:confirmation_pending, :confirmation_token])
1898 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1899 if id not in user.pinned_activities do
1900 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1901 params = %{pinned_activities: user.pinned_activities ++ [id]}
1904 |> cast(params, [:pinned_activities])
1905 |> validate_length(:pinned_activities,
1906 max: max_pinned_statuses,
1907 message: "You have already pinned the maximum number of statuses"
1912 |> update_and_set_cache()
1915 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1916 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1919 |> cast(params, [:pinned_activities])
1920 |> update_and_set_cache()
1923 def update_email_notifications(user, settings) do
1924 email_notifications =
1925 user.email_notifications
1926 |> Map.merge(settings)
1927 |> Map.take(["digest"])
1929 params = %{email_notifications: email_notifications}
1930 fields = [:email_notifications]
1933 |> cast(params, fields)
1934 |> validate_required(fields)
1935 |> update_and_set_cache()
1938 defp set_domain_blocks(user, domain_blocks) do
1939 params = %{domain_blocks: domain_blocks}
1942 |> cast(params, [:domain_blocks])
1943 |> validate_required([:domain_blocks])
1944 |> update_and_set_cache()
1947 def block_domain(user, domain_blocked) do
1948 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1951 def unblock_domain(user, domain_blocked) do
1952 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1955 @spec add_to_block(User.t(), User.t()) ::
1956 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
1957 defp add_to_block(%User{} = user, %User{} = blocked) do
1958 UserRelationship.create_block(user, blocked)
1961 @spec add_to_block(User.t(), User.t()) ::
1962 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
1963 defp remove_from_block(%User{} = user, %User{} = blocked) do
1964 UserRelationship.delete_block(user, blocked)
1967 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
1968 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
1969 {:ok, user_notification_mute} <-
1970 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
1972 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
1976 defp remove_from_mutes(user, %User{} = muted_user) do
1977 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
1978 {:ok, user_notification_mute} <-
1979 UserRelationship.delete_notification_mute(user, muted_user) do
1980 {:ok, [user_mute, user_notification_mute]}
1984 def set_invisible(user, invisible) do
1985 params = %{invisible: invisible}
1988 |> cast(params, [:invisible])
1989 |> validate_required([:invisible])
1990 |> update_and_set_cache()