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
16 alias Pleroma.FollowingRelationship
18 alias Pleroma.Notification
20 alias Pleroma.Registration
22 alias Pleroma.RepoStreamer
25 alias Pleroma.Web.ActivityPub.ActivityPub
26 alias Pleroma.Web.ActivityPub.Utils
27 alias Pleroma.Web.CommonAPI
28 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
29 alias Pleroma.Web.OAuth
30 alias Pleroma.Web.RelMe
31 alias Pleroma.Workers.BackgroundWorker
35 @type t :: %__MODULE__{}
37 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
39 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
40 @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])?)*$/
42 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
43 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
47 field(:email, :string)
49 field(:nickname, :string)
50 field(:password_hash, :string)
51 field(:password, :string, virtual: true)
52 field(:password_confirmation, :string, virtual: true)
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)
109 field(:notification_settings, :map,
113 "non_follows" => true,
114 "non_followers" => true
118 has_many(:notifications, Notification)
119 has_many(:registrations, Registration)
120 has_many(:deliveries, Delivery)
122 field(:info, :map, default: %{})
127 def auth_active?(%User{confirmation_pending: true}),
128 do: !Pleroma.Config.get([:instance, :account_activation_required])
130 def auth_active?(%User{}), do: true
132 def visible_for?(user, for_user \\ nil)
134 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
136 def visible_for?(%User{} = user, for_user) do
137 auth_active?(user) || superuser?(for_user)
140 def visible_for?(_, _), do: false
142 def superuser?(%User{local: true, is_admin: true}), do: true
143 def superuser?(%User{local: true, is_moderator: true}), do: true
144 def superuser?(_), do: false
146 def invisible?(%User{invisible: true}), do: true
147 def invisible?(_), 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 defdelegate following_count(user), to: FollowingRelationship
229 :confirmation_pending,
230 :password_reset_pending,
237 :muted_notifications,
248 :hide_followers_count,
253 :unread_conversation_count,
255 :email_notifications,
258 :pleroma_settings_store,
263 :skip_thread_containment,
264 :notification_settings
267 def info_fields, do: @info_fields
269 defp truncate_fields_param(params) do
270 if Map.has_key?(params, :fields) do
271 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
277 defp truncate_if_exists(params, key, max_length) do
278 if Map.has_key?(params, key) and is_binary(params[key]) do
279 {value, _chopped} = String.split_at(params[key], max_length)
280 Map.put(params, key, value)
286 def remote_user_creation(params) do
287 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
288 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
292 |> Map.put(:info, params[:info] || %{})
293 |> truncate_if_exists(:name, name_limit)
294 |> truncate_if_exists(:bio, bio_limit)
295 |> truncate_fields_param()
315 :hide_followers_count,
324 |> validate_required([:name, :ap_id])
325 |> unique_constraint(:nickname)
326 |> validate_format(:nickname, @email_regex)
327 |> validate_length(:bio, max: bio_limit)
328 |> validate_length(:name, max: name_limit)
329 |> validate_fields(true)
331 case params[:source_data] do
332 %{"followers" => followers, "following" => following} ->
334 |> put_change(:follower_address, followers)
335 |> put_change(:following_address, following)
338 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
339 put_change(changeset, :follower_address, followers)
343 def update_changeset(struct, params \\ %{}) do
344 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
345 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
360 :hide_followers_count,
365 :skip_thread_containment,
368 :pleroma_settings_store,
372 |> unique_constraint(:nickname)
373 |> validate_format(:nickname, local_nickname_regex())
374 |> validate_length(:bio, max: bio_limit)
375 |> validate_length(:name, min: 1, max: name_limit)
376 |> validate_fields(false)
379 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
380 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
381 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
383 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
385 params = if remote?, do: truncate_fields_param(params), else: params
408 :hide_followers_count,
412 |> unique_constraint(:nickname)
413 |> validate_format(:nickname, local_nickname_regex())
414 |> validate_length(:bio, max: bio_limit)
415 |> validate_length(:name, max: name_limit)
416 |> validate_fields(remote?)
419 def password_update_changeset(struct, params) do
421 |> cast(params, [:password, :password_confirmation])
422 |> validate_required([:password, :password_confirmation])
423 |> validate_confirmation(:password)
424 |> put_password_hash()
425 |> put_change(:password_reset_pending, false)
428 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
429 def reset_password(%User{id: user_id} = user, data) do
432 |> Multi.update(:user, password_update_changeset(user, data))
433 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
434 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
436 case Repo.transaction(multi) do
437 {:ok, %{user: user} = _} -> set_cache(user)
438 {:error, _, changeset, _} -> {:error, changeset}
442 def update_password_reset_pending(user, value) do
445 |> put_change(:password_reset_pending, value)
446 |> update_and_set_cache()
449 def force_password_reset_async(user) do
450 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
453 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
454 def force_password_reset(user), do: update_password_reset_pending(user, true)
456 def register_changeset(struct, params \\ %{}, opts \\ []) do
457 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
458 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
461 if is_nil(opts[:need_confirmation]) do
462 Pleroma.Config.get([:instance, :account_activation_required])
464 opts[:need_confirmation]
468 |> confirmation_changeset(need_confirmation: need_confirmation?)
469 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
470 |> validate_required([:name, :nickname, :password, :password_confirmation])
471 |> validate_confirmation(:password)
472 |> unique_constraint(:email)
473 |> unique_constraint(:nickname)
474 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
475 |> validate_format(:nickname, local_nickname_regex())
476 |> validate_format(:email, @email_regex)
477 |> validate_length(:bio, max: bio_limit)
478 |> validate_length(:name, min: 1, max: name_limit)
479 |> maybe_validate_required_email(opts[:external])
482 |> unique_constraint(:ap_id)
483 |> put_following_and_follower_address()
486 def maybe_validate_required_email(changeset, true), do: changeset
487 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
489 defp put_ap_id(changeset) do
490 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
491 put_change(changeset, :ap_id, ap_id)
494 defp put_following_and_follower_address(changeset) do
495 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
498 |> put_change(:follower_address, followers)
501 defp autofollow_users(user) do
502 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
505 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
508 follow_all(user, autofollowed_users)
511 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
512 def register(%Ecto.Changeset{} = changeset) do
513 with {:ok, user} <- Repo.insert(changeset) do
514 post_register_action(user)
518 def post_register_action(%User{} = user) do
519 with {:ok, user} <- autofollow_users(user),
520 {:ok, user} <- set_cache(user),
521 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
522 {:ok, _} <- try_send_confirmation_email(user) do
527 def try_send_confirmation_email(%User{} = user) do
528 if user.confirmation_pending &&
529 Pleroma.Config.get([:instance, :account_activation_required]) do
531 |> Pleroma.Emails.UserEmail.account_confirmation_email()
532 |> Pleroma.Emails.Mailer.deliver_async()
540 def needs_update?(%User{local: true}), do: false
542 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
544 def needs_update?(%User{local: false} = user) do
545 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
548 def needs_update?(_), do: true
550 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
551 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
552 follow(follower, followed, "pending")
555 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
556 follow(follower, followed)
559 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
560 if not ap_enabled?(followed) do
561 follow(follower, followed)
567 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
568 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
569 def follow_all(follower, followeds) do
571 Enum.reject(followeds, fn followed ->
572 blocks?(follower, followed) || blocks?(followed, follower)
575 Enum.each(followeds, &follow(follower, &1, "accept"))
577 Enum.each(followeds, &update_follower_count/1)
582 defdelegate following(user), to: FollowingRelationship
584 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
585 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
588 followed.deactivated ->
589 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
591 deny_follow_blocked and blocks?(followed, follower) ->
592 {:error, "Could not follow user: #{followed.nickname} blocked you."}
595 FollowingRelationship.follow(follower, followed, state)
597 follower = maybe_update_following_count(follower)
599 {:ok, _} = update_follower_count(followed)
605 def unfollow(%User{} = follower, %User{} = followed) do
606 if following?(follower, followed) and follower.ap_id != followed.ap_id do
607 FollowingRelationship.unfollow(follower, followed)
609 follower = maybe_update_following_count(follower)
611 {:ok, followed} = update_follower_count(followed)
615 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
617 {:error, "Not subscribed!"}
621 defdelegate following?(follower, followed), to: FollowingRelationship
623 def locked?(%User{} = user) do
628 Repo.get_by(User, id: id)
631 def get_by_ap_id(ap_id) do
632 Repo.get_by(User, ap_id: ap_id)
635 def get_all_by_ap_id(ap_ids) do
636 from(u in __MODULE__,
637 where: u.ap_id in ^ap_ids
642 def get_all_by_ids(ids) do
643 from(u in __MODULE__, where: u.id in ^ids)
647 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
648 # of the ap_id and the domain and tries to get that user
649 def get_by_guessed_nickname(ap_id) do
650 domain = URI.parse(ap_id).host
651 name = List.last(String.split(ap_id, "/"))
652 nickname = "#{name}@#{domain}"
654 get_cached_by_nickname(nickname)
657 def set_cache({:ok, user}), do: set_cache(user)
658 def set_cache({:error, err}), do: {:error, err}
660 def set_cache(%User{} = user) do
661 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
662 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
663 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
667 def update_and_set_cache(struct, params) do
669 |> update_changeset(params)
670 |> update_and_set_cache()
673 def update_and_set_cache(changeset) do
674 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
679 def invalidate_cache(user) do
680 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
681 Cachex.del(:user_cache, "nickname:#{user.nickname}")
682 Cachex.del(:user_cache, "user_info:#{user.id}")
685 def get_cached_by_ap_id(ap_id) do
686 key = "ap_id:#{ap_id}"
687 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
690 def get_cached_by_id(id) do
694 Cachex.fetch!(:user_cache, key, fn _ ->
698 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
699 {:commit, user.ap_id}
705 get_cached_by_ap_id(ap_id)
708 def get_cached_by_nickname(nickname) do
709 key = "nickname:#{nickname}"
711 Cachex.fetch!(:user_cache, key, fn ->
712 case get_or_fetch_by_nickname(nickname) do
713 {:ok, user} -> {:commit, user}
714 {:error, _error} -> {:ignore, nil}
719 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
720 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
723 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
724 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
726 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
727 get_cached_by_nickname(nickname_or_id)
729 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
730 get_cached_by_nickname(nickname_or_id)
737 def get_by_nickname(nickname) do
738 Repo.get_by(User, nickname: nickname) ||
739 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
740 Repo.get_by(User, nickname: local_nickname(nickname))
744 def get_by_email(email), do: Repo.get_by(User, email: email)
746 def get_by_nickname_or_email(nickname_or_email) do
747 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
750 def get_cached_user_info(user) do
751 key = "user_info:#{user.id}"
752 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
755 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
757 def get_or_fetch_by_nickname(nickname) do
758 with %User{} = user <- get_by_nickname(nickname) do
762 with [_nick, _domain] <- String.split(nickname, "@"),
763 {:ok, user} <- fetch_by_nickname(nickname) do
764 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
765 fetch_initial_posts(user)
770 _e -> {:error, "not found " <> nickname}
775 @doc "Fetch some posts when the user has just been federated with"
776 def fetch_initial_posts(user) do
777 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
780 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
781 def get_followers_query(%User{} = user, nil) do
782 User.Query.build(%{followers: user, deactivated: false})
785 def get_followers_query(user, page) do
787 |> get_followers_query(nil)
788 |> User.Query.paginate(page, 20)
791 @spec get_followers_query(User.t()) :: Ecto.Query.t()
792 def get_followers_query(user), do: get_followers_query(user, nil)
794 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
795 def get_followers(user, page \\ nil) do
797 |> get_followers_query(page)
801 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
802 def get_external_followers(user, page \\ nil) do
804 |> get_followers_query(page)
805 |> User.Query.build(%{external: true})
809 def get_followers_ids(user, page \\ nil) do
811 |> get_followers_query(page)
816 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
817 def get_friends_query(%User{} = user, nil) do
818 User.Query.build(%{friends: user, deactivated: false})
821 def get_friends_query(user, page) do
823 |> get_friends_query(nil)
824 |> User.Query.paginate(page, 20)
827 @spec get_friends_query(User.t()) :: Ecto.Query.t()
828 def get_friends_query(user), do: get_friends_query(user, nil)
830 def get_friends(user, page \\ nil) do
832 |> get_friends_query(page)
836 def get_friends_ids(user, page \\ nil) do
838 |> get_friends_query(page)
843 defdelegate get_follow_requests(user), to: FollowingRelationship
845 def increase_note_count(%User{} = user) do
847 |> where(id: ^user.id)
848 |> update([u], inc: [note_count: 1])
850 |> Repo.update_all([])
852 {1, [user]} -> set_cache(user)
857 def decrease_note_count(%User{} = user) do
859 |> where(id: ^user.id)
862 note_count: fragment("greatest(0, note_count - 1)")
866 |> Repo.update_all([])
868 {1, [user]} -> set_cache(user)
873 def update_note_count(%User{} = user, note_count \\ nil) do
878 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
884 |> cast(%{note_count: note_count}, [:note_count])
885 |> update_and_set_cache()
888 @spec maybe_fetch_follow_information(User.t()) :: User.t()
889 def maybe_fetch_follow_information(user) do
890 with {:ok, user} <- fetch_follow_information(user) do
894 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
900 def fetch_follow_information(user) do
901 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
903 |> follow_information_changeset(info)
904 |> update_and_set_cache()
908 defp follow_information_changeset(user, params) do
915 :hide_followers_count,
920 def update_follower_count(%User{} = user) do
921 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
922 follower_count_query =
923 User.Query.build(%{followers: user, deactivated: false})
924 |> select([u], %{count: count(u.id)})
927 |> where(id: ^user.id)
928 |> join(:inner, [u], s in subquery(follower_count_query))
930 set: [follower_count: s.count]
933 |> Repo.update_all([])
935 {1, [user]} -> set_cache(user)
939 {:ok, maybe_fetch_follow_information(user)}
943 @spec maybe_update_following_count(User.t()) :: User.t()
944 def maybe_update_following_count(%User{local: false} = user) do
945 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
946 maybe_fetch_follow_information(user)
952 def maybe_update_following_count(user), do: user
954 def set_unread_conversation_count(%User{local: true} = user) do
955 unread_query = Participation.unread_conversation_count_for_user(user)
958 |> join(:inner, [u], p in subquery(unread_query))
960 set: [unread_conversation_count: p.count]
962 |> where([u], u.id == ^user.id)
964 |> Repo.update_all([])
966 {1, [user]} -> set_cache(user)
971 def set_unread_conversation_count(_), do: :noop
973 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
975 Participation.unread_conversation_count_for_user(user)
976 |> where([p], p.conversation_id == ^conversation.id)
979 |> join(:inner, [u], p in subquery(unread_query))
981 inc: [unread_conversation_count: 1]
983 |> where([u], u.id == ^user.id)
984 |> where([u, p], p.count == 0)
986 |> Repo.update_all([])
988 {1, [user]} -> set_cache(user)
993 def increment_unread_conversation_count(_, _), do: :noop
995 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
996 def get_users_from_set(ap_ids, local_only \\ true) do
997 criteria = %{ap_id: ap_ids, deactivated: false}
998 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1000 User.Query.build(criteria)
1004 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1005 def get_recipients_from_activity(%Activity{recipients: to}) do
1006 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1010 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
1011 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
1012 add_to_mutes(muter, ap_id, notifications?)
1015 def unmute(muter, %{ap_id: ap_id}) do
1016 remove_from_mutes(muter, ap_id)
1019 def subscribe(subscriber, %{ap_id: ap_id}) do
1020 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
1021 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1023 if blocks?(subscribed, subscriber) and deny_follow_blocked do
1024 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
1026 User.add_to_subscribers(subscribed, subscriber.ap_id)
1031 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
1032 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1033 User.remove_from_subscribers(user, unsubscriber.ap_id)
1037 def block(blocker, %User{ap_id: ap_id} = blocked) do
1038 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1040 if following?(blocker, blocked) do
1041 {:ok, blocker, _} = unfollow(blocker, blocked)
1047 # clear any requested follows as well
1049 case CommonAPI.reject_follow_request(blocked, blocker) do
1050 {:ok, %User{} = updated_blocked} -> updated_blocked
1055 if subscribed_to?(blocked, blocker) do
1056 {:ok, blocker} = unsubscribe(blocked, blocker)
1062 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1064 {:ok, blocker} = update_follower_count(blocker)
1066 add_to_block(blocker, ap_id)
1069 # helper to handle the block given only an actor's AP id
1070 def block(blocker, %{ap_id: ap_id}) do
1071 block(blocker, get_cached_by_ap_id(ap_id))
1074 def unblock(blocker, %{ap_id: ap_id}) do
1075 remove_from_block(blocker, ap_id)
1078 def mutes?(nil, _), do: false
1079 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1081 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1082 def muted_notifications?(nil, _), do: false
1084 def muted_notifications?(user, %{ap_id: ap_id}),
1085 do: Enum.member?(user.muted_notifications, ap_id)
1087 def blocks?(%User{} = user, %User{} = target) do
1088 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1091 def blocks?(nil, _), do: false
1093 def blocks_ap_id?(%User{} = user, %User{} = target) do
1094 Enum.member?(user.blocks, target.ap_id)
1097 def blocks_ap_id?(_, _), do: false
1099 def blocks_domain?(%User{} = user, %User{} = target) do
1100 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1101 %{host: host} = URI.parse(target.ap_id)
1102 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1105 def blocks_domain?(_, _), do: false
1107 def subscribed_to?(user, %{ap_id: ap_id}) do
1108 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1109 Enum.member?(target.subscribers, user.ap_id)
1113 @spec muted_users(User.t()) :: [User.t()]
1114 def muted_users(user) do
1115 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1119 @spec blocked_users(User.t()) :: [User.t()]
1120 def blocked_users(user) do
1121 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1125 @spec subscribers(User.t()) :: [User.t()]
1126 def subscribers(user) do
1127 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1131 def deactivate_async(user, status \\ true) do
1132 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1135 def deactivate(user, status \\ true)
1137 def deactivate(users, status) when is_list(users) do
1138 Repo.transaction(fn ->
1139 for user <- users, do: deactivate(user, status)
1143 def deactivate(%User{} = user, status) do
1144 with {:ok, user} <- set_activation_status(user, status) do
1145 Enum.each(get_followers(user), &invalidate_cache/1)
1146 Enum.each(get_friends(user), &update_follower_count/1)
1152 def update_notification_settings(%User{} = user, settings) do
1155 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1158 notification_settings =
1159 user.notification_settings
1160 |> Map.merge(settings)
1161 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1163 params = %{notification_settings: notification_settings}
1166 |> cast(params, [:notification_settings])
1167 |> validate_required([:notification_settings])
1168 |> update_and_set_cache()
1171 def delete(users) when is_list(users) do
1172 for user <- users, do: delete(user)
1175 def delete(%User{} = user) do
1176 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1179 def perform(:force_password_reset, user), do: force_password_reset(user)
1181 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1182 def perform(:delete, %User{} = user) do
1183 {:ok, _user} = ActivityPub.delete(user)
1185 # Remove all relationships
1188 |> Enum.each(fn follower ->
1189 ActivityPub.unfollow(follower, user)
1190 unfollow(follower, user)
1195 |> Enum.each(fn followed ->
1196 ActivityPub.unfollow(user, followed)
1197 unfollow(user, followed)
1200 delete_user_activities(user)
1201 invalidate_cache(user)
1205 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1206 def perform(:fetch_initial_posts, %User{} = user) do
1207 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1209 # Insert all the posts in reverse order, so they're in the right order on the timeline
1210 user.source_data["outbox"]
1211 |> Utils.fetch_ordered_collection(pages)
1213 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1216 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1218 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1219 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1220 when is_list(blocked_identifiers) do
1222 blocked_identifiers,
1223 fn blocked_identifier ->
1224 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1225 {:ok, blocker} <- block(blocker, blocked),
1226 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1230 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1237 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1238 def perform(:follow_import, %User{} = follower, followed_identifiers)
1239 when is_list(followed_identifiers) do
1241 followed_identifiers,
1242 fn followed_identifier ->
1243 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1244 {:ok, follower} <- maybe_direct_follow(follower, followed),
1245 {:ok, _} <- ActivityPub.follow(follower, followed) do
1249 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1256 @spec external_users_query() :: Ecto.Query.t()
1257 def external_users_query do
1265 @spec external_users(keyword()) :: [User.t()]
1266 def external_users(opts \\ []) do
1268 external_users_query()
1269 |> select([u], struct(u, [:id, :ap_id, :info]))
1273 do: where(query, [u], u.id > ^opts[:max_id]),
1278 do: limit(query, ^opts[:limit]),
1284 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1285 BackgroundWorker.enqueue("blocks_import", %{
1286 "blocker_id" => blocker.id,
1287 "blocked_identifiers" => blocked_identifiers
1291 def follow_import(%User{} = follower, followed_identifiers)
1292 when is_list(followed_identifiers) do
1293 BackgroundWorker.enqueue("follow_import", %{
1294 "follower_id" => follower.id,
1295 "followed_identifiers" => followed_identifiers
1299 def delete_user_activities(%User{ap_id: ap_id}) do
1301 |> Activity.Queries.by_actor()
1302 |> RepoStreamer.chunk_stream(50)
1303 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1307 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1309 |> Object.normalize()
1310 |> ActivityPub.delete()
1313 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1314 object = Object.normalize(activity)
1317 |> get_cached_by_ap_id()
1318 |> ActivityPub.unlike(object)
1321 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1322 object = Object.normalize(activity)
1325 |> get_cached_by_ap_id()
1326 |> ActivityPub.unannounce(object)
1329 defp delete_activity(_activity), do: "Doing nothing"
1331 def html_filter_policy(%User{no_rich_text: true}) do
1332 Pleroma.HTML.Scrubber.TwitterText
1335 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1337 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1339 def get_or_fetch_by_ap_id(ap_id) do
1340 user = get_cached_by_ap_id(ap_id)
1342 if !is_nil(user) and !needs_update?(user) do
1345 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1346 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1348 resp = fetch_by_ap_id(ap_id)
1350 if should_fetch_initial do
1351 with {:ok, %User{} = user} <- resp do
1352 fetch_initial_posts(user)
1360 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1361 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1362 with %User{} = user <- get_cached_by_ap_id(uri) do
1368 |> cast(%{}, [:ap_id, :nickname, :local])
1369 |> put_change(:ap_id, uri)
1370 |> put_change(:nickname, nickname)
1371 |> put_change(:local, true)
1372 |> put_change(:follower_address, uri <> "/followers")
1380 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1383 |> :public_key.pem_decode()
1385 |> :public_key.pem_entry_decode()
1390 def public_key(_), do: {:error, "not found key"}
1392 def get_public_key_for_ap_id(ap_id) do
1393 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1394 {:ok, public_key} <- public_key(user) do
1401 defp blank?(""), do: nil
1402 defp blank?(n), do: n
1404 def insert_or_update_user(data) do
1406 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1407 |> remote_user_creation()
1408 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1412 def ap_enabled?(%User{local: true}), do: true
1413 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1414 def ap_enabled?(_), do: false
1416 @doc "Gets or fetch a user by uri or nickname."
1417 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1418 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1419 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1421 # wait a period of time and return newest version of the User structs
1422 # this is because we have synchronous follow APIs and need to simulate them
1423 # with an async handshake
1424 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1425 with %User{} = a <- get_cached_by_id(a.id),
1426 %User{} = b <- get_cached_by_id(b.id) do
1433 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1434 with :ok <- :timer.sleep(timeout),
1435 %User{} = a <- get_cached_by_id(a.id),
1436 %User{} = b <- get_cached_by_id(b.id) do
1443 def parse_bio(bio) when is_binary(bio) and bio != "" do
1445 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1449 def parse_bio(_), do: ""
1451 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1452 # TODO: get profile URLs other than user.ap_id
1453 profile_urls = [user.ap_id]
1456 |> CommonUtils.format_input("text/plain",
1457 mentions_format: :full,
1458 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1463 def parse_bio(_, _), do: ""
1465 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1466 Repo.transaction(fn ->
1467 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1471 def tag(nickname, tags) when is_binary(nickname),
1472 do: tag(get_by_nickname(nickname), tags)
1474 def tag(%User{} = user, tags),
1475 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1477 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1478 Repo.transaction(fn ->
1479 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1483 def untag(nickname, tags) when is_binary(nickname),
1484 do: untag(get_by_nickname(nickname), tags)
1486 def untag(%User{} = user, tags),
1487 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1489 defp update_tags(%User{} = user, new_tags) do
1490 {:ok, updated_user} =
1492 |> change(%{tags: new_tags})
1493 |> update_and_set_cache()
1498 defp normalize_tags(tags) do
1501 |> Enum.map(&String.downcase/1)
1504 defp local_nickname_regex do
1505 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1506 @extended_local_nickname_regex
1508 @strict_local_nickname_regex
1512 def local_nickname(nickname_or_mention) do
1515 |> String.split("@")
1519 def full_nickname(nickname_or_mention),
1520 do: String.trim_leading(nickname_or_mention, "@")
1522 def error_user(ap_id) do
1526 nickname: "erroruser@example.com",
1527 inserted_at: NaiveDateTime.utc_now()
1531 @spec all_superusers() :: [User.t()]
1532 def all_superusers do
1533 User.Query.build(%{super_users: true, local: true, deactivated: false})
1537 def showing_reblogs?(%User{} = user, %User{} = target) do
1538 target.ap_id not in user.muted_reblogs
1542 The function returns a query to get users with no activity for given interval of days.
1543 Inactive users are those who didn't read any notification, or had any activity where
1544 the user is the activity's actor, during `inactivity_threshold` days.
1545 Deactivated users will not appear in this list.
1549 iex> Pleroma.User.list_inactive_users()
1552 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1553 def list_inactive_users_query(inactivity_threshold \\ 7) do
1554 negative_inactivity_threshold = -inactivity_threshold
1555 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1556 # Subqueries are not supported in `where` clauses, join gets too complicated.
1557 has_read_notifications =
1558 from(n in Pleroma.Notification,
1559 where: n.seen == true,
1561 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1564 |> Pleroma.Repo.all()
1566 from(u in Pleroma.User,
1567 left_join: a in Pleroma.Activity,
1568 on: u.ap_id == a.actor,
1569 where: not is_nil(u.nickname),
1570 where: u.deactivated != ^true,
1571 where: u.id not in ^has_read_notifications,
1574 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1575 is_nil(max(a.inserted_at))
1580 Enable or disable email notifications for user
1584 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1585 Pleroma.User{email_notifications: %{"digest" => true}}
1587 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1588 Pleroma.User{email_notifications: %{"digest" => false}}
1590 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1591 {:ok, t()} | {:error, Ecto.Changeset.t()}
1592 def switch_email_notifications(user, type, status) do
1593 User.update_email_notifications(user, %{type => status})
1597 Set `last_digest_emailed_at` value for the user to current time
1599 @spec touch_last_digest_emailed_at(t()) :: t()
1600 def touch_last_digest_emailed_at(user) do
1601 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1603 {:ok, updated_user} =
1605 |> change(%{last_digest_emailed_at: now})
1606 |> update_and_set_cache()
1611 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1612 def toggle_confirmation(%User{} = user) do
1614 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1615 |> update_and_set_cache()
1618 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1622 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1623 # use instance-default
1624 config = Pleroma.Config.get([:assets, :mascots])
1625 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1626 mascot = Keyword.get(config, default_mascot)
1629 "id" => "default-mascot",
1630 "url" => mascot[:url],
1631 "preview_url" => mascot[:url],
1633 "mime_type" => mascot[:mime_type]
1638 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1640 def ensure_keys_present(%User{} = user) do
1641 with {:ok, pem} <- Keys.generate_rsa_pem() do
1643 |> cast(%{keys: pem}, [:keys])
1644 |> validate_required([:keys])
1645 |> update_and_set_cache()
1649 def get_ap_ids_by_nicknames(nicknames) do
1651 where: u.nickname in ^nicknames,
1657 defdelegate search(query, opts \\ []), to: User.Search
1659 defp put_password_hash(
1660 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1662 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1665 defp put_password_hash(changeset), do: changeset
1667 def is_internal_user?(%User{nickname: nil}), do: true
1668 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1669 def is_internal_user?(_), do: false
1671 # A hack because user delete activities have a fake id for whatever reason
1672 # TODO: Get rid of this
1673 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1675 def get_delivered_users_by_object_id(object_id) do
1677 inner_join: delivery in assoc(u, :deliveries),
1678 where: delivery.object_id == ^object_id
1683 def change_email(user, email) do
1685 |> cast(%{email: email}, [:email])
1686 |> validate_required([:email])
1687 |> unique_constraint(:email)
1688 |> validate_format(:email, @email_regex)
1689 |> update_and_set_cache()
1692 # Internal function; public one is `deactivate/2`
1693 defp set_activation_status(user, deactivated) do
1695 |> cast(%{deactivated: deactivated}, [:deactivated])
1696 |> update_and_set_cache()
1699 def update_banner(user, banner) do
1701 |> cast(%{banner: banner}, [:banner])
1702 |> update_and_set_cache()
1705 def update_background(user, background) do
1707 |> cast(%{background: background}, [:background])
1708 |> update_and_set_cache()
1711 def update_source_data(user, source_data) do
1713 |> cast(%{source_data: source_data}, [:source_data])
1714 |> update_and_set_cache()
1717 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1720 moderator: is_moderator
1724 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1725 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1726 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1727 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1730 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1731 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1735 def fields(%{fields: nil}), do: []
1737 def fields(%{fields: fields}), do: fields
1739 def validate_fields(changeset, remote? \\ false) do
1740 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1741 limit = Pleroma.Config.get([:instance, limit_name], 0)
1744 |> validate_length(:fields, max: limit)
1745 |> validate_change(:fields, fn :fields, fields ->
1746 if Enum.all?(fields, &valid_field?/1) do
1754 defp valid_field?(%{"name" => name, "value" => value}) do
1755 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1756 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1758 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1759 String.length(value) <= value_limit
1762 defp valid_field?(_), do: false
1764 defp truncate_field(%{"name" => name, "value" => value}) do
1766 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1769 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1771 %{"name" => name, "value" => value}
1774 def admin_api_update(user, params) do
1781 |> update_and_set_cache()
1784 def mascot_update(user, url) do
1786 |> cast(%{mascot: url}, [:mascot])
1787 |> validate_required([:mascot])
1788 |> update_and_set_cache()
1791 def mastodon_settings_update(user, settings) do
1793 |> cast(%{settings: settings}, [:settings])
1794 |> validate_required([:settings])
1795 |> update_and_set_cache()
1798 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1799 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1801 if need_confirmation? do
1803 confirmation_pending: true,
1804 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1808 confirmation_pending: false,
1809 confirmation_token: nil
1813 cast(user, params, [:confirmation_pending, :confirmation_token])
1816 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1817 if id not in user.pinned_activities do
1818 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1819 params = %{pinned_activities: user.pinned_activities ++ [id]}
1822 |> cast(params, [:pinned_activities])
1823 |> validate_length(:pinned_activities,
1824 max: max_pinned_statuses,
1825 message: "You have already pinned the maximum number of statuses"
1830 |> update_and_set_cache()
1833 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1834 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1837 |> cast(params, [:pinned_activities])
1838 |> update_and_set_cache()
1841 def update_email_notifications(user, settings) do
1842 email_notifications =
1843 user.email_notifications
1844 |> Map.merge(settings)
1845 |> Map.take(["digest"])
1847 params = %{email_notifications: email_notifications}
1848 fields = [:email_notifications]
1851 |> cast(params, fields)
1852 |> validate_required(fields)
1853 |> update_and_set_cache()
1856 defp set_subscribers(user, subscribers) do
1857 params = %{subscribers: subscribers}
1860 |> cast(params, [:subscribers])
1861 |> validate_required([:subscribers])
1862 |> update_and_set_cache()
1865 def add_to_subscribers(user, subscribed) do
1866 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1869 def remove_from_subscribers(user, subscribed) do
1870 set_subscribers(user, List.delete(user.subscribers, subscribed))
1873 defp set_domain_blocks(user, domain_blocks) do
1874 params = %{domain_blocks: domain_blocks}
1877 |> cast(params, [:domain_blocks])
1878 |> validate_required([:domain_blocks])
1879 |> update_and_set_cache()
1882 def block_domain(user, domain_blocked) do
1883 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1886 def unblock_domain(user, domain_blocked) do
1887 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1890 defp set_blocks(user, blocks) do
1891 params = %{blocks: blocks}
1894 |> cast(params, [:blocks])
1895 |> validate_required([:blocks])
1896 |> update_and_set_cache()
1899 def add_to_block(user, blocked) do
1900 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1903 def remove_from_block(user, blocked) do
1904 set_blocks(user, List.delete(user.blocks, blocked))
1907 defp set_mutes(user, mutes) do
1908 params = %{mutes: mutes}
1911 |> cast(params, [:mutes])
1912 |> validate_required([:mutes])
1913 |> update_and_set_cache()
1916 def add_to_mutes(user, muted, notifications?) do
1917 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1918 set_notification_mutes(
1920 Enum.uniq([muted | user.muted_notifications]),
1926 def remove_from_mutes(user, muted) do
1927 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1928 set_notification_mutes(
1930 List.delete(user.muted_notifications, muted),
1936 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1940 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1941 params = %{muted_notifications: muted_notifications}
1944 |> cast(params, [:muted_notifications])
1945 |> validate_required([:muted_notifications])
1946 |> update_and_set_cache()
1949 def add_reblog_mute(user, ap_id) do
1950 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1953 |> cast(params, [:muted_reblogs])
1954 |> update_and_set_cache()
1957 def remove_reblog_mute(user, ap_id) do
1958 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1961 |> cast(params, [:muted_reblogs])
1962 |> update_and_set_cache()
1965 def set_invisible(user, invisible) do
1966 params = %{invisible: invisible}
1969 |> cast(params, [:invisible])
1970 |> validate_required([:invisible])
1971 |> update_and_set_cache()