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.RelMe
30 alias Pleroma.Workers.BackgroundWorker
34 @type t :: %__MODULE__{}
36 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
38 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
39 @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])?)*$/
41 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
42 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
46 field(:email, :string)
48 field(:nickname, :string)
49 field(:password_hash, :string)
50 field(:password, :string, virtual: true)
51 field(:password_confirmation, :string, virtual: true)
53 field(:following, {:array, :string}, default: [])
54 field(:ap_id, :string)
56 field(:local, :boolean, default: true)
57 field(:follower_address, :string)
58 field(:following_address, :string)
59 field(:search_rank, :float, virtual: true)
60 field(:search_type, :integer, virtual: true)
61 field(:tags, {:array, :string}, default: [])
62 field(:last_refreshed_at, :naive_datetime_usec)
63 field(:last_digest_emailed_at, :naive_datetime)
65 field(:banner, :map, default: %{})
66 field(:background, :map, default: %{})
67 field(:source_data, :map, default: %{})
68 field(:note_count, :integer, default: 0)
69 field(:follower_count, :integer, default: 0)
70 # Should be filled in only for remote users
71 field(:following_count, :integer, default: nil)
72 field(:locked, :boolean, default: false)
73 field(:confirmation_pending, :boolean, default: false)
74 field(:password_reset_pending, :boolean, default: false)
75 field(:confirmation_token, :string, default: nil)
76 field(:default_scope, :string, default: "public")
77 field(:blocks, {:array, :string}, default: [])
78 field(:domain_blocks, {:array, :string}, default: [])
79 field(:mutes, {:array, :string}, default: [])
80 field(:muted_reblogs, {:array, :string}, default: [])
81 field(:muted_notifications, {:array, :string}, default: [])
82 field(:subscribers, {:array, :string}, default: [])
83 field(:deactivated, :boolean, default: false)
84 field(:no_rich_text, :boolean, default: false)
85 field(:ap_enabled, :boolean, default: false)
86 field(:is_moderator, :boolean, default: false)
87 field(:is_admin, :boolean, default: false)
88 field(:show_role, :boolean, default: true)
89 field(:settings, :map, default: nil)
90 field(:magic_key, :string, default: nil)
91 field(:uri, :string, default: nil)
92 field(:hide_followers_count, :boolean, default: false)
93 field(:hide_follows_count, :boolean, default: false)
94 field(:hide_followers, :boolean, default: false)
95 field(:hide_follows, :boolean, default: false)
96 field(:hide_favorites, :boolean, default: true)
97 field(:unread_conversation_count, :integer, default: 0)
98 field(:pinned_activities, {:array, :string}, default: [])
99 field(:email_notifications, :map, default: %{"digest" => false})
100 field(:mascot, :map, default: nil)
101 field(:emoji, {:array, :map}, default: [])
102 field(:pleroma_settings_store, :map, default: %{})
103 field(:fields, {:array, :map}, default: [])
104 field(:raw_fields, {:array, :map}, default: [])
105 field(:discoverable, :boolean, default: false)
106 field(:invisible, :boolean, default: false)
107 field(:skip_thread_containment, :boolean, default: false)
108 field(:also_known_as, {:array, :string}, default: [])
110 field(:notification_settings, :map,
114 "non_follows" => true,
115 "non_followers" => true
119 has_many(:notifications, Notification)
120 has_many(:registrations, Registration)
121 has_many(:deliveries, Delivery)
123 field(:info, :map, default: %{})
128 def auth_active?(%User{confirmation_pending: true}),
129 do: !Pleroma.Config.get([:instance, :account_activation_required])
131 def auth_active?(%User{}), do: true
133 def visible_for?(user, for_user \\ nil)
135 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
137 def visible_for?(%User{} = user, for_user) do
138 auth_active?(user) || superuser?(for_user)
141 def visible_for?(_, _), do: false
143 def superuser?(%User{local: true, is_admin: true}), do: true
144 def superuser?(%User{local: true, is_moderator: true}), do: true
145 def superuser?(_), do: false
147 def invisible?(%User{invisible: true}), do: true
148 def invisible?(_), do: false
150 def avatar_url(user, options \\ []) do
152 %{"url" => [%{"href" => href} | _]} -> href
153 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
157 def banner_url(user, options \\ []) do
159 %{"url" => [%{"href" => href} | _]} -> href
160 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
164 def profile_url(%User{source_data: %{"url" => url}}), do: url
165 def profile_url(%User{ap_id: ap_id}), do: ap_id
166 def profile_url(_), do: nil
168 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
170 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
171 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
173 @spec ap_following(User.t()) :: Sring.t()
174 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
175 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
177 def user_info(%User{} = user, args \\ %{}) do
179 Map.get(args, :following_count, user.following_count || following_count(user))
181 follower_count = Map.get(args, :follower_count, user.follower_count)
184 note_count: user.note_count,
186 confirmation_pending: user.confirmation_pending,
187 default_scope: user.default_scope
189 |> Map.put(:following_count, following_count)
190 |> Map.put(:follower_count, follower_count)
193 def follow_state(%User{} = user, %User{} = target) do
194 case Utils.fetch_latest_follow(user, target) do
195 %{data: %{"state" => state}} -> state
196 # Ideally this would be nil, but then Cachex does not commit the value
201 def get_cached_follow_state(user, target) do
202 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
203 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
206 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
207 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
208 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
211 def set_info_cache(user, args) do
212 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
215 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
216 def restrict_deactivated(query) do
217 from(u in query, where: u.deactivated != ^true)
220 def following_count(%User{following: []}), do: 0
222 def following_count(%User{} = user) do
224 |> get_friends_query()
225 |> Repo.aggregate(:count, :id)
236 :confirmation_pending,
237 :password_reset_pending,
244 :muted_notifications,
255 :hide_followers_count,
260 :unread_conversation_count,
262 :email_notifications,
265 :pleroma_settings_store,
270 :skip_thread_containment,
271 :notification_settings
274 def info_fields, do: @info_fields
276 defp truncate_fields_param(params) do
277 if Map.has_key?(params, :fields) do
278 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
284 defp truncate_if_exists(params, key, max_length) do
285 if Map.has_key?(params, key) and is_binary(params[key]) do
286 {value, _chopped} = String.split_at(params[key], max_length)
287 Map.put(params, key, value)
293 def remote_user_creation(params) do
294 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
295 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
299 |> Map.put(:info, params[:info] || %{})
300 |> truncate_if_exists(:name, name_limit)
301 |> truncate_if_exists(:bio, bio_limit)
302 |> truncate_fields_param()
322 :hide_followers_count,
332 |> validate_required([:name, :ap_id])
333 |> unique_constraint(:nickname)
334 |> validate_format(:nickname, @email_regex)
335 |> validate_length(:bio, max: bio_limit)
336 |> validate_length(:name, max: name_limit)
337 |> validate_fields(true)
339 case params[:source_data] do
340 %{"followers" => followers, "following" => following} ->
342 |> put_change(:follower_address, followers)
343 |> put_change(:following_address, following)
346 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
347 put_change(changeset, :follower_address, followers)
351 def update_changeset(struct, params \\ %{}) do
352 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
353 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
369 :hide_followers_count,
374 :skip_thread_containment,
377 :pleroma_settings_store,
382 |> unique_constraint(:nickname)
383 |> validate_format(:nickname, local_nickname_regex())
384 |> validate_length(:bio, max: bio_limit)
385 |> validate_length(:name, min: 1, max: name_limit)
386 |> validate_fields(false)
389 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
390 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
391 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
393 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
395 params = if remote?, do: truncate_fields_param(params), else: params
418 :hide_followers_count,
423 |> unique_constraint(:nickname)
424 |> validate_format(:nickname, local_nickname_regex())
425 |> validate_length(:bio, max: bio_limit)
426 |> validate_length(:name, max: name_limit)
427 |> validate_fields(remote?)
430 def password_update_changeset(struct, params) do
432 |> cast(params, [:password, :password_confirmation])
433 |> validate_required([:password, :password_confirmation])
434 |> validate_confirmation(:password)
435 |> put_password_hash()
436 |> put_change(:password_reset_pending, false)
439 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
440 def reset_password(%User{id: user_id} = user, data) do
443 |> Multi.update(:user, password_update_changeset(user, data))
444 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
445 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
447 case Repo.transaction(multi) do
448 {:ok, %{user: user} = _} -> set_cache(user)
449 {:error, _, changeset, _} -> {:error, changeset}
453 def update_password_reset_pending(user, value) do
456 |> put_change(:password_reset_pending, value)
457 |> update_and_set_cache()
460 def force_password_reset_async(user) do
461 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
464 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
465 def force_password_reset(user), do: update_password_reset_pending(user, true)
467 def register_changeset(struct, params \\ %{}, opts \\ []) do
468 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
469 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
472 if is_nil(opts[:need_confirmation]) do
473 Pleroma.Config.get([:instance, :account_activation_required])
475 opts[:need_confirmation]
479 |> confirmation_changeset(need_confirmation: need_confirmation?)
480 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
481 |> validate_required([:name, :nickname, :password, :password_confirmation])
482 |> validate_confirmation(:password)
483 |> unique_constraint(:email)
484 |> unique_constraint(:nickname)
485 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
486 |> validate_format(:nickname, local_nickname_regex())
487 |> validate_format(:email, @email_regex)
488 |> validate_length(:bio, max: bio_limit)
489 |> validate_length(:name, min: 1, max: name_limit)
490 |> maybe_validate_required_email(opts[:external])
493 |> unique_constraint(:ap_id)
494 |> put_following_and_follower_address()
497 def maybe_validate_required_email(changeset, true), do: changeset
498 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
500 defp put_ap_id(changeset) do
501 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
502 put_change(changeset, :ap_id, ap_id)
505 defp put_following_and_follower_address(changeset) do
506 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
509 |> put_change(:following, [followers])
510 |> put_change(:follower_address, followers)
513 defp autofollow_users(user) do
514 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
517 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
520 follow_all(user, autofollowed_users)
523 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
524 def register(%Ecto.Changeset{} = changeset) do
525 with {:ok, user} <- Repo.insert(changeset) do
526 post_register_action(user)
530 def post_register_action(%User{} = user) do
531 with {:ok, user} <- autofollow_users(user),
532 {:ok, user} <- set_cache(user),
533 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
534 {:ok, _} <- try_send_confirmation_email(user) do
539 def try_send_confirmation_email(%User{} = user) do
540 if user.confirmation_pending &&
541 Pleroma.Config.get([:instance, :account_activation_required]) do
543 |> Pleroma.Emails.UserEmail.account_confirmation_email()
544 |> Pleroma.Emails.Mailer.deliver_async()
552 def needs_update?(%User{local: true}), do: false
554 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
556 def needs_update?(%User{local: false} = user) do
557 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
560 def needs_update?(_), do: true
562 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
563 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true}) do
567 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
568 follow(follower, followed)
571 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
572 if not ap_enabled?(followed) do
573 follow(follower, followed)
579 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
580 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
581 def follow_all(follower, followeds) do
584 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
585 |> Enum.map(fn %{follower_address: fa} -> fa end)
589 where: u.id == ^follower.id,
594 "array(select distinct unnest (array_cat(?, ?)))",
603 {1, [follower]} = Repo.update_all(q, [])
605 Enum.each(followeds, &update_follower_count/1)
610 def follow(%User{} = follower, %User{} = followed) do
611 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
612 ap_followers = followed.follower_address
615 followed.deactivated ->
616 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
618 deny_follow_blocked and blocks?(followed, follower) ->
619 {:error, "Could not follow user: #{followed.nickname} blocked you."}
624 where: u.id == ^follower.id,
625 update: [push: [following: ^ap_followers]],
629 {1, [follower]} = Repo.update_all(q, [])
631 follower = maybe_update_following_count(follower)
633 {:ok, _} = update_follower_count(followed)
639 def unfollow(%User{} = follower, %User{} = followed) do
640 ap_followers = followed.follower_address
642 if following?(follower, followed) and follower.ap_id != followed.ap_id do
645 where: u.id == ^follower.id,
646 update: [pull: [following: ^ap_followers]],
650 {1, [follower]} = Repo.update_all(q, [])
652 follower = maybe_update_following_count(follower)
654 {:ok, followed} = update_follower_count(followed)
658 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
660 {:error, "Not subscribed!"}
664 @spec following?(User.t(), User.t()) :: boolean
665 def following?(%User{} = follower, %User{} = followed) do
666 Enum.member?(follower.following, followed.follower_address)
669 def locked?(%User{} = user) do
674 Repo.get_by(User, id: id)
677 def get_by_ap_id(ap_id) do
678 Repo.get_by(User, ap_id: ap_id)
681 def get_all_by_ap_id(ap_ids) do
682 from(u in __MODULE__,
683 where: u.ap_id in ^ap_ids
688 def get_all_by_ids(ids) do
689 from(u in __MODULE__, where: u.id in ^ids)
693 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
694 # of the ap_id and the domain and tries to get that user
695 def get_by_guessed_nickname(ap_id) do
696 domain = URI.parse(ap_id).host
697 name = List.last(String.split(ap_id, "/"))
698 nickname = "#{name}@#{domain}"
700 get_cached_by_nickname(nickname)
703 def set_cache({:ok, user}), do: set_cache(user)
704 def set_cache({:error, err}), do: {:error, err}
706 def set_cache(%User{} = user) do
707 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
708 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
709 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
713 def update_and_set_cache(struct, params) do
715 |> update_changeset(params)
716 |> update_and_set_cache()
719 def update_and_set_cache(changeset) do
720 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
725 def invalidate_cache(user) do
726 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
727 Cachex.del(:user_cache, "nickname:#{user.nickname}")
728 Cachex.del(:user_cache, "user_info:#{user.id}")
731 def get_cached_by_ap_id(ap_id) do
732 key = "ap_id:#{ap_id}"
733 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
736 def get_cached_by_id(id) do
740 Cachex.fetch!(:user_cache, key, fn _ ->
744 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
745 {:commit, user.ap_id}
751 get_cached_by_ap_id(ap_id)
754 def get_cached_by_nickname(nickname) do
755 key = "nickname:#{nickname}"
757 Cachex.fetch!(:user_cache, key, fn ->
758 case get_or_fetch_by_nickname(nickname) do
759 {:ok, user} -> {:commit, user}
760 {:error, _error} -> {:ignore, nil}
765 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
766 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
769 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
770 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
772 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
773 get_cached_by_nickname(nickname_or_id)
775 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
776 get_cached_by_nickname(nickname_or_id)
783 def get_by_nickname(nickname) do
784 Repo.get_by(User, nickname: nickname) ||
785 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
786 Repo.get_by(User, nickname: local_nickname(nickname))
790 def get_by_email(email), do: Repo.get_by(User, email: email)
792 def get_by_nickname_or_email(nickname_or_email) do
793 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
796 def get_cached_user_info(user) do
797 key = "user_info:#{user.id}"
798 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
801 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
803 def get_or_fetch_by_nickname(nickname) do
804 with %User{} = user <- get_by_nickname(nickname) do
808 with [_nick, _domain] <- String.split(nickname, "@"),
809 {:ok, user} <- fetch_by_nickname(nickname) do
810 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
811 fetch_initial_posts(user)
816 _e -> {:error, "not found " <> nickname}
821 @doc "Fetch some posts when the user has just been federated with"
822 def fetch_initial_posts(user) do
823 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
826 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
827 def get_followers_query(%User{} = user, nil) do
828 User.Query.build(%{followers: user, deactivated: false})
831 def get_followers_query(user, page) do
833 |> get_followers_query(nil)
834 |> User.Query.paginate(page, 20)
837 @spec get_followers_query(User.t()) :: Ecto.Query.t()
838 def get_followers_query(user), do: get_followers_query(user, nil)
840 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
841 def get_followers(user, page \\ nil) do
843 |> get_followers_query(page)
847 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
848 def get_external_followers(user, page \\ nil) do
850 |> get_followers_query(page)
851 |> User.Query.build(%{external: true})
855 def get_followers_ids(user, page \\ nil) do
857 |> get_followers_query(page)
862 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
863 def get_friends_query(%User{} = user, nil) do
864 User.Query.build(%{friends: user, deactivated: false})
867 def get_friends_query(user, page) do
869 |> get_friends_query(nil)
870 |> User.Query.paginate(page, 20)
873 @spec get_friends_query(User.t()) :: Ecto.Query.t()
874 def get_friends_query(user), do: get_friends_query(user, nil)
876 def get_friends(user, page \\ nil) do
878 |> get_friends_query(page)
882 def get_friends_ids(user, page \\ nil) do
884 |> get_friends_query(page)
889 @spec get_follow_requests(User.t()) :: {:ok, [User.t()]}
890 def get_follow_requests(%User{} = user) do
892 |> Activity.follow_requests_for_actor()
893 |> join(:inner, [a], u in User, on: a.actor == u.ap_id)
894 |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
895 |> group_by([a, u], u.id)
900 def increase_note_count(%User{} = user) do
902 |> where(id: ^user.id)
903 |> update([u], inc: [note_count: 1])
905 |> Repo.update_all([])
907 {1, [user]} -> set_cache(user)
912 def decrease_note_count(%User{} = user) do
914 |> where(id: ^user.id)
917 note_count: fragment("greatest(0, note_count - 1)")
921 |> Repo.update_all([])
923 {1, [user]} -> set_cache(user)
928 def update_note_count(%User{} = user, note_count \\ nil) do
933 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
939 |> cast(%{note_count: note_count}, [:note_count])
940 |> update_and_set_cache()
943 @spec maybe_fetch_follow_information(User.t()) :: User.t()
944 def maybe_fetch_follow_information(user) do
945 with {:ok, user} <- fetch_follow_information(user) do
949 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
955 def fetch_follow_information(user) do
956 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
958 |> follow_information_changeset(info)
959 |> update_and_set_cache()
963 defp follow_information_changeset(user, params) do
970 :hide_followers_count,
975 def update_follower_count(%User{} = user) do
976 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
977 follower_count_query =
978 User.Query.build(%{followers: user, deactivated: false})
979 |> select([u], %{count: count(u.id)})
982 |> where(id: ^user.id)
983 |> join(:inner, [u], s in subquery(follower_count_query))
985 set: [follower_count: s.count]
988 |> Repo.update_all([])
990 {1, [user]} -> set_cache(user)
994 {:ok, maybe_fetch_follow_information(user)}
998 @spec maybe_update_following_count(User.t()) :: User.t()
999 def maybe_update_following_count(%User{local: false} = user) do
1000 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1001 maybe_fetch_follow_information(user)
1007 def maybe_update_following_count(user), do: user
1009 def set_unread_conversation_count(%User{local: true} = user) do
1010 unread_query = Participation.unread_conversation_count_for_user(user)
1013 |> join(:inner, [u], p in subquery(unread_query))
1015 set: [unread_conversation_count: p.count]
1017 |> where([u], u.id == ^user.id)
1019 |> Repo.update_all([])
1021 {1, [user]} -> set_cache(user)
1026 def set_unread_conversation_count(_), do: :noop
1028 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1030 Participation.unread_conversation_count_for_user(user)
1031 |> where([p], p.conversation_id == ^conversation.id)
1034 |> join(:inner, [u], p in subquery(unread_query))
1036 inc: [unread_conversation_count: 1]
1038 |> where([u], u.id == ^user.id)
1039 |> where([u, p], p.count == 0)
1041 |> Repo.update_all([])
1043 {1, [user]} -> set_cache(user)
1048 def increment_unread_conversation_count(_, _), do: :noop
1050 def remove_duplicated_following(%User{following: following} = user) do
1051 uniq_following = Enum.uniq(following)
1053 if length(following) == length(uniq_following) do
1057 |> update_changeset(%{following: uniq_following})
1058 |> update_and_set_cache()
1062 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1063 def get_users_from_set(ap_ids, local_only \\ true) do
1064 criteria = %{ap_id: ap_ids, deactivated: false}
1065 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1067 User.Query.build(criteria)
1071 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1072 def get_recipients_from_activity(%Activity{recipients: to}) do
1073 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1077 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
1078 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
1079 add_to_mutes(muter, ap_id, notifications?)
1082 def unmute(muter, %{ap_id: ap_id}) do
1083 remove_from_mutes(muter, ap_id)
1086 def subscribe(subscriber, %{ap_id: ap_id}) do
1087 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
1088 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1090 if blocks?(subscribed, subscriber) and deny_follow_blocked do
1091 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
1093 User.add_to_subscribers(subscribed, subscriber.ap_id)
1098 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
1099 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1100 User.remove_from_subscribers(user, unsubscriber.ap_id)
1104 def block(blocker, %User{ap_id: ap_id} = blocked) do
1105 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1107 if following?(blocker, blocked) do
1108 {:ok, blocker, _} = unfollow(blocker, blocked)
1114 # clear any requested follows as well
1116 case CommonAPI.reject_follow_request(blocked, blocker) do
1117 {:ok, %User{} = updated_blocked} -> updated_blocked
1122 if subscribed_to?(blocked, blocker) do
1123 {:ok, blocker} = unsubscribe(blocked, blocker)
1129 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1131 {:ok, blocker} = update_follower_count(blocker)
1133 add_to_block(blocker, ap_id)
1136 # helper to handle the block given only an actor's AP id
1137 def block(blocker, %{ap_id: ap_id}) do
1138 block(blocker, get_cached_by_ap_id(ap_id))
1141 def unblock(blocker, %{ap_id: ap_id}) do
1142 remove_from_block(blocker, ap_id)
1145 def mutes?(nil, _), do: false
1146 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1148 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1149 def muted_notifications?(nil, _), do: false
1151 def muted_notifications?(user, %{ap_id: ap_id}),
1152 do: Enum.member?(user.muted_notifications, ap_id)
1154 def blocks?(%User{} = user, %User{} = target) do
1155 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1158 def blocks?(nil, _), do: false
1160 def blocks_ap_id?(%User{} = user, %User{} = target) do
1161 Enum.member?(user.blocks, target.ap_id)
1164 def blocks_ap_id?(_, _), do: false
1166 def blocks_domain?(%User{} = user, %User{} = target) do
1167 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1168 %{host: host} = URI.parse(target.ap_id)
1169 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1172 def blocks_domain?(_, _), do: false
1174 def subscribed_to?(user, %{ap_id: ap_id}) do
1175 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1176 Enum.member?(target.subscribers, user.ap_id)
1180 @spec muted_users(User.t()) :: [User.t()]
1181 def muted_users(user) do
1182 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1186 @spec blocked_users(User.t()) :: [User.t()]
1187 def blocked_users(user) do
1188 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1192 @spec subscribers(User.t()) :: [User.t()]
1193 def subscribers(user) do
1194 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1198 def deactivate_async(user, status \\ true) do
1199 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1202 def deactivate(user, status \\ true)
1204 def deactivate(users, status) when is_list(users) do
1205 Repo.transaction(fn ->
1206 for user <- users, do: deactivate(user, status)
1210 def deactivate(%User{} = user, status) do
1211 with {:ok, user} <- set_activation_status(user, status) do
1212 Enum.each(get_followers(user), &invalidate_cache/1)
1213 Enum.each(get_friends(user), &update_follower_count/1)
1219 def update_notification_settings(%User{} = user, settings) do
1222 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1225 notification_settings =
1226 user.notification_settings
1227 |> Map.merge(settings)
1228 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1230 params = %{notification_settings: notification_settings}
1233 |> cast(params, [:notification_settings])
1234 |> validate_required([:notification_settings])
1235 |> update_and_set_cache()
1238 def delete(users) when is_list(users) do
1239 for user <- users, do: delete(user)
1242 def delete(%User{} = user) do
1243 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1246 def perform(:force_password_reset, user), do: force_password_reset(user)
1248 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1249 def perform(:delete, %User{} = user) do
1250 {:ok, _user} = ActivityPub.delete(user)
1252 # Remove all relationships
1255 |> Enum.each(fn follower ->
1256 ActivityPub.unfollow(follower, user)
1257 unfollow(follower, user)
1262 |> Enum.each(fn followed ->
1263 ActivityPub.unfollow(user, followed)
1264 unfollow(user, followed)
1267 delete_user_activities(user)
1268 invalidate_cache(user)
1272 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1273 def perform(:fetch_initial_posts, %User{} = user) do
1274 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1276 # Insert all the posts in reverse order, so they're in the right order on the timeline
1277 user.source_data["outbox"]
1278 |> Utils.fetch_ordered_collection(pages)
1280 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1283 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1285 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1286 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1287 when is_list(blocked_identifiers) do
1289 blocked_identifiers,
1290 fn blocked_identifier ->
1291 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1292 {:ok, blocker} <- block(blocker, blocked),
1293 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1297 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1304 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1305 def perform(:follow_import, %User{} = follower, followed_identifiers)
1306 when is_list(followed_identifiers) do
1308 followed_identifiers,
1309 fn followed_identifier ->
1310 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1311 {:ok, follower} <- maybe_direct_follow(follower, followed),
1312 {:ok, _} <- ActivityPub.follow(follower, followed) do
1316 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1323 @spec external_users_query() :: Ecto.Query.t()
1324 def external_users_query do
1332 @spec external_users(keyword()) :: [User.t()]
1333 def external_users(opts \\ []) do
1335 external_users_query()
1336 |> select([u], struct(u, [:id, :ap_id, :info]))
1340 do: where(query, [u], u.id > ^opts[:max_id]),
1345 do: limit(query, ^opts[:limit]),
1351 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1352 BackgroundWorker.enqueue("blocks_import", %{
1353 "blocker_id" => blocker.id,
1354 "blocked_identifiers" => blocked_identifiers
1358 def follow_import(%User{} = follower, followed_identifiers)
1359 when is_list(followed_identifiers) do
1360 BackgroundWorker.enqueue("follow_import", %{
1361 "follower_id" => follower.id,
1362 "followed_identifiers" => followed_identifiers
1366 def delete_user_activities(%User{ap_id: ap_id}) do
1368 |> Activity.Queries.by_actor()
1369 |> RepoStreamer.chunk_stream(50)
1370 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1374 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1376 |> Object.normalize()
1377 |> ActivityPub.delete()
1380 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1381 object = Object.normalize(activity)
1384 |> get_cached_by_ap_id()
1385 |> ActivityPub.unlike(object)
1388 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1389 object = Object.normalize(activity)
1392 |> get_cached_by_ap_id()
1393 |> ActivityPub.unannounce(object)
1396 defp delete_activity(_activity), do: "Doing nothing"
1398 def html_filter_policy(%User{no_rich_text: true}) do
1399 Pleroma.HTML.Scrubber.TwitterText
1402 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1404 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1406 def get_or_fetch_by_ap_id(ap_id) do
1407 user = get_cached_by_ap_id(ap_id)
1409 if !is_nil(user) and !needs_update?(user) do
1412 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1413 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1415 resp = fetch_by_ap_id(ap_id)
1417 if should_fetch_initial do
1418 with {:ok, %User{} = user} <- resp do
1419 fetch_initial_posts(user)
1427 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1428 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1429 with %User{} = user <- get_cached_by_ap_id(uri) do
1435 |> cast(%{}, [:ap_id, :nickname, :local])
1436 |> put_change(:ap_id, uri)
1437 |> put_change(:nickname, nickname)
1438 |> put_change(:local, true)
1439 |> put_change(:follower_address, uri <> "/followers")
1447 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1450 |> :public_key.pem_decode()
1452 |> :public_key.pem_entry_decode()
1457 def public_key(_), do: {:error, "not found key"}
1459 def get_public_key_for_ap_id(ap_id) do
1460 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1461 {:ok, public_key} <- public_key(user) do
1468 defp blank?(""), do: nil
1469 defp blank?(n), do: n
1471 def insert_or_update_user(data) do
1473 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1474 |> remote_user_creation()
1475 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1479 def ap_enabled?(%User{local: true}), do: true
1480 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1481 def ap_enabled?(_), do: false
1483 @doc "Gets or fetch a user by uri or nickname."
1484 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1485 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1486 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1488 # wait a period of time and return newest version of the User structs
1489 # this is because we have synchronous follow APIs and need to simulate them
1490 # with an async handshake
1491 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1492 with %User{} = a <- get_cached_by_id(a.id),
1493 %User{} = b <- get_cached_by_id(b.id) do
1500 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1501 with :ok <- :timer.sleep(timeout),
1502 %User{} = a <- get_cached_by_id(a.id),
1503 %User{} = b <- get_cached_by_id(b.id) do
1510 def parse_bio(bio) when is_binary(bio) and bio != "" do
1512 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1516 def parse_bio(_), do: ""
1518 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1519 # TODO: get profile URLs other than user.ap_id
1520 profile_urls = [user.ap_id]
1523 |> CommonUtils.format_input("text/plain",
1524 mentions_format: :full,
1525 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1530 def parse_bio(_, _), do: ""
1532 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1533 Repo.transaction(fn ->
1534 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1538 def tag(nickname, tags) when is_binary(nickname),
1539 do: tag(get_by_nickname(nickname), tags)
1541 def tag(%User{} = user, tags),
1542 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1544 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1545 Repo.transaction(fn ->
1546 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1550 def untag(nickname, tags) when is_binary(nickname),
1551 do: untag(get_by_nickname(nickname), tags)
1553 def untag(%User{} = user, tags),
1554 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1556 defp update_tags(%User{} = user, new_tags) do
1557 {:ok, updated_user} =
1559 |> change(%{tags: new_tags})
1560 |> update_and_set_cache()
1565 defp normalize_tags(tags) do
1568 |> Enum.map(&String.downcase/1)
1571 defp local_nickname_regex do
1572 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1573 @extended_local_nickname_regex
1575 @strict_local_nickname_regex
1579 def local_nickname(nickname_or_mention) do
1582 |> String.split("@")
1586 def full_nickname(nickname_or_mention),
1587 do: String.trim_leading(nickname_or_mention, "@")
1589 def error_user(ap_id) do
1593 nickname: "erroruser@example.com",
1594 inserted_at: NaiveDateTime.utc_now()
1598 @spec all_superusers() :: [User.t()]
1599 def all_superusers do
1600 User.Query.build(%{super_users: true, local: true, deactivated: false})
1604 def showing_reblogs?(%User{} = user, %User{} = target) do
1605 target.ap_id not in user.muted_reblogs
1609 The function returns a query to get users with no activity for given interval of days.
1610 Inactive users are those who didn't read any notification, or had any activity where
1611 the user is the activity's actor, during `inactivity_threshold` days.
1612 Deactivated users will not appear in this list.
1616 iex> Pleroma.User.list_inactive_users()
1619 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1620 def list_inactive_users_query(inactivity_threshold \\ 7) do
1621 negative_inactivity_threshold = -inactivity_threshold
1622 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1623 # Subqueries are not supported in `where` clauses, join gets too complicated.
1624 has_read_notifications =
1625 from(n in Pleroma.Notification,
1626 where: n.seen == true,
1628 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1631 |> Pleroma.Repo.all()
1633 from(u in Pleroma.User,
1634 left_join: a in Pleroma.Activity,
1635 on: u.ap_id == a.actor,
1636 where: not is_nil(u.nickname),
1637 where: u.deactivated != ^true,
1638 where: u.id not in ^has_read_notifications,
1641 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1642 is_nil(max(a.inserted_at))
1647 Enable or disable email notifications for user
1651 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1652 Pleroma.User{email_notifications: %{"digest" => true}}
1654 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1655 Pleroma.User{email_notifications: %{"digest" => false}}
1657 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1658 {:ok, t()} | {:error, Ecto.Changeset.t()}
1659 def switch_email_notifications(user, type, status) do
1660 User.update_email_notifications(user, %{type => status})
1664 Set `last_digest_emailed_at` value for the user to current time
1666 @spec touch_last_digest_emailed_at(t()) :: t()
1667 def touch_last_digest_emailed_at(user) do
1668 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1670 {:ok, updated_user} =
1672 |> change(%{last_digest_emailed_at: now})
1673 |> update_and_set_cache()
1678 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1679 def toggle_confirmation(%User{} = user) do
1681 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1682 |> update_and_set_cache()
1685 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1689 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1690 # use instance-default
1691 config = Pleroma.Config.get([:assets, :mascots])
1692 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1693 mascot = Keyword.get(config, default_mascot)
1696 "id" => "default-mascot",
1697 "url" => mascot[:url],
1698 "preview_url" => mascot[:url],
1700 "mime_type" => mascot[:mime_type]
1705 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1707 def ensure_keys_present(%User{} = user) do
1708 with {:ok, pem} <- Keys.generate_rsa_pem() do
1710 |> cast(%{keys: pem}, [:keys])
1711 |> validate_required([:keys])
1712 |> update_and_set_cache()
1716 def get_ap_ids_by_nicknames(nicknames) do
1718 where: u.nickname in ^nicknames,
1724 defdelegate search(query, opts \\ []), to: User.Search
1726 defp put_password_hash(
1727 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1729 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1732 defp put_password_hash(changeset), do: changeset
1734 def is_internal_user?(%User{nickname: nil}), do: true
1735 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1736 def is_internal_user?(_), do: false
1738 # A hack because user delete activities have a fake id for whatever reason
1739 # TODO: Get rid of this
1740 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1742 def get_delivered_users_by_object_id(object_id) do
1744 inner_join: delivery in assoc(u, :deliveries),
1745 where: delivery.object_id == ^object_id
1750 def change_email(user, email) do
1752 |> cast(%{email: email}, [:email])
1753 |> validate_required([:email])
1754 |> unique_constraint(:email)
1755 |> validate_format(:email, @email_regex)
1756 |> update_and_set_cache()
1759 # Internal function; public one is `deactivate/2`
1760 defp set_activation_status(user, deactivated) do
1762 |> cast(%{deactivated: deactivated}, [:deactivated])
1763 |> update_and_set_cache()
1766 def update_banner(user, banner) do
1768 |> cast(%{banner: banner}, [:banner])
1769 |> update_and_set_cache()
1772 def update_background(user, background) do
1774 |> cast(%{background: background}, [:background])
1775 |> update_and_set_cache()
1778 def update_source_data(user, source_data) do
1780 |> cast(%{source_data: source_data}, [:source_data])
1781 |> update_and_set_cache()
1784 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1787 moderator: is_moderator
1791 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1792 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1793 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1794 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1797 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1798 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1802 def fields(%{fields: nil}), do: []
1804 def fields(%{fields: fields}), do: fields
1806 def validate_fields(changeset, remote? \\ false) do
1807 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1808 limit = Pleroma.Config.get([:instance, limit_name], 0)
1811 |> validate_length(:fields, max: limit)
1812 |> validate_change(:fields, fn :fields, fields ->
1813 if Enum.all?(fields, &valid_field?/1) do
1821 defp valid_field?(%{"name" => name, "value" => value}) do
1822 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1823 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1825 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1826 String.length(value) <= value_limit
1829 defp valid_field?(_), do: false
1831 defp truncate_field(%{"name" => name, "value" => value}) do
1833 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1836 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1838 %{"name" => name, "value" => value}
1841 def admin_api_update(user, params) do
1848 |> update_and_set_cache()
1851 def mascot_update(user, url) do
1853 |> cast(%{mascot: url}, [:mascot])
1854 |> validate_required([:mascot])
1855 |> update_and_set_cache()
1858 def mastodon_settings_update(user, settings) do
1860 |> cast(%{settings: settings}, [:settings])
1861 |> validate_required([:settings])
1862 |> update_and_set_cache()
1865 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1866 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1868 if need_confirmation? do
1870 confirmation_pending: true,
1871 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1875 confirmation_pending: false,
1876 confirmation_token: nil
1880 cast(user, params, [:confirmation_pending, :confirmation_token])
1883 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1884 if id not in user.pinned_activities do
1885 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1886 params = %{pinned_activities: user.pinned_activities ++ [id]}
1889 |> cast(params, [:pinned_activities])
1890 |> validate_length(:pinned_activities,
1891 max: max_pinned_statuses,
1892 message: "You have already pinned the maximum number of statuses"
1897 |> update_and_set_cache()
1900 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1901 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1904 |> cast(params, [:pinned_activities])
1905 |> update_and_set_cache()
1908 def update_email_notifications(user, settings) do
1909 email_notifications =
1910 user.email_notifications
1911 |> Map.merge(settings)
1912 |> Map.take(["digest"])
1914 params = %{email_notifications: email_notifications}
1915 fields = [:email_notifications]
1918 |> cast(params, fields)
1919 |> validate_required(fields)
1920 |> update_and_set_cache()
1923 defp set_subscribers(user, subscribers) do
1924 params = %{subscribers: subscribers}
1927 |> cast(params, [:subscribers])
1928 |> validate_required([:subscribers])
1929 |> update_and_set_cache()
1932 def add_to_subscribers(user, subscribed) do
1933 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1936 def remove_from_subscribers(user, subscribed) do
1937 set_subscribers(user, List.delete(user.subscribers, subscribed))
1940 defp set_domain_blocks(user, domain_blocks) do
1941 params = %{domain_blocks: domain_blocks}
1944 |> cast(params, [:domain_blocks])
1945 |> validate_required([:domain_blocks])
1946 |> update_and_set_cache()
1949 def block_domain(user, domain_blocked) do
1950 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1953 def unblock_domain(user, domain_blocked) do
1954 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1957 defp set_blocks(user, blocks) do
1958 params = %{blocks: blocks}
1961 |> cast(params, [:blocks])
1962 |> validate_required([:blocks])
1963 |> update_and_set_cache()
1966 def add_to_block(user, blocked) do
1967 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1970 def remove_from_block(user, blocked) do
1971 set_blocks(user, List.delete(user.blocks, blocked))
1974 defp set_mutes(user, mutes) do
1975 params = %{mutes: mutes}
1978 |> cast(params, [:mutes])
1979 |> validate_required([:mutes])
1980 |> update_and_set_cache()
1983 def add_to_mutes(user, muted, notifications?) do
1984 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1985 set_notification_mutes(
1987 Enum.uniq([muted | user.muted_notifications]),
1993 def remove_from_mutes(user, muted) do
1994 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1995 set_notification_mutes(
1997 List.delete(user.muted_notifications, muted),
2003 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
2007 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
2008 params = %{muted_notifications: muted_notifications}
2011 |> cast(params, [:muted_notifications])
2012 |> validate_required([:muted_notifications])
2013 |> update_and_set_cache()
2016 def add_reblog_mute(user, ap_id) do
2017 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
2020 |> cast(params, [:muted_reblogs])
2021 |> update_and_set_cache()
2024 def remove_reblog_mute(user, ap_id) do
2025 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
2028 |> cast(params, [:muted_reblogs])
2029 |> update_and_set_cache()
2032 def set_invisible(user, invisible) do
2033 params = %{invisible: invisible}
2036 |> cast(params, [:invisible])
2037 |> validate_required([:invisible])
2038 |> update_and_set_cache()