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
13 alias Pleroma.Activity
14 alias Pleroma.Conversation.Participation
15 alias Pleroma.Delivery
17 alias Pleroma.Notification
19 alias Pleroma.Registration
21 alias Pleroma.RepoStreamer
24 alias Pleroma.Web.ActivityPub.ActivityPub
25 alias Pleroma.Web.ActivityPub.Utils
26 alias Pleroma.Web.CommonAPI
27 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
28 alias Pleroma.Web.OAuth
29 alias Pleroma.Web.OStatus
30 alias Pleroma.Web.RelMe
31 alias Pleroma.Web.Websub
32 alias Pleroma.Workers.BackgroundWorker
36 @type t :: %__MODULE__{}
38 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
40 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
41 @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])?)*$/
43 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
44 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
48 field(:email, :string)
50 field(:nickname, :string)
51 field(:password_hash, :string)
52 field(:password, :string, virtual: true)
53 field(:password_confirmation, :string, virtual: true)
55 field(:following, {:array, :string}, default: [])
56 field(:ap_id, :string)
58 field(:local, :boolean, default: true)
59 field(:follower_address, :string)
60 field(:following_address, :string)
61 field(:search_rank, :float, virtual: true)
62 field(:search_type, :integer, virtual: true)
63 field(:tags, {:array, :string}, default: [])
64 field(:last_refreshed_at, :naive_datetime_usec)
65 field(:last_digest_emailed_at, :naive_datetime)
67 field(:banner, :map, default: %{})
68 field(:background, :map, default: %{})
69 field(:source_data, :map, default: %{})
70 field(:note_count, :integer, default: 0)
71 field(:follower_count, :integer, default: 0)
72 # Should be filled in only for remote users
73 field(:following_count, :integer, default: nil)
74 field(:locked, :boolean, default: false)
75 field(:confirmation_pending, :boolean, default: false)
76 field(:password_reset_pending, :boolean, default: false)
77 field(:confirmation_token, :string, default: nil)
78 field(:default_scope, :string, default: "public")
79 field(:blocks, {:array, :string}, default: [])
80 field(:domain_blocks, {:array, :string}, default: [])
81 field(:mutes, {:array, :string}, default: [])
82 field(:muted_reblogs, {:array, :string}, default: [])
83 field(:muted_notifications, {:array, :string}, default: [])
84 field(:subscribers, {:array, :string}, default: [])
85 field(:deactivated, :boolean, default: false)
86 field(:no_rich_text, :boolean, default: false)
87 field(:ap_enabled, :boolean, default: false)
88 field(:is_moderator, :boolean, default: false)
89 field(:is_admin, :boolean, default: false)
90 field(:show_role, :boolean, default: true)
91 field(:settings, :map, default: nil)
92 field(:magic_key, :string, default: nil)
93 field(:uri, :string, default: nil)
94 field(:topic, :string, default: nil)
95 field(:hub, :string, default: nil)
96 field(:salmon, :string, default: nil)
97 field(:hide_followers_count, :boolean, default: false)
98 field(:hide_follows_count, :boolean, default: false)
99 field(:hide_followers, :boolean, default: false)
100 field(:hide_follows, :boolean, default: false)
101 field(:hide_favorites, :boolean, default: true)
102 field(:unread_conversation_count, :integer, default: 0)
103 field(:pinned_activities, {:array, :string}, default: [])
104 field(:email_notifications, :map, default: %{"digest" => false})
105 field(:mascot, :map, default: nil)
106 field(:emoji, {:array, :map}, default: [])
107 field(:pleroma_settings_store, :map, default: %{})
108 field(:fields, {:array, :map}, default: nil)
109 field(:raw_fields, {:array, :map}, default: [])
110 field(:discoverable, :boolean, default: false)
111 field(:skip_thread_containment, :boolean, default: false)
113 field(:notification_settings, :map,
117 "non_follows" => true,
118 "non_followers" => true
122 has_many(:notifications, Notification)
123 has_many(:registrations, Registration)
124 has_many(:deliveries, Delivery)
125 embeds_one(:info, User.Info)
130 def auth_active?(%User{confirmation_pending: true}),
131 do: !Pleroma.Config.get([:instance, :account_activation_required])
133 def auth_active?(%User{}), do: true
135 def visible_for?(user, for_user \\ nil)
137 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
139 def visible_for?(%User{} = user, for_user) do
140 auth_active?(user) || superuser?(for_user)
143 def visible_for?(_, _), do: false
145 def superuser?(%User{local: true, is_admin: true}), do: true
146 def superuser?(%User{local: true, is_moderator: true}), do: true
147 def superuser?(_), do: false
149 def avatar_url(user, options \\ []) do
151 %{"url" => [%{"href" => href} | _]} -> href
152 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
156 def banner_url(user, options \\ []) do
158 %{"url" => [%{"href" => href} | _]} -> href
159 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
163 def profile_url(%User{source_data: %{"url" => url}}), do: url
164 def profile_url(%User{ap_id: ap_id}), do: ap_id
165 def profile_url(_), do: nil
167 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
169 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
170 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
172 @spec ap_following(User.t()) :: Sring.t()
173 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
174 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
176 def user_info(%User{} = user, args \\ %{}) do
178 Map.get(args, :following_count, user.following_count || following_count(user))
180 follower_count = Map.get(args, :follower_count, user.follower_count)
183 note_count: user.note_count,
185 confirmation_pending: user.confirmation_pending,
186 default_scope: user.default_scope
188 |> Map.put(:following_count, following_count)
189 |> Map.put(:follower_count, follower_count)
192 def follow_state(%User{} = user, %User{} = target) do
193 case Utils.fetch_latest_follow(user, target) do
194 %{data: %{"state" => state}} -> state
195 # Ideally this would be nil, but then Cachex does not commit the value
200 def get_cached_follow_state(user, target) do
201 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
202 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
205 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
206 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
207 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
210 def set_info_cache(user, args) do
211 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
214 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
215 def restrict_deactivated(query) do
216 from(u in query, where: u.deactivated != ^true)
219 def following_count(%User{following: []}), do: 0
221 def following_count(%User{} = user) do
223 |> get_friends_query()
224 |> Repo.aggregate(:count, :id)
235 :confirmation_pending,
236 :password_reset_pending,
243 :muted_notifications,
257 :hide_followers_count,
262 :unread_conversation_count,
264 :email_notifications,
267 :pleroma_settings_store,
271 :skip_thread_containment,
272 :notification_settings
275 def info_fields, do: @info_fields
277 defp truncate_fields_param(params) do
278 if Map.has_key?(params, :fields) do
279 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
285 defp truncate_if_exists(params, key, max_length) do
286 if Map.has_key?(params, key) and is_binary(params[key]) do
287 {value, _chopped} = String.split_at(params[key], max_length)
288 Map.put(params, key, value)
294 def remote_user_creation(params) do
295 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
296 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
300 |> Map.put(:info, params[:info] || %{})
301 |> truncate_if_exists(:name, name_limit)
302 |> truncate_if_exists(:bio, bio_limit)
303 |> truncate_fields_param()
307 |> cast(params, [:bio, :name, :ap_id, :nickname, :avatar] ++ @info_fields)
308 |> validate_required([:name, :ap_id])
309 |> unique_constraint(:nickname)
310 |> validate_format(:nickname, @email_regex)
311 |> validate_length(:bio, max: bio_limit)
312 |> validate_length(:name, max: name_limit)
313 |> validate_fields(true)
316 case params[:source_data] do
317 %{"followers" => followers, "following" => following} ->
319 |> put_change(:follower_address, followers)
320 |> put_change(:following_address, following)
323 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
324 put_change(changeset, :follower_address, followers)
328 def update_changeset(struct, params \\ %{}) do
329 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
330 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
333 |> cast(params, [:bio, :name, :avatar, :following] ++ @info_fields)
334 |> unique_constraint(:nickname)
335 |> validate_format(:nickname, local_nickname_regex())
336 |> validate_length(:bio, max: bio_limit)
337 |> validate_length(:name, min: 1, max: name_limit)
338 |> validate_fields(false)
341 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
342 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
343 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
345 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
347 params = if remote?, do: truncate_fields_param(params), else: params
361 |> unique_constraint(:nickname)
362 |> validate_format(:nickname, local_nickname_regex())
363 |> validate_length(:bio, max: bio_limit)
364 |> validate_length(:name, max: name_limit)
365 |> validate_fields(remote?)
369 def password_update_changeset(struct, params) do
371 |> cast(params, [:password, :password_confirmation])
372 |> validate_required([:password, :password_confirmation])
373 |> validate_confirmation(:password)
374 |> put_password_hash()
375 |> put_change(:password_reset_pending, false)
378 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
379 def reset_password(%User{id: user_id} = user, data) do
382 |> Multi.update(:user, password_update_changeset(user, data))
383 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
384 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
386 case Repo.transaction(multi) do
387 {:ok, %{user: user} = _} -> set_cache(user)
388 {:error, _, changeset, _} -> {:error, changeset}
392 def update_password_reset_pending(user, value) do
395 |> put_change(:password_reset_pending, value)
396 |> update_and_set_cache()
399 def force_password_reset_async(user) do
400 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
403 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
404 def force_password_reset(user), do: update_password_reset_pending(user, true)
406 def register_changeset(struct, params \\ %{}, opts \\ []) do
407 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
408 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
411 if is_nil(opts[:need_confirmation]) do
412 Pleroma.Config.get([:instance, :account_activation_required])
414 opts[:need_confirmation]
418 |> confirmation_changeset(need_confirmation: need_confirmation?)
419 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
420 |> validate_required([:name, :nickname, :password, :password_confirmation])
421 |> validate_confirmation(:password)
422 |> unique_constraint(:email)
423 |> unique_constraint(:nickname)
424 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
425 |> validate_format(:nickname, local_nickname_regex())
426 |> validate_format(:email, @email_regex)
427 |> validate_length(:bio, max: bio_limit)
428 |> validate_length(:name, min: 1, max: name_limit)
430 |> maybe_validate_required_email(opts[:external])
433 |> unique_constraint(:ap_id)
434 |> put_following_and_follower_address()
437 def maybe_validate_required_email(changeset, true), do: changeset
438 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
440 defp put_ap_id(changeset) do
441 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
442 put_change(changeset, :ap_id, ap_id)
445 defp put_following_and_follower_address(changeset) do
446 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
449 |> put_change(:following, [followers])
450 |> put_change(:follower_address, followers)
453 defp autofollow_users(user) do
454 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
457 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
460 follow_all(user, autofollowed_users)
463 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
464 def register(%Ecto.Changeset{} = changeset) do
465 with {:ok, user} <- Repo.insert(changeset) do
466 post_register_action(user)
470 def post_register_action(%User{} = user) do
471 with {:ok, user} <- autofollow_users(user),
472 {:ok, user} <- set_cache(user),
473 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
474 {:ok, _} <- try_send_confirmation_email(user) do
479 def try_send_confirmation_email(%User{} = user) do
480 if user.confirmation_pending &&
481 Pleroma.Config.get([:instance, :account_activation_required]) do
483 |> Pleroma.Emails.UserEmail.account_confirmation_email()
484 |> Pleroma.Emails.Mailer.deliver_async()
492 def needs_update?(%User{local: true}), do: false
494 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
496 def needs_update?(%User{local: false} = user) do
497 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
500 def needs_update?(_), do: true
502 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
503 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true}) do
507 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
508 follow(follower, followed)
511 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
512 if not ap_enabled?(followed) do
513 follow(follower, followed)
519 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
520 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
521 def follow_all(follower, followeds) do
524 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
525 |> Enum.map(fn %{follower_address: fa} -> fa end)
529 where: u.id == ^follower.id,
534 "array(select distinct unnest (array_cat(?, ?)))",
543 {1, [follower]} = Repo.update_all(q, [])
545 Enum.each(followeds, &update_follower_count/1)
550 def follow(%User{} = follower, %User{} = followed) do
551 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
552 ap_followers = followed.follower_address
555 followed.deactivated ->
556 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
558 deny_follow_blocked and blocks?(followed, follower) ->
559 {:error, "Could not follow user: #{followed.nickname} blocked you."}
562 if !followed.local && follower.local && !ap_enabled?(followed) do
563 Websub.subscribe(follower, followed)
568 where: u.id == ^follower.id,
569 update: [push: [following: ^ap_followers]],
573 {1, [follower]} = Repo.update_all(q, [])
575 follower = maybe_update_following_count(follower)
577 {:ok, _} = update_follower_count(followed)
583 def unfollow(%User{} = follower, %User{} = followed) do
584 ap_followers = followed.follower_address
586 if following?(follower, followed) and follower.ap_id != followed.ap_id do
589 where: u.id == ^follower.id,
590 update: [pull: [following: ^ap_followers]],
594 {1, [follower]} = Repo.update_all(q, [])
596 follower = maybe_update_following_count(follower)
598 {:ok, followed} = update_follower_count(followed)
602 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
604 {:error, "Not subscribed!"}
608 @spec following?(User.t(), User.t()) :: boolean
609 def following?(%User{} = follower, %User{} = followed) do
610 Enum.member?(follower.following, followed.follower_address)
613 def locked?(%User{} = user) do
618 Repo.get_by(User, id: id)
621 def get_by_ap_id(ap_id) do
622 Repo.get_by(User, ap_id: ap_id)
625 def get_all_by_ap_id(ap_ids) do
626 from(u in __MODULE__,
627 where: u.ap_id in ^ap_ids
632 def get_all_by_ids(ids) do
633 from(u in __MODULE__, where: u.id in ^ids)
637 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
638 # of the ap_id and the domain and tries to get that user
639 def get_by_guessed_nickname(ap_id) do
640 domain = URI.parse(ap_id).host
641 name = List.last(String.split(ap_id, "/"))
642 nickname = "#{name}@#{domain}"
644 get_cached_by_nickname(nickname)
647 def set_cache({:ok, user}), do: set_cache(user)
648 def set_cache({:error, err}), do: {:error, err}
650 def set_cache(%User{} = user) do
651 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
652 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
653 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
657 def update_and_set_cache(struct, params) do
659 |> update_changeset(params)
660 |> update_and_set_cache()
663 def update_and_set_cache(changeset) do
664 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
669 def invalidate_cache(user) do
670 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
671 Cachex.del(:user_cache, "nickname:#{user.nickname}")
672 Cachex.del(:user_cache, "user_info:#{user.id}")
675 def get_cached_by_ap_id(ap_id) do
676 key = "ap_id:#{ap_id}"
677 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
680 def get_cached_by_id(id) do
684 Cachex.fetch!(:user_cache, key, fn _ ->
688 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
689 {:commit, user.ap_id}
695 get_cached_by_ap_id(ap_id)
698 def get_cached_by_nickname(nickname) do
699 key = "nickname:#{nickname}"
701 Cachex.fetch!(:user_cache, key, fn ->
702 case get_or_fetch_by_nickname(nickname) do
703 {:ok, user} -> {:commit, user}
704 {:error, _error} -> {:ignore, nil}
709 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
710 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
713 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
714 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
716 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
717 get_cached_by_nickname(nickname_or_id)
719 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
720 get_cached_by_nickname(nickname_or_id)
727 def get_by_nickname(nickname) do
728 Repo.get_by(User, nickname: nickname) ||
729 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
730 Repo.get_by(User, nickname: local_nickname(nickname))
734 def get_by_email(email), do: Repo.get_by(User, email: email)
736 def get_by_nickname_or_email(nickname_or_email) do
737 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
740 def get_cached_user_info(user) do
741 key = "user_info:#{user.id}"
742 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
745 def fetch_by_nickname(nickname) do
746 case ActivityPub.make_user_from_nickname(nickname) do
747 {:ok, user} -> {:ok, user}
748 _ -> OStatus.make_user(nickname)
752 def get_or_fetch_by_nickname(nickname) do
753 with %User{} = user <- get_by_nickname(nickname) do
757 with [_nick, _domain] <- String.split(nickname, "@"),
758 {:ok, user} <- fetch_by_nickname(nickname) do
759 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
760 fetch_initial_posts(user)
765 _e -> {:error, "not found " <> nickname}
770 @doc "Fetch some posts when the user has just been federated with"
771 def fetch_initial_posts(user) do
772 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
775 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
776 def get_followers_query(%User{} = user, nil) do
777 User.Query.build(%{followers: user, deactivated: false})
780 def get_followers_query(user, page) do
782 |> get_followers_query(nil)
783 |> User.Query.paginate(page, 20)
786 @spec get_followers_query(User.t()) :: Ecto.Query.t()
787 def get_followers_query(user), do: get_followers_query(user, nil)
789 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
790 def get_followers(user, page \\ nil) do
792 |> get_followers_query(page)
796 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
797 def get_external_followers(user, page \\ nil) do
799 |> get_followers_query(page)
800 |> User.Query.build(%{external: true})
804 def get_followers_ids(user, page \\ nil) do
806 |> get_followers_query(page)
811 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
812 def get_friends_query(%User{} = user, nil) do
813 User.Query.build(%{friends: user, deactivated: false})
816 def get_friends_query(user, page) do
818 |> get_friends_query(nil)
819 |> User.Query.paginate(page, 20)
822 @spec get_friends_query(User.t()) :: Ecto.Query.t()
823 def get_friends_query(user), do: get_friends_query(user, nil)
825 def get_friends(user, page \\ nil) do
827 |> get_friends_query(page)
831 def get_friends_ids(user, page \\ nil) do
833 |> get_friends_query(page)
838 @spec get_follow_requests(User.t()) :: {:ok, [User.t()]}
839 def get_follow_requests(%User{} = user) do
841 |> Activity.follow_requests_for_actor()
842 |> join(:inner, [a], u in User, on: a.actor == u.ap_id)
843 |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
844 |> group_by([a, u], u.id)
849 def increase_note_count(%User{} = user) do
851 |> where(id: ^user.id)
852 |> update([u], inc: [note_count: 1])
854 |> Repo.update_all([])
856 {1, [user]} -> set_cache(user)
861 def decrease_note_count(%User{} = user) do
863 |> where(id: ^user.id)
866 note_count: fragment("greatest(0, note_count - 1)")
870 |> Repo.update_all([])
872 {1, [user]} -> set_cache(user)
877 def update_note_count(%User{} = user, note_count \\ nil) do
882 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
887 update_and_set_cache(user, %{note_count: note_count})
890 @spec maybe_fetch_follow_information(User.t()) :: User.t()
891 def maybe_fetch_follow_information(user) do
892 with {:ok, user} <- fetch_follow_information(user) do
896 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
902 def fetch_follow_information(user) do
903 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
905 |> follow_information_changeset(info)
906 |> update_and_set_cache()
910 defp follow_information_changeset(user, params) do
917 :hide_followers_count,
922 def update_follower_count(%User{} = user) do
923 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
924 follower_count_query =
925 User.Query.build(%{followers: user, deactivated: false})
926 |> select([u], %{count: count(u.id)})
929 |> where(id: ^user.id)
930 |> join(:inner, [u], s in subquery(follower_count_query))
932 set: [follower_count: s.count]
935 |> Repo.update_all([])
937 {1, [user]} -> set_cache(user)
941 {:ok, maybe_fetch_follow_information(user)}
945 @spec maybe_update_following_count(User.t()) :: User.t()
946 def maybe_update_following_count(%User{local: false} = user) do
947 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
948 maybe_fetch_follow_information(user)
954 def maybe_update_following_count(user), do: user
956 def set_unread_conversation_count(%User{local: true} = user) do
957 unread_query = Participation.unread_conversation_count_for_user(user)
960 |> join(:inner, [u], p in subquery(unread_query))
962 set: [unread_conversation_count: p.count]
964 |> where([u], u.id == ^user.id)
966 |> Repo.update_all([])
968 {1, [user]} -> set_cache(user)
973 def set_unread_conversation_count(_), do: :noop
975 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
977 Participation.unread_conversation_count_for_user(user)
978 |> where([p], p.conversation_id == ^conversation.id)
981 |> join(:inner, [u], p in subquery(unread_query))
983 inc: [unread_conversation_count: 1]
985 |> where([u], u.id == ^user.id)
986 |> where([u, p], p.count == 0)
988 |> Repo.update_all([])
990 {1, [user]} -> set_cache(user)
995 def increment_unread_conversation_count(_, _), do: :noop
997 def remove_duplicated_following(%User{following: following} = user) do
998 uniq_following = Enum.uniq(following)
1000 if length(following) == length(uniq_following) do
1004 |> update_changeset(%{following: uniq_following})
1005 |> update_and_set_cache()
1009 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1010 def get_users_from_set(ap_ids, local_only \\ true) do
1011 criteria = %{ap_id: ap_ids, deactivated: false}
1012 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1014 User.Query.build(criteria)
1018 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1019 def get_recipients_from_activity(%Activity{recipients: to}) do
1020 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1024 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
1025 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
1026 update_info(muter, &User.Info.add_to_mutes(&1, ap_id, notifications?))
1029 def unmute(muter, %{ap_id: ap_id}) do
1030 update_info(muter, &User.Info.remove_from_mutes(&1, ap_id))
1033 def subscribe(subscriber, %{ap_id: ap_id}) do
1034 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
1035 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1037 if blocks?(subscribed, subscriber) and deny_follow_blocked do
1038 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
1040 User.add_to_subscribers(subscribed, subscriber.ap_id)
1045 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
1046 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1047 User.remove_from_subscribers(user, unsubscriber.ap_id)
1051 def block(blocker, %User{ap_id: ap_id} = blocked) do
1052 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1054 if following?(blocker, blocked) do
1055 {:ok, blocker, _} = unfollow(blocker, blocked)
1061 # clear any requested follows as well
1063 case CommonAPI.reject_follow_request(blocked, blocker) do
1064 {:ok, %User{} = updated_blocked} -> updated_blocked
1069 if subscribed_to?(blocked, blocker) do
1070 {:ok, blocker} = unsubscribe(blocked, blocker)
1076 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1078 {:ok, blocker} = update_follower_count(blocker)
1080 update_info(blocker, &User.Info.add_to_block(&1, ap_id))
1083 # helper to handle the block given only an actor's AP id
1084 def block(blocker, %{ap_id: ap_id}) do
1085 block(blocker, get_cached_by_ap_id(ap_id))
1088 def unblock(blocker, %{ap_id: ap_id}) do
1089 update_info(blocker, &User.Info.remove_from_block(&1, ap_id))
1092 def mutes?(nil, _), do: false
1093 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
1095 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1096 def muted_notifications?(nil, _), do: false
1098 def muted_notifications?(user, %{ap_id: ap_id}),
1099 do: Enum.member?(user.info.muted_notifications, ap_id)
1101 def blocks?(%User{} = user, %User{} = target) do
1102 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1105 def blocks?(nil, _), do: false
1107 def blocks_ap_id?(%User{} = user, %User{} = target) do
1108 Enum.member?(user.info.blocks, target.ap_id)
1111 def blocks_ap_id?(_, _), do: false
1113 def blocks_domain?(%User{} = user, %User{} = target) do
1114 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks)
1115 %{host: host} = URI.parse(target.ap_id)
1116 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1119 def blocks_domain?(_, _), do: false
1121 def subscribed_to?(user, %{ap_id: ap_id}) do
1122 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1123 Enum.member?(target.subscribers, user.ap_id)
1127 @spec muted_users(User.t()) :: [User.t()]
1128 def muted_users(user) do
1129 User.Query.build(%{ap_id: user.info.mutes, deactivated: false})
1133 @spec blocked_users(User.t()) :: [User.t()]
1134 def blocked_users(user) do
1135 User.Query.build(%{ap_id: user.info.blocks, deactivated: false})
1139 @spec subscribers(User.t()) :: [User.t()]
1140 def subscribers(user) do
1141 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1145 def block_domain(user, domain) do
1146 update_info(user, &User.Info.add_to_domain_block(&1, domain))
1149 def unblock_domain(user, domain) do
1150 update_info(user, &User.Info.remove_from_domain_block(&1, domain))
1153 def deactivate_async(user, status \\ true) do
1154 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1157 def deactivate(%User{} = user, status \\ true) do
1158 with {:ok, user} <- set_activation_status(user, status) do
1159 Enum.each(get_followers(user), &invalidate_cache/1)
1160 Enum.each(get_friends(user), &update_follower_count/1)
1166 def update_notification_settings(%User{} = user, settings) do
1169 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1172 notification_settings =
1173 user.notification_settings
1174 |> Map.merge(settings)
1175 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1177 params = %{notification_settings: notification_settings}
1180 |> cast(params, [:notification_settings])
1181 |> validate_required([:notification_settings])
1182 |> update_and_set_cache()
1185 def delete(%User{} = user) do
1186 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1189 def perform(:force_password_reset, user), do: force_password_reset(user)
1191 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1192 def perform(:delete, %User{} = user) do
1193 {:ok, _user} = ActivityPub.delete(user)
1195 # Remove all relationships
1198 |> Enum.each(fn follower ->
1199 ActivityPub.unfollow(follower, user)
1200 unfollow(follower, user)
1205 |> Enum.each(fn followed ->
1206 ActivityPub.unfollow(user, followed)
1207 unfollow(user, followed)
1210 delete_user_activities(user)
1211 invalidate_cache(user)
1215 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1216 def perform(:fetch_initial_posts, %User{} = user) do
1217 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1219 # Insert all the posts in reverse order, so they're in the right order on the timeline
1220 user.source_data["outbox"]
1221 |> Utils.fetch_ordered_collection(pages)
1223 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1226 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1228 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1229 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1230 when is_list(blocked_identifiers) do
1232 blocked_identifiers,
1233 fn blocked_identifier ->
1234 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1235 {:ok, blocker} <- block(blocker, blocked),
1236 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1240 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1247 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1248 def perform(:follow_import, %User{} = follower, followed_identifiers)
1249 when is_list(followed_identifiers) do
1251 followed_identifiers,
1252 fn followed_identifier ->
1253 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1254 {:ok, follower} <- maybe_direct_follow(follower, followed),
1255 {:ok, _} <- ActivityPub.follow(follower, followed) do
1259 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1266 @spec external_users_query() :: Ecto.Query.t()
1267 def external_users_query do
1275 @spec external_users(keyword()) :: [User.t()]
1276 def external_users(opts \\ []) do
1278 external_users_query()
1279 |> select([u], struct(u, [:id, :ap_id, :info]))
1283 do: where(query, [u], u.id > ^opts[:max_id]),
1288 do: limit(query, ^opts[:limit]),
1294 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1295 BackgroundWorker.enqueue("blocks_import", %{
1296 "blocker_id" => blocker.id,
1297 "blocked_identifiers" => blocked_identifiers
1301 def follow_import(%User{} = follower, followed_identifiers)
1302 when is_list(followed_identifiers) do
1303 BackgroundWorker.enqueue("follow_import", %{
1304 "follower_id" => follower.id,
1305 "followed_identifiers" => followed_identifiers
1309 def delete_user_activities(%User{ap_id: ap_id}) do
1311 |> Activity.Queries.by_actor()
1312 |> RepoStreamer.chunk_stream(50)
1313 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1317 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1319 |> Object.normalize()
1320 |> ActivityPub.delete()
1323 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1324 object = Object.normalize(activity)
1327 |> get_cached_by_ap_id()
1328 |> ActivityPub.unlike(object)
1331 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1332 object = Object.normalize(activity)
1335 |> get_cached_by_ap_id()
1336 |> ActivityPub.unannounce(object)
1339 defp delete_activity(_activity), do: "Doing nothing"
1341 def html_filter_policy(%User{no_rich_text: true}) do
1342 Pleroma.HTML.Scrubber.TwitterText
1345 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1347 def fetch_by_ap_id(ap_id) do
1348 case ActivityPub.make_user_from_ap_id(ap_id) do
1353 case OStatus.make_user(ap_id) do
1354 {:ok, user} -> {:ok, user}
1355 _ -> {:error, "Could not fetch by AP id"}
1360 def get_or_fetch_by_ap_id(ap_id) do
1361 user = get_cached_by_ap_id(ap_id)
1363 if !is_nil(user) and !needs_update?(user) do
1366 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1367 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1369 resp = fetch_by_ap_id(ap_id)
1371 if should_fetch_initial do
1372 with {:ok, %User{} = user} <- resp do
1373 fetch_initial_posts(user)
1381 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1382 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1383 with %User{} = user <- get_cached_by_ap_id(uri) do
1388 %User{info: %User.Info{}}
1389 |> cast(%{}, [:ap_id, :nickname, :local])
1390 |> put_change(:ap_id, uri)
1391 |> put_change(:nickname, nickname)
1392 |> put_change(:local, true)
1393 |> put_change(:follower_address, uri <> "/followers")
1401 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1404 |> :public_key.pem_decode()
1406 |> :public_key.pem_entry_decode()
1412 def public_key(%{magic_key: magic_key}) when not is_nil(magic_key) do
1413 {:ok, Pleroma.Web.Salmon.decode_key(magic_key)}
1416 def public_key(_), do: {:error, "not found key"}
1418 def get_public_key_for_ap_id(ap_id) do
1419 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1420 {:ok, public_key} <- public_key(user) do
1427 defp blank?(""), do: nil
1428 defp blank?(n), do: n
1430 def insert_or_update_user(data) do
1432 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1433 |> remote_user_creation()
1434 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1438 def ap_enabled?(%User{local: true}), do: true
1439 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1440 def ap_enabled?(_), do: false
1442 @doc "Gets or fetch a user by uri or nickname."
1443 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1444 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1445 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1447 # wait a period of time and return newest version of the User structs
1448 # this is because we have synchronous follow APIs and need to simulate them
1449 # with an async handshake
1450 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1451 with %User{} = a <- get_cached_by_id(a.id),
1452 %User{} = b <- get_cached_by_id(b.id) do
1459 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1460 with :ok <- :timer.sleep(timeout),
1461 %User{} = a <- get_cached_by_id(a.id),
1462 %User{} = b <- get_cached_by_id(b.id) do
1469 def parse_bio(bio) when is_binary(bio) and bio != "" do
1471 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1475 def parse_bio(_), do: ""
1477 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1478 # TODO: get profile URLs other than user.ap_id
1479 profile_urls = [user.ap_id]
1482 |> CommonUtils.format_input("text/plain",
1483 mentions_format: :full,
1484 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1489 def parse_bio(_, _), do: ""
1491 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1492 Repo.transaction(fn ->
1493 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1497 def tag(nickname, tags) when is_binary(nickname),
1498 do: tag(get_by_nickname(nickname), tags)
1500 def tag(%User{} = user, tags),
1501 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1503 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1504 Repo.transaction(fn ->
1505 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1509 def untag(nickname, tags) when is_binary(nickname),
1510 do: untag(get_by_nickname(nickname), tags)
1512 def untag(%User{} = user, tags),
1513 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1515 defp update_tags(%User{} = user, new_tags) do
1516 {:ok, updated_user} =
1518 |> change(%{tags: new_tags})
1519 |> update_and_set_cache()
1524 defp normalize_tags(tags) do
1527 |> Enum.map(&String.downcase/1)
1530 defp local_nickname_regex do
1531 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1532 @extended_local_nickname_regex
1534 @strict_local_nickname_regex
1538 def local_nickname(nickname_or_mention) do
1541 |> String.split("@")
1545 def full_nickname(nickname_or_mention),
1546 do: String.trim_leading(nickname_or_mention, "@")
1548 def error_user(ap_id) do
1553 nickname: "erroruser@example.com",
1554 inserted_at: NaiveDateTime.utc_now()
1558 @spec all_superusers() :: [User.t()]
1559 def all_superusers do
1560 User.Query.build(%{super_users: true, local: true, deactivated: false})
1564 def showing_reblogs?(%User{} = user, %User{} = target) do
1565 target.ap_id not in user.info.muted_reblogs
1569 The function returns a query to get users with no activity for given interval of days.
1570 Inactive users are those who didn't read any notification, or had any activity where
1571 the user is the activity's actor, during `inactivity_threshold` days.
1572 Deactivated users will not appear in this list.
1576 iex> Pleroma.User.list_inactive_users()
1579 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1580 def list_inactive_users_query(inactivity_threshold \\ 7) do
1581 negative_inactivity_threshold = -inactivity_threshold
1582 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1583 # Subqueries are not supported in `where` clauses, join gets too complicated.
1584 has_read_notifications =
1585 from(n in Pleroma.Notification,
1586 where: n.seen == true,
1588 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1591 |> Pleroma.Repo.all()
1593 from(u in Pleroma.User,
1594 left_join: a in Pleroma.Activity,
1595 on: u.ap_id == a.actor,
1596 where: not is_nil(u.nickname),
1597 where: u.deactivated != ^true,
1598 where: u.id not in ^has_read_notifications,
1601 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1602 is_nil(max(a.inserted_at))
1607 Enable or disable email notifications for user
1611 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1612 Pleroma.User{email_notifications: %{"digest" => true}}
1614 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1615 Pleroma.User{email_notifications: %{"digest" => false}}
1617 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1618 {:ok, t()} | {:error, Ecto.Changeset.t()}
1619 def switch_email_notifications(user, type, status) do
1620 User.update_email_notifications(user, %{type => status})
1624 Set `last_digest_emailed_at` value for the user to current time
1626 @spec touch_last_digest_emailed_at(t()) :: t()
1627 def touch_last_digest_emailed_at(user) do
1628 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1630 {:ok, updated_user} =
1632 |> change(%{last_digest_emailed_at: now})
1633 |> update_and_set_cache()
1638 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1639 def toggle_confirmation(%User{} = user) do
1641 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1642 |> update_and_set_cache()
1645 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1649 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1650 # use instance-default
1651 config = Pleroma.Config.get([:assets, :mascots])
1652 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1653 mascot = Keyword.get(config, default_mascot)
1656 "id" => "default-mascot",
1657 "url" => mascot[:url],
1658 "preview_url" => mascot[:url],
1660 "mime_type" => mascot[:mime_type]
1665 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1667 def ensure_keys_present(%User{} = user) do
1668 with {:ok, pem} <- Keys.generate_rsa_pem() do
1670 |> cast(%{keys: pem}, [:keys])
1671 |> validate_required([:keys])
1672 |> update_and_set_cache()
1676 def get_ap_ids_by_nicknames(nicknames) do
1678 where: u.nickname in ^nicknames,
1684 defdelegate search(query, opts \\ []), to: User.Search
1686 defp put_password_hash(
1687 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1689 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1692 defp put_password_hash(changeset), do: changeset
1694 def is_internal_user?(%User{nickname: nil}), do: true
1695 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1696 def is_internal_user?(_), do: false
1698 # A hack because user delete activities have a fake id for whatever reason
1699 # TODO: Get rid of this
1700 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1702 def get_delivered_users_by_object_id(object_id) do
1704 inner_join: delivery in assoc(u, :deliveries),
1705 where: delivery.object_id == ^object_id
1710 def change_email(user, email) do
1712 |> cast(%{email: email}, [:email])
1713 |> validate_required([:email])
1714 |> unique_constraint(:email)
1715 |> validate_format(:email, @email_regex)
1716 |> update_and_set_cache()
1719 # Internal function; public one is `deactivate/2`
1720 defp set_activation_status(user, deactivated) do
1722 |> cast(%{deactivated: deactivated}, [:deactivated])
1723 |> update_and_set_cache()
1726 def update_banner(user, banner) do
1728 |> cast(%{banner: banner}, [:banner])
1729 |> update_and_set_cache()
1732 def update_background(user, background) do
1734 |> cast(%{background: background}, [:background])
1735 |> update_and_set_cache()
1738 def update_source_data(user, source_data) do
1740 |> cast(%{source_data: source_data}, [:source_data])
1741 |> update_and_set_cache()
1745 Changes `user.info` and returns the user changeset.
1747 `fun` is called with the `user.info`.
1749 def change_info(user, fun) do
1750 changeset = change(user)
1751 info = get_field(changeset, :info) || %User.Info{}
1752 put_embed(changeset, :info, fun.(info))
1756 Updates `user.info` and sets cache.
1758 `fun` is called with the `user.info`.
1760 def update_info(user, fun) do
1763 |> update_and_set_cache()
1766 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1769 moderator: is_moderator
1773 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1774 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1775 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1776 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1779 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1780 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1784 def fields(%{fields: nil}), do: []
1786 def fields(%{fields: fields}), do: fields
1788 def validate_fields(changeset, remote? \\ false) do
1789 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1790 limit = Pleroma.Config.get([:instance, limit_name], 0)
1793 |> validate_length(:fields, max: limit)
1794 |> validate_change(:fields, fn :fields, fields ->
1795 if Enum.all?(fields, &valid_field?/1) do
1803 defp valid_field?(%{"name" => name, "value" => value}) do
1804 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1805 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1807 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1808 String.length(value) <= value_limit
1811 defp valid_field?(_), do: false
1813 defp truncate_field(%{"name" => name, "value" => value}) do
1815 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1818 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1820 %{"name" => name, "value" => value}
1823 def admin_api_update(user, params) do
1830 |> update_and_set_cache()
1833 def mascot_update(user, url) do
1835 |> cast(%{mascot: url}, [:mascot])
1836 |> validate_required([:mascot])
1837 |> update_and_set_cache()
1840 def mastodon_settings_update(user, settings) do
1842 |> cast(%{settings: settings}, [:settings])
1843 |> validate_required([:settings])
1844 |> update_and_set_cache()
1847 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1848 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1850 if need_confirmation? do
1852 confirmation_pending: true,
1853 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1857 confirmation_pending: false,
1858 confirmation_token: nil
1862 cast(user, params, [:confirmation_pending, :confirmation_token])
1865 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1866 if id not in user.pinned_activities do
1867 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1868 params = %{pinned_activities: user.pinned_activities ++ [id]}
1871 |> cast(params, [:pinned_activities])
1872 |> validate_length(:pinned_activities,
1873 max: max_pinned_statuses,
1874 message: "You have already pinned the maximum number of statuses"
1879 |> update_and_set_cache()
1882 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1883 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1886 |> cast(params, [:pinned_activities])
1887 |> update_and_set_cache()
1890 def update_email_notifications(user, settings) do
1891 email_notifications =
1892 user.email_notifications
1893 |> Map.merge(settings)
1894 |> Map.take(["digest"])
1896 params = %{email_notifications: email_notifications}
1897 fields = [:email_notifications]
1900 |> cast(params, fields)
1901 |> validate_required(fields)
1902 |> update_and_set_cache()
1905 defp set_subscribers(user, subscribers) do
1906 params = %{subscribers: subscribers}
1909 |> cast(params, [:subscribers])
1910 |> validate_required([:subscribers])
1911 |> update_and_set_cache()
1914 def add_to_subscribers(user, subscribed) do
1915 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1918 def remove_from_subscribers(user, subscribed) do
1919 set_subscribers(user, List.delete(user.subscribers, subscribed))