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 field(:following_count, :integer, default: 0)
71 field(:locked, :boolean, default: false)
72 field(:confirmation_pending, :boolean, default: false)
73 field(:password_reset_pending, :boolean, default: false)
74 field(:confirmation_token, :string, default: nil)
75 field(:default_scope, :string, default: "public")
76 field(:blocks, {:array, :string}, default: [])
77 field(:domain_blocks, {:array, :string}, default: [])
78 field(:mutes, {:array, :string}, default: [])
79 field(:muted_reblogs, {:array, :string}, default: [])
80 field(:muted_notifications, {:array, :string}, default: [])
81 field(:subscribers, {:array, :string}, default: [])
82 field(:deactivated, :boolean, default: false)
83 field(:no_rich_text, :boolean, default: false)
84 field(:ap_enabled, :boolean, default: false)
85 field(:is_moderator, :boolean, default: false)
86 field(:is_admin, :boolean, default: false)
87 field(:show_role, :boolean, default: true)
88 field(:settings, :map, default: nil)
89 field(:magic_key, :string, default: nil)
90 field(:uri, :string, default: nil)
91 field(:hide_followers_count, :boolean, default: false)
92 field(:hide_follows_count, :boolean, default: false)
93 field(:hide_followers, :boolean, default: false)
94 field(:hide_follows, :boolean, default: false)
95 field(:hide_favorites, :boolean, default: true)
96 field(:unread_conversation_count, :integer, default: 0)
97 field(:pinned_activities, {:array, :string}, default: [])
98 field(:email_notifications, :map, default: %{"digest" => false})
99 field(:mascot, :map, default: nil)
100 field(:emoji, {:array, :map}, default: [])
101 field(:pleroma_settings_store, :map, default: %{})
102 field(:fields, {:array, :map}, default: [])
103 field(:raw_fields, {:array, :map}, default: [])
104 field(:discoverable, :boolean, default: false)
105 field(:invisible, :boolean, default: false)
106 field(:skip_thread_containment, :boolean, default: false)
108 field(:notification_settings, :map,
112 "non_follows" => true,
113 "non_followers" => true
117 has_many(:notifications, Notification)
118 has_many(:registrations, Registration)
119 has_many(:deliveries, Delivery)
121 field(:info, :map, default: %{})
126 @doc "Returns if the user should be allowed to authenticate"
127 def auth_active?(%User{deactivated: true}), do: false
129 def auth_active?(%User{confirmation_pending: true}),
130 do: !Pleroma.Config.get([:instance, :account_activation_required])
132 def auth_active?(%User{}), do: true
134 def visible_for?(user, for_user \\ nil)
136 def visible_for?(%User{invisible: true}, _), do: false
138 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
140 def visible_for?(%User{} = user, for_user) do
141 auth_active?(user) || superuser?(for_user)
144 def visible_for?(_, _), do: false
146 def superuser?(%User{local: true, is_admin: true}), do: true
147 def superuser?(%User{local: true, is_moderator: true}), do: true
148 def superuser?(_), do: false
150 def invisible?(%User{invisible: true}), do: true
151 def invisible?(_), do: false
153 def avatar_url(user, options \\ []) do
155 %{"url" => [%{"href" => href} | _]} -> href
156 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
160 def banner_url(user, options \\ []) do
162 %{"url" => [%{"href" => href} | _]} -> href
163 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
167 def profile_url(%User{source_data: %{"url" => url}}), do: url
168 def profile_url(%User{ap_id: ap_id}), do: ap_id
169 def profile_url(_), do: nil
171 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
173 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
174 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
176 @spec ap_following(User.t()) :: Sring.t()
177 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
178 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
180 def user_info(%User{} = user, args \\ %{}) do
181 following_count = Map.get(args, :following_count, user.following_count)
182 follower_count = Map.get(args, :follower_count, user.follower_count)
185 note_count: user.note_count,
187 confirmation_pending: user.confirmation_pending,
188 default_scope: user.default_scope,
189 follower_count: follower_count,
190 following_count: following_count
194 def follow_state(%User{} = user, %User{} = target) do
195 case Utils.fetch_latest_follow(user, target) do
196 %{data: %{"state" => state}} -> state
197 # Ideally this would be nil, but then Cachex does not commit the value
202 def get_cached_follow_state(user, target) do
203 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
204 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
207 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
208 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
209 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
212 def set_info_cache(user, args) do
213 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
216 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
217 def restrict_deactivated(query) do
218 from(u in query, where: u.deactivated != ^true)
221 defdelegate following_count(user), to: FollowingRelationship
223 defp truncate_fields_param(params) do
224 if Map.has_key?(params, :fields) do
225 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
231 defp truncate_if_exists(params, key, max_length) do
232 if Map.has_key?(params, key) and is_binary(params[key]) do
233 {value, _chopped} = String.split_at(params[key], max_length)
234 Map.put(params, key, value)
240 def remote_user_creation(params) do
241 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
242 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
246 |> Map.put(:info, params[:info] || %{})
247 |> truncate_if_exists(:name, name_limit)
248 |> truncate_if_exists(:bio, bio_limit)
249 |> truncate_fields_param()
269 :hide_followers_count,
278 |> validate_required([:name, :ap_id])
279 |> unique_constraint(:nickname)
280 |> validate_format(:nickname, @email_regex)
281 |> validate_length(:bio, max: bio_limit)
282 |> validate_length(:name, max: name_limit)
283 |> validate_fields(true)
285 case params[:source_data] do
286 %{"followers" => followers, "following" => following} ->
288 |> put_change(:follower_address, followers)
289 |> put_change(:following_address, following)
292 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
293 put_change(changeset, :follower_address, followers)
297 def update_changeset(struct, params \\ %{}) do
298 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
299 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
314 :hide_followers_count,
319 :skip_thread_containment,
322 :pleroma_settings_store,
326 |> unique_constraint(:nickname)
327 |> validate_format(:nickname, local_nickname_regex())
328 |> validate_length(:bio, max: bio_limit)
329 |> validate_length(:name, min: 1, max: name_limit)
330 |> validate_fields(false)
333 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
334 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
335 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
337 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
339 params = if remote?, do: truncate_fields_param(params), else: params
362 :hide_followers_count,
366 |> unique_constraint(:nickname)
367 |> validate_format(:nickname, local_nickname_regex())
368 |> validate_length(:bio, max: bio_limit)
369 |> validate_length(:name, max: name_limit)
370 |> validate_fields(remote?)
373 def password_update_changeset(struct, params) do
375 |> cast(params, [:password, :password_confirmation])
376 |> validate_required([:password, :password_confirmation])
377 |> validate_confirmation(:password)
378 |> put_password_hash()
379 |> put_change(:password_reset_pending, false)
382 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
383 def reset_password(%User{id: user_id} = user, data) do
386 |> Multi.update(:user, password_update_changeset(user, data))
387 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
388 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
390 case Repo.transaction(multi) do
391 {:ok, %{user: user} = _} -> set_cache(user)
392 {:error, _, changeset, _} -> {:error, changeset}
396 def update_password_reset_pending(user, value) do
399 |> put_change(:password_reset_pending, value)
400 |> update_and_set_cache()
403 def force_password_reset_async(user) do
404 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
407 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
408 def force_password_reset(user), do: update_password_reset_pending(user, true)
410 def register_changeset(struct, params \\ %{}, opts \\ []) do
411 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
412 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
415 if is_nil(opts[:need_confirmation]) do
416 Pleroma.Config.get([:instance, :account_activation_required])
418 opts[:need_confirmation]
422 |> confirmation_changeset(need_confirmation: need_confirmation?)
423 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
424 |> validate_required([:name, :nickname, :password, :password_confirmation])
425 |> validate_confirmation(:password)
426 |> unique_constraint(:email)
427 |> unique_constraint(:nickname)
428 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
429 |> validate_format(:nickname, local_nickname_regex())
430 |> validate_format(:email, @email_regex)
431 |> validate_length(:bio, max: bio_limit)
432 |> validate_length(:name, min: 1, max: name_limit)
433 |> maybe_validate_required_email(opts[:external])
436 |> unique_constraint(:ap_id)
437 |> put_following_and_follower_address()
440 def maybe_validate_required_email(changeset, true), do: changeset
441 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
443 defp put_ap_id(changeset) do
444 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
445 put_change(changeset, :ap_id, ap_id)
448 defp put_following_and_follower_address(changeset) do
449 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
452 |> put_change(:follower_address, followers)
455 defp autofollow_users(user) do
456 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
459 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
462 follow_all(user, autofollowed_users)
465 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
466 def register(%Ecto.Changeset{} = changeset) do
467 with {:ok, user} <- Repo.insert(changeset) do
468 post_register_action(user)
472 def post_register_action(%User{} = user) do
473 with {:ok, user} <- autofollow_users(user),
474 {:ok, user} <- set_cache(user),
475 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
476 {:ok, _} <- try_send_confirmation_email(user) do
481 def try_send_confirmation_email(%User{} = user) do
482 if user.confirmation_pending &&
483 Pleroma.Config.get([:instance, :account_activation_required]) do
485 |> Pleroma.Emails.UserEmail.account_confirmation_email()
486 |> Pleroma.Emails.Mailer.deliver_async()
494 def try_send_confirmation_email(users) do
495 Enum.each(users, &try_send_confirmation_email/1)
498 def needs_update?(%User{local: true}), do: false
500 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
502 def needs_update?(%User{local: false} = user) do
503 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
506 def needs_update?(_), do: true
508 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
509 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
510 follow(follower, followed, "pending")
513 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
514 follow(follower, followed)
517 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
518 if not ap_enabled?(followed) do
519 follow(follower, followed)
525 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
526 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
527 def follow_all(follower, followeds) do
529 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
530 |> Enum.each(&follow(follower, &1, "accept"))
535 defdelegate following(user), to: FollowingRelationship
537 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
538 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
541 followed.deactivated ->
542 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
544 deny_follow_blocked and blocks?(followed, follower) ->
545 {:error, "Could not follow user: #{followed.nickname} blocked you."}
548 FollowingRelationship.follow(follower, followed, state)
550 {:ok, _} = update_follower_count(followed)
553 |> update_following_count()
558 def unfollow(%User{} = follower, %User{} = followed) do
559 if following?(follower, followed) and follower.ap_id != followed.ap_id do
560 FollowingRelationship.unfollow(follower, followed)
562 {:ok, followed} = update_follower_count(followed)
566 |> update_following_count()
569 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
571 {:error, "Not subscribed!"}
575 defdelegate following?(follower, followed), to: FollowingRelationship
577 def locked?(%User{} = user) do
582 Repo.get_by(User, id: id)
585 def get_by_ap_id(ap_id) do
586 Repo.get_by(User, ap_id: ap_id)
589 def get_all_by_ap_id(ap_ids) do
590 from(u in __MODULE__,
591 where: u.ap_id in ^ap_ids
596 def get_all_by_ids(ids) do
597 from(u in __MODULE__, where: u.id in ^ids)
601 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
602 # of the ap_id and the domain and tries to get that user
603 def get_by_guessed_nickname(ap_id) do
604 domain = URI.parse(ap_id).host
605 name = List.last(String.split(ap_id, "/"))
606 nickname = "#{name}@#{domain}"
608 get_cached_by_nickname(nickname)
611 def set_cache({:ok, user}), do: set_cache(user)
612 def set_cache({:error, err}), do: {:error, err}
614 def set_cache(%User{} = user) do
615 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
616 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
617 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
621 def update_and_set_cache(struct, params) do
623 |> update_changeset(params)
624 |> update_and_set_cache()
627 def update_and_set_cache(changeset) do
628 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
633 def invalidate_cache(user) do
634 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
635 Cachex.del(:user_cache, "nickname:#{user.nickname}")
636 Cachex.del(:user_cache, "user_info:#{user.id}")
639 def get_cached_by_ap_id(ap_id) do
640 key = "ap_id:#{ap_id}"
641 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
644 def get_cached_by_id(id) do
648 Cachex.fetch!(:user_cache, key, fn _ ->
652 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
653 {:commit, user.ap_id}
659 get_cached_by_ap_id(ap_id)
662 def get_cached_by_nickname(nickname) do
663 key = "nickname:#{nickname}"
665 Cachex.fetch!(:user_cache, key, fn ->
666 case get_or_fetch_by_nickname(nickname) do
667 {:ok, user} -> {:commit, user}
668 {:error, _error} -> {:ignore, nil}
673 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
674 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
677 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
678 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
680 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
681 get_cached_by_nickname(nickname_or_id)
683 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
684 get_cached_by_nickname(nickname_or_id)
691 def get_by_nickname(nickname) do
692 Repo.get_by(User, nickname: nickname) ||
693 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
694 Repo.get_by(User, nickname: local_nickname(nickname))
698 def get_by_email(email), do: Repo.get_by(User, email: email)
700 def get_by_nickname_or_email(nickname_or_email) do
701 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
704 def get_cached_user_info(user) do
705 key = "user_info:#{user.id}"
706 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
709 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
711 def get_or_fetch_by_nickname(nickname) do
712 with %User{} = user <- get_by_nickname(nickname) do
716 with [_nick, _domain] <- String.split(nickname, "@"),
717 {:ok, user} <- fetch_by_nickname(nickname) do
718 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
719 fetch_initial_posts(user)
724 _e -> {:error, "not found " <> nickname}
729 @doc "Fetch some posts when the user has just been federated with"
730 def fetch_initial_posts(user) do
731 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
734 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
735 def get_followers_query(%User{} = user, nil) do
736 User.Query.build(%{followers: user, deactivated: false})
739 def get_followers_query(user, page) do
741 |> get_followers_query(nil)
742 |> User.Query.paginate(page, 20)
745 @spec get_followers_query(User.t()) :: Ecto.Query.t()
746 def get_followers_query(user), do: get_followers_query(user, nil)
748 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
749 def get_followers(user, page \\ nil) do
751 |> get_followers_query(page)
755 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
756 def get_external_followers(user, page \\ nil) do
758 |> get_followers_query(page)
759 |> User.Query.build(%{external: true})
763 def get_followers_ids(user, page \\ nil) do
765 |> get_followers_query(page)
770 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
771 def get_friends_query(%User{} = user, nil) do
772 User.Query.build(%{friends: user, deactivated: false})
775 def get_friends_query(user, page) do
777 |> get_friends_query(nil)
778 |> User.Query.paginate(page, 20)
781 @spec get_friends_query(User.t()) :: Ecto.Query.t()
782 def get_friends_query(user), do: get_friends_query(user, nil)
784 def get_friends(user, page \\ nil) do
786 |> get_friends_query(page)
790 def get_friends_ids(user, page \\ nil) do
792 |> get_friends_query(page)
797 defdelegate get_follow_requests(user), to: FollowingRelationship
799 def increase_note_count(%User{} = user) do
801 |> where(id: ^user.id)
802 |> update([u], inc: [note_count: 1])
804 |> Repo.update_all([])
806 {1, [user]} -> set_cache(user)
811 def decrease_note_count(%User{} = user) do
813 |> where(id: ^user.id)
816 note_count: fragment("greatest(0, note_count - 1)")
820 |> Repo.update_all([])
822 {1, [user]} -> set_cache(user)
827 def update_note_count(%User{} = user, note_count \\ nil) do
832 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
838 |> cast(%{note_count: note_count}, [:note_count])
839 |> update_and_set_cache()
842 @spec maybe_fetch_follow_information(User.t()) :: User.t()
843 def maybe_fetch_follow_information(user) do
844 with {:ok, user} <- fetch_follow_information(user) do
848 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
854 def fetch_follow_information(user) do
855 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
857 |> follow_information_changeset(info)
858 |> update_and_set_cache()
862 defp follow_information_changeset(user, params) do
869 :hide_followers_count,
874 def update_follower_count(%User{} = user) do
875 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
876 follower_count_query =
877 User.Query.build(%{followers: user, deactivated: false})
878 |> select([u], %{count: count(u.id)})
881 |> where(id: ^user.id)
882 |> join(:inner, [u], s in subquery(follower_count_query))
884 set: [follower_count: s.count]
887 |> Repo.update_all([])
889 {1, [user]} -> set_cache(user)
893 {:ok, maybe_fetch_follow_information(user)}
897 @spec update_following_count(User.t()) :: User.t()
898 def update_following_count(%User{local: false} = user) do
899 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
900 maybe_fetch_follow_information(user)
906 def update_following_count(%User{local: true} = user) do
907 following_count = FollowingRelationship.following_count(user)
910 |> follow_information_changeset(%{following_count: following_count})
914 def set_unread_conversation_count(%User{local: true} = user) do
915 unread_query = Participation.unread_conversation_count_for_user(user)
918 |> join(:inner, [u], p in subquery(unread_query))
920 set: [unread_conversation_count: p.count]
922 |> where([u], u.id == ^user.id)
924 |> Repo.update_all([])
926 {1, [user]} -> set_cache(user)
931 def set_unread_conversation_count(user), do: {:ok, user}
933 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
935 Participation.unread_conversation_count_for_user(user)
936 |> where([p], p.conversation_id == ^conversation.id)
939 |> join(:inner, [u], p in subquery(unread_query))
941 inc: [unread_conversation_count: 1]
943 |> where([u], u.id == ^user.id)
944 |> where([u, p], p.count == 0)
946 |> Repo.update_all([])
948 {1, [user]} -> set_cache(user)
953 def increment_unread_conversation_count(_, user), do: {:ok, user}
955 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
956 def get_users_from_set(ap_ids, local_only \\ true) do
957 criteria = %{ap_id: ap_ids, deactivated: false}
958 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
960 User.Query.build(criteria)
964 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
965 def get_recipients_from_activity(%Activity{recipients: to}) do
966 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
970 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
971 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
972 add_to_mutes(muter, ap_id, notifications?)
975 def unmute(muter, %{ap_id: ap_id}) do
976 remove_from_mutes(muter, ap_id)
979 def subscribe(subscriber, %{ap_id: ap_id}) do
980 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
981 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
983 if blocks?(subscribed, subscriber) and deny_follow_blocked do
984 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
986 User.add_to_subscribers(subscribed, subscriber.ap_id)
991 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
992 with %User{} = user <- get_cached_by_ap_id(ap_id) do
993 User.remove_from_subscribers(user, unsubscriber.ap_id)
997 def block(blocker, %User{ap_id: ap_id} = blocked) do
998 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1000 if following?(blocker, blocked) do
1001 {:ok, blocker, _} = unfollow(blocker, blocked)
1007 # clear any requested follows as well
1009 case CommonAPI.reject_follow_request(blocked, blocker) do
1010 {:ok, %User{} = updated_blocked} -> updated_blocked
1015 if subscribed_to?(blocked, blocker) do
1016 {:ok, blocker} = unsubscribe(blocked, blocker)
1022 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1024 {:ok, blocker} = update_follower_count(blocker)
1025 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1026 add_to_block(blocker, ap_id)
1029 # helper to handle the block given only an actor's AP id
1030 def block(blocker, %{ap_id: ap_id}) do
1031 block(blocker, get_cached_by_ap_id(ap_id))
1034 def unblock(blocker, %{ap_id: ap_id}) do
1035 remove_from_block(blocker, ap_id)
1038 def mutes?(nil, _), do: false
1039 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1041 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1042 def muted_notifications?(nil, _), do: false
1044 def muted_notifications?(user, %{ap_id: ap_id}),
1045 do: Enum.member?(user.muted_notifications, ap_id)
1047 def blocks?(%User{} = user, %User{} = target) do
1048 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1051 def blocks?(nil, _), do: false
1053 def blocks_ap_id?(%User{} = user, %User{} = target) do
1054 Enum.member?(user.blocks, target.ap_id)
1057 def blocks_ap_id?(_, _), do: false
1059 def blocks_domain?(%User{} = user, %User{} = target) do
1060 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1061 %{host: host} = URI.parse(target.ap_id)
1062 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1065 def blocks_domain?(_, _), do: false
1067 def subscribed_to?(user, %{ap_id: ap_id}) do
1068 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1069 Enum.member?(target.subscribers, user.ap_id)
1073 @spec muted_users(User.t()) :: [User.t()]
1074 def muted_users(user) do
1075 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1079 @spec blocked_users(User.t()) :: [User.t()]
1080 def blocked_users(user) do
1081 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1085 @spec subscribers(User.t()) :: [User.t()]
1086 def subscribers(user) do
1087 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1091 def deactivate_async(user, status \\ true) do
1092 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1095 def deactivate(user, status \\ true)
1097 def deactivate(users, status) when is_list(users) do
1098 Repo.transaction(fn ->
1099 for user <- users, do: deactivate(user, status)
1103 def deactivate(%User{} = user, status) do
1104 with {:ok, user} <- set_activation_status(user, status) do
1107 |> Enum.filter(& &1.local)
1108 |> Enum.each(fn follower ->
1109 follower |> update_following_count() |> set_cache()
1112 # Only update local user counts, remote will be update during the next pull.
1115 |> Enum.filter(& &1.local)
1116 |> Enum.each(&update_follower_count/1)
1122 def update_notification_settings(%User{} = user, settings) do
1125 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1128 notification_settings =
1129 user.notification_settings
1130 |> Map.merge(settings)
1131 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1133 params = %{notification_settings: notification_settings}
1136 |> cast(params, [:notification_settings])
1137 |> validate_required([:notification_settings])
1138 |> update_and_set_cache()
1141 def delete(users) when is_list(users) do
1142 for user <- users, do: delete(user)
1145 def delete(%User{} = user) do
1146 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1149 def perform(:force_password_reset, user), do: force_password_reset(user)
1151 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1152 def perform(:delete, %User{} = user) do
1153 {:ok, _user} = ActivityPub.delete(user)
1155 # Remove all relationships
1158 |> Enum.each(fn follower ->
1159 ActivityPub.unfollow(follower, user)
1160 unfollow(follower, user)
1165 |> Enum.each(fn followed ->
1166 ActivityPub.unfollow(user, followed)
1167 unfollow(user, followed)
1170 delete_user_activities(user)
1171 invalidate_cache(user)
1175 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1176 def perform(:fetch_initial_posts, %User{} = user) do
1177 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1179 # Insert all the posts in reverse order, so they're in the right order on the timeline
1180 user.source_data["outbox"]
1181 |> Utils.fetch_ordered_collection(pages)
1183 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1186 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1188 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1189 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1190 when is_list(blocked_identifiers) do
1192 blocked_identifiers,
1193 fn blocked_identifier ->
1194 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1195 {:ok, blocker} <- block(blocker, blocked),
1196 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1200 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1207 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1208 def perform(:follow_import, %User{} = follower, followed_identifiers)
1209 when is_list(followed_identifiers) do
1211 followed_identifiers,
1212 fn followed_identifier ->
1213 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1214 {:ok, follower} <- maybe_direct_follow(follower, followed),
1215 {:ok, _} <- ActivityPub.follow(follower, followed) do
1219 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1226 @spec external_users_query() :: Ecto.Query.t()
1227 def external_users_query do
1235 @spec external_users(keyword()) :: [User.t()]
1236 def external_users(opts \\ []) do
1238 external_users_query()
1239 |> select([u], struct(u, [:id, :ap_id, :info]))
1243 do: where(query, [u], u.id > ^opts[:max_id]),
1248 do: limit(query, ^opts[:limit]),
1254 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1255 BackgroundWorker.enqueue("blocks_import", %{
1256 "blocker_id" => blocker.id,
1257 "blocked_identifiers" => blocked_identifiers
1261 def follow_import(%User{} = follower, followed_identifiers)
1262 when is_list(followed_identifiers) do
1263 BackgroundWorker.enqueue("follow_import", %{
1264 "follower_id" => follower.id,
1265 "followed_identifiers" => followed_identifiers
1269 def delete_user_activities(%User{ap_id: ap_id}) do
1271 |> Activity.Queries.by_actor()
1272 |> RepoStreamer.chunk_stream(50)
1273 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1277 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1279 |> Object.normalize()
1280 |> ActivityPub.delete()
1283 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1284 object = Object.normalize(activity)
1287 |> get_cached_by_ap_id()
1288 |> ActivityPub.unlike(object)
1291 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1292 object = Object.normalize(activity)
1295 |> get_cached_by_ap_id()
1296 |> ActivityPub.unannounce(object)
1299 defp delete_activity(_activity), do: "Doing nothing"
1301 def html_filter_policy(%User{no_rich_text: true}) do
1302 Pleroma.HTML.Scrubber.TwitterText
1305 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1307 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1309 def get_or_fetch_by_ap_id(ap_id) do
1310 user = get_cached_by_ap_id(ap_id)
1312 if !is_nil(user) and !needs_update?(user) do
1315 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1316 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1318 resp = fetch_by_ap_id(ap_id)
1320 if should_fetch_initial do
1321 with {:ok, %User{} = user} <- resp do
1322 fetch_initial_posts(user)
1331 Creates an internal service actor by URI if missing.
1332 Optionally takes nickname for addressing.
1334 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1335 with user when is_nil(user) <- get_cached_by_ap_id(uri) do
1342 follower_address: uri <> "/followers"
1351 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1354 |> :public_key.pem_decode()
1356 |> :public_key.pem_entry_decode()
1361 def public_key(_), do: {:error, "not found key"}
1363 def get_public_key_for_ap_id(ap_id) do
1364 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1365 {:ok, public_key} <- public_key(user) do
1372 defp blank?(""), do: nil
1373 defp blank?(n), do: n
1375 def insert_or_update_user(data) do
1377 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1378 |> remote_user_creation()
1379 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1383 def ap_enabled?(%User{local: true}), do: true
1384 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1385 def ap_enabled?(_), do: false
1387 @doc "Gets or fetch a user by uri or nickname."
1388 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1389 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1390 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1392 # wait a period of time and return newest version of the User structs
1393 # this is because we have synchronous follow APIs and need to simulate them
1394 # with an async handshake
1395 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1396 with %User{} = a <- get_cached_by_id(a.id),
1397 %User{} = b <- get_cached_by_id(b.id) do
1404 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1405 with :ok <- :timer.sleep(timeout),
1406 %User{} = a <- get_cached_by_id(a.id),
1407 %User{} = b <- get_cached_by_id(b.id) do
1414 def parse_bio(bio) when is_binary(bio) and bio != "" do
1416 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1420 def parse_bio(_), do: ""
1422 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1423 # TODO: get profile URLs other than user.ap_id
1424 profile_urls = [user.ap_id]
1427 |> CommonUtils.format_input("text/plain",
1428 mentions_format: :full,
1429 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1434 def parse_bio(_, _), do: ""
1436 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1437 Repo.transaction(fn ->
1438 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1442 def tag(nickname, tags) when is_binary(nickname),
1443 do: tag(get_by_nickname(nickname), tags)
1445 def tag(%User{} = user, tags),
1446 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1448 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1449 Repo.transaction(fn ->
1450 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1454 def untag(nickname, tags) when is_binary(nickname),
1455 do: untag(get_by_nickname(nickname), tags)
1457 def untag(%User{} = user, tags),
1458 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1460 defp update_tags(%User{} = user, new_tags) do
1461 {:ok, updated_user} =
1463 |> change(%{tags: new_tags})
1464 |> update_and_set_cache()
1469 defp normalize_tags(tags) do
1472 |> Enum.map(&String.downcase/1)
1475 defp local_nickname_regex do
1476 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1477 @extended_local_nickname_regex
1479 @strict_local_nickname_regex
1483 def local_nickname(nickname_or_mention) do
1486 |> String.split("@")
1490 def full_nickname(nickname_or_mention),
1491 do: String.trim_leading(nickname_or_mention, "@")
1493 def error_user(ap_id) do
1497 nickname: "erroruser@example.com",
1498 inserted_at: NaiveDateTime.utc_now()
1502 @spec all_superusers() :: [User.t()]
1503 def all_superusers do
1504 User.Query.build(%{super_users: true, local: true, deactivated: false})
1508 def showing_reblogs?(%User{} = user, %User{} = target) do
1509 target.ap_id not in user.muted_reblogs
1513 The function returns a query to get users with no activity for given interval of days.
1514 Inactive users are those who didn't read any notification, or had any activity where
1515 the user is the activity's actor, during `inactivity_threshold` days.
1516 Deactivated users will not appear in this list.
1520 iex> Pleroma.User.list_inactive_users()
1523 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1524 def list_inactive_users_query(inactivity_threshold \\ 7) do
1525 negative_inactivity_threshold = -inactivity_threshold
1526 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1527 # Subqueries are not supported in `where` clauses, join gets too complicated.
1528 has_read_notifications =
1529 from(n in Pleroma.Notification,
1530 where: n.seen == true,
1532 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1535 |> Pleroma.Repo.all()
1537 from(u in Pleroma.User,
1538 left_join: a in Pleroma.Activity,
1539 on: u.ap_id == a.actor,
1540 where: not is_nil(u.nickname),
1541 where: u.deactivated != ^true,
1542 where: u.id not in ^has_read_notifications,
1545 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1546 is_nil(max(a.inserted_at))
1551 Enable or disable email notifications for user
1555 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1556 Pleroma.User{email_notifications: %{"digest" => true}}
1558 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1559 Pleroma.User{email_notifications: %{"digest" => false}}
1561 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1562 {:ok, t()} | {:error, Ecto.Changeset.t()}
1563 def switch_email_notifications(user, type, status) do
1564 User.update_email_notifications(user, %{type => status})
1568 Set `last_digest_emailed_at` value for the user to current time
1570 @spec touch_last_digest_emailed_at(t()) :: t()
1571 def touch_last_digest_emailed_at(user) do
1572 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1574 {:ok, updated_user} =
1576 |> change(%{last_digest_emailed_at: now})
1577 |> update_and_set_cache()
1582 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1583 def toggle_confirmation(%User{} = user) do
1585 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1586 |> update_and_set_cache()
1589 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1590 def toggle_confirmation(users) do
1591 Enum.map(users, &toggle_confirmation/1)
1594 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1598 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1599 # use instance-default
1600 config = Pleroma.Config.get([:assets, :mascots])
1601 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1602 mascot = Keyword.get(config, default_mascot)
1605 "id" => "default-mascot",
1606 "url" => mascot[:url],
1607 "preview_url" => mascot[:url],
1609 "mime_type" => mascot[:mime_type]
1614 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1616 def ensure_keys_present(%User{} = user) do
1617 with {:ok, pem} <- Keys.generate_rsa_pem() do
1619 |> cast(%{keys: pem}, [:keys])
1620 |> validate_required([:keys])
1621 |> update_and_set_cache()
1625 def get_ap_ids_by_nicknames(nicknames) do
1627 where: u.nickname in ^nicknames,
1633 defdelegate search(query, opts \\ []), to: User.Search
1635 defp put_password_hash(
1636 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1638 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1641 defp put_password_hash(changeset), do: changeset
1643 def is_internal_user?(%User{nickname: nil}), do: true
1644 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1645 def is_internal_user?(_), do: false
1647 # A hack because user delete activities have a fake id for whatever reason
1648 # TODO: Get rid of this
1649 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1651 def get_delivered_users_by_object_id(object_id) do
1653 inner_join: delivery in assoc(u, :deliveries),
1654 where: delivery.object_id == ^object_id
1659 def change_email(user, email) do
1661 |> cast(%{email: email}, [:email])
1662 |> validate_required([:email])
1663 |> unique_constraint(:email)
1664 |> validate_format(:email, @email_regex)
1665 |> update_and_set_cache()
1668 # Internal function; public one is `deactivate/2`
1669 defp set_activation_status(user, deactivated) do
1671 |> cast(%{deactivated: deactivated}, [:deactivated])
1672 |> update_and_set_cache()
1675 def update_banner(user, banner) do
1677 |> cast(%{banner: banner}, [:banner])
1678 |> update_and_set_cache()
1681 def update_background(user, background) do
1683 |> cast(%{background: background}, [:background])
1684 |> update_and_set_cache()
1687 def update_source_data(user, source_data) do
1689 |> cast(%{source_data: source_data}, [:source_data])
1690 |> update_and_set_cache()
1693 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1696 moderator: is_moderator
1700 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1701 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1702 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1703 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1706 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1707 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1711 def fields(%{fields: nil}), do: []
1713 def fields(%{fields: fields}), do: fields
1715 def validate_fields(changeset, remote? \\ false) do
1716 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1717 limit = Pleroma.Config.get([:instance, limit_name], 0)
1720 |> validate_length(:fields, max: limit)
1721 |> validate_change(:fields, fn :fields, fields ->
1722 if Enum.all?(fields, &valid_field?/1) do
1730 defp valid_field?(%{"name" => name, "value" => value}) do
1731 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1732 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1734 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1735 String.length(value) <= value_limit
1738 defp valid_field?(_), do: false
1740 defp truncate_field(%{"name" => name, "value" => value}) do
1742 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1745 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1747 %{"name" => name, "value" => value}
1750 def admin_api_update(user, params) do
1757 |> update_and_set_cache()
1760 def mascot_update(user, url) do
1762 |> cast(%{mascot: url}, [:mascot])
1763 |> validate_required([:mascot])
1764 |> update_and_set_cache()
1767 def mastodon_settings_update(user, settings) do
1769 |> cast(%{settings: settings}, [:settings])
1770 |> validate_required([:settings])
1771 |> update_and_set_cache()
1774 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1775 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1777 if need_confirmation? do
1779 confirmation_pending: true,
1780 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1784 confirmation_pending: false,
1785 confirmation_token: nil
1789 cast(user, params, [:confirmation_pending, :confirmation_token])
1792 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1793 if id not in user.pinned_activities do
1794 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1795 params = %{pinned_activities: user.pinned_activities ++ [id]}
1798 |> cast(params, [:pinned_activities])
1799 |> validate_length(:pinned_activities,
1800 max: max_pinned_statuses,
1801 message: "You have already pinned the maximum number of statuses"
1806 |> update_and_set_cache()
1809 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1810 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1813 |> cast(params, [:pinned_activities])
1814 |> update_and_set_cache()
1817 def update_email_notifications(user, settings) do
1818 email_notifications =
1819 user.email_notifications
1820 |> Map.merge(settings)
1821 |> Map.take(["digest"])
1823 params = %{email_notifications: email_notifications}
1824 fields = [:email_notifications]
1827 |> cast(params, fields)
1828 |> validate_required(fields)
1829 |> update_and_set_cache()
1832 defp set_subscribers(user, subscribers) do
1833 params = %{subscribers: subscribers}
1836 |> cast(params, [:subscribers])
1837 |> validate_required([:subscribers])
1838 |> update_and_set_cache()
1841 def add_to_subscribers(user, subscribed) do
1842 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1845 def remove_from_subscribers(user, subscribed) do
1846 set_subscribers(user, List.delete(user.subscribers, subscribed))
1849 defp set_domain_blocks(user, domain_blocks) do
1850 params = %{domain_blocks: domain_blocks}
1853 |> cast(params, [:domain_blocks])
1854 |> validate_required([:domain_blocks])
1855 |> update_and_set_cache()
1858 def block_domain(user, domain_blocked) do
1859 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1862 def unblock_domain(user, domain_blocked) do
1863 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1866 defp set_blocks(user, blocks) do
1867 params = %{blocks: blocks}
1870 |> cast(params, [:blocks])
1871 |> validate_required([:blocks])
1872 |> update_and_set_cache()
1875 def add_to_block(user, blocked) do
1876 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1879 def remove_from_block(user, blocked) do
1880 set_blocks(user, List.delete(user.blocks, blocked))
1883 defp set_mutes(user, mutes) do
1884 params = %{mutes: mutes}
1887 |> cast(params, [:mutes])
1888 |> validate_required([:mutes])
1889 |> update_and_set_cache()
1892 def add_to_mutes(user, muted, notifications?) do
1893 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1894 set_notification_mutes(
1896 Enum.uniq([muted | user.muted_notifications]),
1902 def remove_from_mutes(user, muted) do
1903 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1904 set_notification_mutes(
1906 List.delete(user.muted_notifications, muted),
1912 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1916 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1917 params = %{muted_notifications: muted_notifications}
1920 |> cast(params, [:muted_notifications])
1921 |> validate_required([:muted_notifications])
1922 |> update_and_set_cache()
1925 def add_reblog_mute(user, ap_id) do
1926 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1929 |> cast(params, [:muted_reblogs])
1930 |> update_and_set_cache()
1933 def remove_reblog_mute(user, ap_id) do
1934 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1937 |> cast(params, [:muted_reblogs])
1938 |> update_and_set_cache()
1941 def set_invisible(user, invisible) do
1942 params = %{invisible: invisible}
1945 |> cast(params, [:invisible])
1946 |> validate_required([:invisible])
1947 |> update_and_set_cache()