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
221 defp truncate_fields_param(params) do
222 if Map.has_key?(params, :fields) do
223 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
229 defp truncate_if_exists(params, key, max_length) do
230 if Map.has_key?(params, key) and is_binary(params[key]) do
231 {value, _chopped} = String.split_at(params[key], max_length)
232 Map.put(params, key, value)
238 def remote_user_creation(params) do
239 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
240 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
244 |> Map.put(:info, params[:info] || %{})
245 |> truncate_if_exists(:name, name_limit)
246 |> truncate_if_exists(:bio, bio_limit)
247 |> truncate_fields_param()
267 :hide_followers_count,
276 |> validate_required([:name, :ap_id])
277 |> unique_constraint(:nickname)
278 |> validate_format(:nickname, @email_regex)
279 |> validate_length(:bio, max: bio_limit)
280 |> validate_length(:name, max: name_limit)
281 |> validate_fields(true)
283 case params[:source_data] do
284 %{"followers" => followers, "following" => following} ->
286 |> put_change(:follower_address, followers)
287 |> put_change(:following_address, following)
290 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
291 put_change(changeset, :follower_address, followers)
295 def update_changeset(struct, params \\ %{}) do
296 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
297 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
312 :hide_followers_count,
317 :skip_thread_containment,
320 :pleroma_settings_store,
324 |> unique_constraint(:nickname)
325 |> validate_format(:nickname, local_nickname_regex())
326 |> validate_length(:bio, max: bio_limit)
327 |> validate_length(:name, min: 1, max: name_limit)
328 |> validate_fields(false)
331 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
332 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
333 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
335 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
337 params = if remote?, do: truncate_fields_param(params), else: params
360 :hide_followers_count,
364 |> unique_constraint(:nickname)
365 |> validate_format(:nickname, local_nickname_regex())
366 |> validate_length(:bio, max: bio_limit)
367 |> validate_length(:name, max: name_limit)
368 |> validate_fields(remote?)
371 def password_update_changeset(struct, params) do
373 |> cast(params, [:password, :password_confirmation])
374 |> validate_required([:password, :password_confirmation])
375 |> validate_confirmation(:password)
376 |> put_password_hash()
377 |> put_change(:password_reset_pending, false)
380 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
381 def reset_password(%User{id: user_id} = user, data) do
384 |> Multi.update(:user, password_update_changeset(user, data))
385 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
386 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
388 case Repo.transaction(multi) do
389 {:ok, %{user: user} = _} -> set_cache(user)
390 {:error, _, changeset, _} -> {:error, changeset}
394 def update_password_reset_pending(user, value) do
397 |> put_change(:password_reset_pending, value)
398 |> update_and_set_cache()
401 def force_password_reset_async(user) do
402 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
405 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
406 def force_password_reset(user), do: update_password_reset_pending(user, true)
408 def register_changeset(struct, params \\ %{}, opts \\ []) do
409 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
410 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
413 if is_nil(opts[:need_confirmation]) do
414 Pleroma.Config.get([:instance, :account_activation_required])
416 opts[:need_confirmation]
420 |> confirmation_changeset(need_confirmation: need_confirmation?)
421 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
422 |> validate_required([:name, :nickname, :password, :password_confirmation])
423 |> validate_confirmation(:password)
424 |> unique_constraint(:email)
425 |> unique_constraint(:nickname)
426 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
427 |> validate_format(:nickname, local_nickname_regex())
428 |> validate_format(:email, @email_regex)
429 |> validate_length(:bio, max: bio_limit)
430 |> validate_length(:name, min: 1, max: name_limit)
431 |> maybe_validate_required_email(opts[:external])
434 |> unique_constraint(:ap_id)
435 |> put_following_and_follower_address()
438 def maybe_validate_required_email(changeset, true), do: changeset
439 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
441 defp put_ap_id(changeset) do
442 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
443 put_change(changeset, :ap_id, ap_id)
446 defp put_following_and_follower_address(changeset) do
447 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
450 |> put_change(:follower_address, followers)
453 defp autofollow_users(user) do
454 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
457 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
460 follow_all(user, autofollowed_users)
463 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
464 def register(%Ecto.Changeset{} = changeset) do
465 with {:ok, user} <- Repo.insert(changeset) do
466 post_register_action(user)
470 def post_register_action(%User{} = user) do
471 with {:ok, user} <- autofollow_users(user),
472 {:ok, user} <- set_cache(user),
473 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
474 {:ok, _} <- try_send_confirmation_email(user) do
479 def try_send_confirmation_email(%User{} = user) do
480 if user.confirmation_pending &&
481 Pleroma.Config.get([:instance, :account_activation_required]) do
483 |> Pleroma.Emails.UserEmail.account_confirmation_email()
484 |> Pleroma.Emails.Mailer.deliver_async()
492 def try_send_confirmation_email(users) do
493 Enum.each(users, &try_send_confirmation_email/1)
496 def needs_update?(%User{local: true}), do: false
498 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
500 def needs_update?(%User{local: false} = user) do
501 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
504 def needs_update?(_), do: true
506 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
507 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
508 follow(follower, followed, "pending")
511 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
512 follow(follower, followed)
515 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
516 if not ap_enabled?(followed) do
517 follow(follower, followed)
523 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
524 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
525 def follow_all(follower, followeds) do
527 Enum.reject(followeds, fn followed ->
528 blocks?(follower, followed) || blocks?(followed, follower)
531 Enum.each(followeds, &follow(follower, &1, "accept"))
533 Enum.each(followeds, &update_follower_count/1)
538 defdelegate following(user), to: FollowingRelationship
540 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
541 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
544 followed.deactivated ->
545 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
547 deny_follow_blocked and blocks?(followed, follower) ->
548 {:error, "Could not follow user: #{followed.nickname} blocked you."}
551 FollowingRelationship.follow(follower, followed, state)
553 follower = maybe_update_following_count(follower)
555 {:ok, _} = update_follower_count(followed)
561 def unfollow(%User{} = follower, %User{} = followed) do
562 if following?(follower, followed) and follower.ap_id != followed.ap_id do
563 FollowingRelationship.unfollow(follower, followed)
565 follower = maybe_update_following_count(follower)
567 {:ok, followed} = update_follower_count(followed)
571 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
573 {:error, "Not subscribed!"}
577 defdelegate following?(follower, followed), to: FollowingRelationship
579 def locked?(%User{} = user) do
584 Repo.get_by(User, id: id)
587 def get_by_ap_id(ap_id) do
588 Repo.get_by(User, ap_id: ap_id)
591 def get_all_by_ap_id(ap_ids) do
592 from(u in __MODULE__,
593 where: u.ap_id in ^ap_ids
598 def get_all_by_ids(ids) do
599 from(u in __MODULE__, where: u.id in ^ids)
603 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
604 # of the ap_id and the domain and tries to get that user
605 def get_by_guessed_nickname(ap_id) do
606 domain = URI.parse(ap_id).host
607 name = List.last(String.split(ap_id, "/"))
608 nickname = "#{name}@#{domain}"
610 get_cached_by_nickname(nickname)
613 def set_cache({:ok, user}), do: set_cache(user)
614 def set_cache({:error, err}), do: {:error, err}
616 def set_cache(%User{} = user) do
617 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
618 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
619 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
623 def update_and_set_cache(struct, params) do
625 |> update_changeset(params)
626 |> update_and_set_cache()
629 def update_and_set_cache(changeset) do
630 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
635 def invalidate_cache(user) do
636 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
637 Cachex.del(:user_cache, "nickname:#{user.nickname}")
638 Cachex.del(:user_cache, "user_info:#{user.id}")
641 def get_cached_by_ap_id(ap_id) do
642 key = "ap_id:#{ap_id}"
643 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
646 def get_cached_by_id(id) do
650 Cachex.fetch!(:user_cache, key, fn _ ->
654 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
655 {:commit, user.ap_id}
661 get_cached_by_ap_id(ap_id)
664 def get_cached_by_nickname(nickname) do
665 key = "nickname:#{nickname}"
667 Cachex.fetch!(:user_cache, key, fn ->
668 case get_or_fetch_by_nickname(nickname) do
669 {:ok, user} -> {:commit, user}
670 {:error, _error} -> {:ignore, nil}
675 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
676 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
679 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
680 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
682 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
683 get_cached_by_nickname(nickname_or_id)
685 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
686 get_cached_by_nickname(nickname_or_id)
693 def get_by_nickname(nickname) do
694 Repo.get_by(User, nickname: nickname) ||
695 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
696 Repo.get_by(User, nickname: local_nickname(nickname))
700 def get_by_email(email), do: Repo.get_by(User, email: email)
702 def get_by_nickname_or_email(nickname_or_email) do
703 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
706 def get_cached_user_info(user) do
707 key = "user_info:#{user.id}"
708 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
711 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
713 def get_or_fetch_by_nickname(nickname) do
714 with %User{} = user <- get_by_nickname(nickname) do
718 with [_nick, _domain] <- String.split(nickname, "@"),
719 {:ok, user} <- fetch_by_nickname(nickname) do
720 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
721 fetch_initial_posts(user)
726 _e -> {:error, "not found " <> nickname}
731 @doc "Fetch some posts when the user has just been federated with"
732 def fetch_initial_posts(user) do
733 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
736 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
737 def get_followers_query(%User{} = user, nil) do
738 User.Query.build(%{followers: user, deactivated: false})
741 def get_followers_query(user, page) do
743 |> get_followers_query(nil)
744 |> User.Query.paginate(page, 20)
747 @spec get_followers_query(User.t()) :: Ecto.Query.t()
748 def get_followers_query(user), do: get_followers_query(user, nil)
750 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
751 def get_followers(user, page \\ nil) do
753 |> get_followers_query(page)
757 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
758 def get_external_followers(user, page \\ nil) do
760 |> get_followers_query(page)
761 |> User.Query.build(%{external: true})
765 def get_followers_ids(user, page \\ nil) do
767 |> get_followers_query(page)
772 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
773 def get_friends_query(%User{} = user, nil) do
774 User.Query.build(%{friends: user, deactivated: false})
777 def get_friends_query(user, page) do
779 |> get_friends_query(nil)
780 |> User.Query.paginate(page, 20)
783 @spec get_friends_query(User.t()) :: Ecto.Query.t()
784 def get_friends_query(user), do: get_friends_query(user, nil)
786 def get_friends(user, page \\ nil) do
788 |> get_friends_query(page)
792 def get_friends_ids(user, page \\ nil) do
794 |> get_friends_query(page)
799 defdelegate get_follow_requests(user), to: FollowingRelationship
801 def increase_note_count(%User{} = user) do
803 |> where(id: ^user.id)
804 |> update([u], inc: [note_count: 1])
806 |> Repo.update_all([])
808 {1, [user]} -> set_cache(user)
813 def decrease_note_count(%User{} = user) do
815 |> where(id: ^user.id)
818 note_count: fragment("greatest(0, note_count - 1)")
822 |> Repo.update_all([])
824 {1, [user]} -> set_cache(user)
829 def update_note_count(%User{} = user, note_count \\ nil) do
834 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
840 |> cast(%{note_count: note_count}, [:note_count])
841 |> update_and_set_cache()
844 @spec maybe_fetch_follow_information(User.t()) :: User.t()
845 def maybe_fetch_follow_information(user) do
846 with {:ok, user} <- fetch_follow_information(user) do
850 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
856 def fetch_follow_information(user) do
857 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
859 |> follow_information_changeset(info)
860 |> update_and_set_cache()
864 defp follow_information_changeset(user, params) do
871 :hide_followers_count,
876 def update_follower_count(%User{} = user) do
877 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
878 follower_count_query =
879 User.Query.build(%{followers: user, deactivated: false})
880 |> select([u], %{count: count(u.id)})
883 |> where(id: ^user.id)
884 |> join(:inner, [u], s in subquery(follower_count_query))
886 set: [follower_count: s.count]
889 |> Repo.update_all([])
891 {1, [user]} -> set_cache(user)
895 {:ok, maybe_fetch_follow_information(user)}
899 @spec maybe_update_following_count(User.t()) :: User.t()
900 def maybe_update_following_count(%User{local: false} = user) do
901 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
902 maybe_fetch_follow_information(user)
908 def maybe_update_following_count(user), do: user
910 def set_unread_conversation_count(%User{local: true} = user) do
911 unread_query = Participation.unread_conversation_count_for_user(user)
914 |> join(:inner, [u], p in subquery(unread_query))
916 set: [unread_conversation_count: p.count]
918 |> where([u], u.id == ^user.id)
920 |> Repo.update_all([])
922 {1, [user]} -> set_cache(user)
927 def set_unread_conversation_count(user), do: {:ok, user}
929 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
931 Participation.unread_conversation_count_for_user(user)
932 |> where([p], p.conversation_id == ^conversation.id)
935 |> join(:inner, [u], p in subquery(unread_query))
937 inc: [unread_conversation_count: 1]
939 |> where([u], u.id == ^user.id)
940 |> where([u, p], p.count == 0)
942 |> Repo.update_all([])
944 {1, [user]} -> set_cache(user)
949 def increment_unread_conversation_count(_, user), do: {:ok, user}
951 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
952 def get_users_from_set(ap_ids, local_only \\ true) do
953 criteria = %{ap_id: ap_ids, deactivated: false}
954 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
956 User.Query.build(criteria)
960 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
961 def get_recipients_from_activity(%Activity{recipients: to}) do
962 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
966 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
967 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
968 add_to_mutes(muter, ap_id, notifications?)
971 def unmute(muter, %{ap_id: ap_id}) do
972 remove_from_mutes(muter, ap_id)
975 def subscribe(subscriber, %{ap_id: ap_id}) do
976 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
977 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
979 if blocks?(subscribed, subscriber) and deny_follow_blocked do
980 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
982 User.add_to_subscribers(subscribed, subscriber.ap_id)
987 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
988 with %User{} = user <- get_cached_by_ap_id(ap_id) do
989 User.remove_from_subscribers(user, unsubscriber.ap_id)
993 def block(blocker, %User{ap_id: ap_id} = blocked) do
994 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
996 if following?(blocker, blocked) do
997 {:ok, blocker, _} = unfollow(blocker, blocked)
1003 # clear any requested follows as well
1005 case CommonAPI.reject_follow_request(blocked, blocker) do
1006 {:ok, %User{} = updated_blocked} -> updated_blocked
1011 if subscribed_to?(blocked, blocker) do
1012 {:ok, blocker} = unsubscribe(blocked, blocker)
1018 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1020 {:ok, blocker} = update_follower_count(blocker)
1021 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1022 add_to_block(blocker, ap_id)
1025 # helper to handle the block given only an actor's AP id
1026 def block(blocker, %{ap_id: ap_id}) do
1027 block(blocker, get_cached_by_ap_id(ap_id))
1030 def unblock(blocker, %{ap_id: ap_id}) do
1031 remove_from_block(blocker, ap_id)
1034 def mutes?(nil, _), do: false
1035 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1037 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1038 def muted_notifications?(nil, _), do: false
1040 def muted_notifications?(user, %{ap_id: ap_id}),
1041 do: Enum.member?(user.muted_notifications, ap_id)
1043 def blocks?(%User{} = user, %User{} = target) do
1044 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1047 def blocks?(nil, _), do: false
1049 def blocks_ap_id?(%User{} = user, %User{} = target) do
1050 Enum.member?(user.blocks, target.ap_id)
1053 def blocks_ap_id?(_, _), do: false
1055 def blocks_domain?(%User{} = user, %User{} = target) do
1056 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1057 %{host: host} = URI.parse(target.ap_id)
1058 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1061 def blocks_domain?(_, _), do: false
1063 def subscribed_to?(user, %{ap_id: ap_id}) do
1064 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1065 Enum.member?(target.subscribers, user.ap_id)
1069 @spec muted_users(User.t()) :: [User.t()]
1070 def muted_users(user) do
1071 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1075 @spec blocked_users(User.t()) :: [User.t()]
1076 def blocked_users(user) do
1077 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1081 @spec subscribers(User.t()) :: [User.t()]
1082 def subscribers(user) do
1083 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1087 def deactivate_async(user, status \\ true) do
1088 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1091 def deactivate(user, status \\ true)
1093 def deactivate(users, status) when is_list(users) do
1094 Repo.transaction(fn ->
1095 for user <- users, do: deactivate(user, status)
1099 def deactivate(%User{} = user, status) do
1100 with {:ok, user} <- set_activation_status(user, status) do
1101 Enum.each(get_followers(user), &invalidate_cache/1)
1103 # Only update local user counts, remote will be update during the next pull.
1106 |> Enum.filter(& &1.local)
1107 |> Enum.each(&update_follower_count/1)
1113 def update_notification_settings(%User{} = user, settings) do
1116 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1119 notification_settings =
1120 user.notification_settings
1121 |> Map.merge(settings)
1122 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1124 params = %{notification_settings: notification_settings}
1127 |> cast(params, [:notification_settings])
1128 |> validate_required([:notification_settings])
1129 |> update_and_set_cache()
1132 def delete(users) when is_list(users) do
1133 for user <- users, do: delete(user)
1136 def delete(%User{} = user) do
1137 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1140 def perform(:force_password_reset, user), do: force_password_reset(user)
1142 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1143 def perform(:delete, %User{} = user) do
1144 {:ok, _user} = ActivityPub.delete(user)
1146 # Remove all relationships
1149 |> Enum.each(fn follower ->
1150 ActivityPub.unfollow(follower, user)
1151 unfollow(follower, user)
1156 |> Enum.each(fn followed ->
1157 ActivityPub.unfollow(user, followed)
1158 unfollow(user, followed)
1161 delete_user_activities(user)
1162 invalidate_cache(user)
1166 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1167 def perform(:fetch_initial_posts, %User{} = user) do
1168 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1170 # Insert all the posts in reverse order, so they're in the right order on the timeline
1171 user.source_data["outbox"]
1172 |> Utils.fetch_ordered_collection(pages)
1174 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1177 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1179 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1180 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1181 when is_list(blocked_identifiers) do
1183 blocked_identifiers,
1184 fn blocked_identifier ->
1185 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1186 {:ok, blocker} <- block(blocker, blocked),
1187 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1191 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1198 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1199 def perform(:follow_import, %User{} = follower, followed_identifiers)
1200 when is_list(followed_identifiers) do
1202 followed_identifiers,
1203 fn followed_identifier ->
1204 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1205 {:ok, follower} <- maybe_direct_follow(follower, followed),
1206 {:ok, _} <- ActivityPub.follow(follower, followed) do
1210 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1217 @spec external_users_query() :: Ecto.Query.t()
1218 def external_users_query do
1226 @spec external_users(keyword()) :: [User.t()]
1227 def external_users(opts \\ []) do
1229 external_users_query()
1230 |> select([u], struct(u, [:id, :ap_id, :info]))
1234 do: where(query, [u], u.id > ^opts[:max_id]),
1239 do: limit(query, ^opts[:limit]),
1245 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1246 BackgroundWorker.enqueue("blocks_import", %{
1247 "blocker_id" => blocker.id,
1248 "blocked_identifiers" => blocked_identifiers
1252 def follow_import(%User{} = follower, followed_identifiers)
1253 when is_list(followed_identifiers) do
1254 BackgroundWorker.enqueue("follow_import", %{
1255 "follower_id" => follower.id,
1256 "followed_identifiers" => followed_identifiers
1260 def delete_user_activities(%User{ap_id: ap_id}) do
1262 |> Activity.Queries.by_actor()
1263 |> RepoStreamer.chunk_stream(50)
1264 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1268 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1270 |> Object.normalize()
1271 |> ActivityPub.delete()
1274 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1275 object = Object.normalize(activity)
1278 |> get_cached_by_ap_id()
1279 |> ActivityPub.unlike(object)
1282 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1283 object = Object.normalize(activity)
1286 |> get_cached_by_ap_id()
1287 |> ActivityPub.unannounce(object)
1290 defp delete_activity(_activity), do: "Doing nothing"
1292 def html_filter_policy(%User{no_rich_text: true}) do
1293 Pleroma.HTML.Scrubber.TwitterText
1296 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1298 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1300 def get_or_fetch_by_ap_id(ap_id) do
1301 user = get_cached_by_ap_id(ap_id)
1303 if !is_nil(user) and !needs_update?(user) do
1306 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1307 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1309 resp = fetch_by_ap_id(ap_id)
1311 if should_fetch_initial do
1312 with {:ok, %User{} = user} <- resp do
1313 fetch_initial_posts(user)
1321 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1322 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1323 with %User{} = user <- get_cached_by_ap_id(uri) do
1329 |> cast(%{}, [:ap_id, :nickname, :local])
1330 |> put_change(:ap_id, uri)
1331 |> put_change(:nickname, nickname)
1332 |> put_change(:local, true)
1333 |> put_change(:follower_address, uri <> "/followers")
1341 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1344 |> :public_key.pem_decode()
1346 |> :public_key.pem_entry_decode()
1351 def public_key(_), do: {:error, "not found key"}
1353 def get_public_key_for_ap_id(ap_id) do
1354 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1355 {:ok, public_key} <- public_key(user) do
1362 defp blank?(""), do: nil
1363 defp blank?(n), do: n
1365 def insert_or_update_user(data) do
1367 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1368 |> remote_user_creation()
1369 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1373 def ap_enabled?(%User{local: true}), do: true
1374 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1375 def ap_enabled?(_), do: false
1377 @doc "Gets or fetch a user by uri or nickname."
1378 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1379 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1380 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1382 # wait a period of time and return newest version of the User structs
1383 # this is because we have synchronous follow APIs and need to simulate them
1384 # with an async handshake
1385 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1386 with %User{} = a <- get_cached_by_id(a.id),
1387 %User{} = b <- get_cached_by_id(b.id) do
1394 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1395 with :ok <- :timer.sleep(timeout),
1396 %User{} = a <- get_cached_by_id(a.id),
1397 %User{} = b <- get_cached_by_id(b.id) do
1404 def parse_bio(bio) when is_binary(bio) and bio != "" do
1406 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1410 def parse_bio(_), do: ""
1412 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1413 # TODO: get profile URLs other than user.ap_id
1414 profile_urls = [user.ap_id]
1417 |> CommonUtils.format_input("text/plain",
1418 mentions_format: :full,
1419 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1424 def parse_bio(_, _), do: ""
1426 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1427 Repo.transaction(fn ->
1428 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1432 def tag(nickname, tags) when is_binary(nickname),
1433 do: tag(get_by_nickname(nickname), tags)
1435 def tag(%User{} = user, tags),
1436 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1438 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1439 Repo.transaction(fn ->
1440 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1444 def untag(nickname, tags) when is_binary(nickname),
1445 do: untag(get_by_nickname(nickname), tags)
1447 def untag(%User{} = user, tags),
1448 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1450 defp update_tags(%User{} = user, new_tags) do
1451 {:ok, updated_user} =
1453 |> change(%{tags: new_tags})
1454 |> update_and_set_cache()
1459 defp normalize_tags(tags) do
1462 |> Enum.map(&String.downcase/1)
1465 defp local_nickname_regex do
1466 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1467 @extended_local_nickname_regex
1469 @strict_local_nickname_regex
1473 def local_nickname(nickname_or_mention) do
1476 |> String.split("@")
1480 def full_nickname(nickname_or_mention),
1481 do: String.trim_leading(nickname_or_mention, "@")
1483 def error_user(ap_id) do
1487 nickname: "erroruser@example.com",
1488 inserted_at: NaiveDateTime.utc_now()
1492 @spec all_superusers() :: [User.t()]
1493 def all_superusers do
1494 User.Query.build(%{super_users: true, local: true, deactivated: false})
1498 def showing_reblogs?(%User{} = user, %User{} = target) do
1499 target.ap_id not in user.muted_reblogs
1503 The function returns a query to get users with no activity for given interval of days.
1504 Inactive users are those who didn't read any notification, or had any activity where
1505 the user is the activity's actor, during `inactivity_threshold` days.
1506 Deactivated users will not appear in this list.
1510 iex> Pleroma.User.list_inactive_users()
1513 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1514 def list_inactive_users_query(inactivity_threshold \\ 7) do
1515 negative_inactivity_threshold = -inactivity_threshold
1516 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1517 # Subqueries are not supported in `where` clauses, join gets too complicated.
1518 has_read_notifications =
1519 from(n in Pleroma.Notification,
1520 where: n.seen == true,
1522 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1525 |> Pleroma.Repo.all()
1527 from(u in Pleroma.User,
1528 left_join: a in Pleroma.Activity,
1529 on: u.ap_id == a.actor,
1530 where: not is_nil(u.nickname),
1531 where: u.deactivated != ^true,
1532 where: u.id not in ^has_read_notifications,
1535 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1536 is_nil(max(a.inserted_at))
1541 Enable or disable email notifications for user
1545 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1546 Pleroma.User{email_notifications: %{"digest" => true}}
1548 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1549 Pleroma.User{email_notifications: %{"digest" => false}}
1551 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1552 {:ok, t()} | {:error, Ecto.Changeset.t()}
1553 def switch_email_notifications(user, type, status) do
1554 User.update_email_notifications(user, %{type => status})
1558 Set `last_digest_emailed_at` value for the user to current time
1560 @spec touch_last_digest_emailed_at(t()) :: t()
1561 def touch_last_digest_emailed_at(user) do
1562 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1564 {:ok, updated_user} =
1566 |> change(%{last_digest_emailed_at: now})
1567 |> update_and_set_cache()
1572 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1573 def toggle_confirmation(%User{} = user) do
1575 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1576 |> update_and_set_cache()
1579 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1580 def toggle_confirmation(users) do
1581 Enum.map(users, &toggle_confirmation/1)
1584 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1588 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1589 # use instance-default
1590 config = Pleroma.Config.get([:assets, :mascots])
1591 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1592 mascot = Keyword.get(config, default_mascot)
1595 "id" => "default-mascot",
1596 "url" => mascot[:url],
1597 "preview_url" => mascot[:url],
1599 "mime_type" => mascot[:mime_type]
1604 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1606 def ensure_keys_present(%User{} = user) do
1607 with {:ok, pem} <- Keys.generate_rsa_pem() do
1609 |> cast(%{keys: pem}, [:keys])
1610 |> validate_required([:keys])
1611 |> update_and_set_cache()
1615 def get_ap_ids_by_nicknames(nicknames) do
1617 where: u.nickname in ^nicknames,
1623 defdelegate search(query, opts \\ []), to: User.Search
1625 defp put_password_hash(
1626 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1628 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1631 defp put_password_hash(changeset), do: changeset
1633 def is_internal_user?(%User{nickname: nil}), do: true
1634 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1635 def is_internal_user?(_), do: false
1637 # A hack because user delete activities have a fake id for whatever reason
1638 # TODO: Get rid of this
1639 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1641 def get_delivered_users_by_object_id(object_id) do
1643 inner_join: delivery in assoc(u, :deliveries),
1644 where: delivery.object_id == ^object_id
1649 def change_email(user, email) do
1651 |> cast(%{email: email}, [:email])
1652 |> validate_required([:email])
1653 |> unique_constraint(:email)
1654 |> validate_format(:email, @email_regex)
1655 |> update_and_set_cache()
1658 # Internal function; public one is `deactivate/2`
1659 defp set_activation_status(user, deactivated) do
1661 |> cast(%{deactivated: deactivated}, [:deactivated])
1662 |> update_and_set_cache()
1665 def update_banner(user, banner) do
1667 |> cast(%{banner: banner}, [:banner])
1668 |> update_and_set_cache()
1671 def update_background(user, background) do
1673 |> cast(%{background: background}, [:background])
1674 |> update_and_set_cache()
1677 def update_source_data(user, source_data) do
1679 |> cast(%{source_data: source_data}, [:source_data])
1680 |> update_and_set_cache()
1683 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1686 moderator: is_moderator
1690 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1691 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1692 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1693 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1696 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1697 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1701 def fields(%{fields: nil}), do: []
1703 def fields(%{fields: fields}), do: fields
1705 def validate_fields(changeset, remote? \\ false) do
1706 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1707 limit = Pleroma.Config.get([:instance, limit_name], 0)
1710 |> validate_length(:fields, max: limit)
1711 |> validate_change(:fields, fn :fields, fields ->
1712 if Enum.all?(fields, &valid_field?/1) do
1720 defp valid_field?(%{"name" => name, "value" => value}) do
1721 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1722 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1724 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1725 String.length(value) <= value_limit
1728 defp valid_field?(_), do: false
1730 defp truncate_field(%{"name" => name, "value" => value}) do
1732 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1735 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1737 %{"name" => name, "value" => value}
1740 def admin_api_update(user, params) do
1747 |> update_and_set_cache()
1750 def mascot_update(user, url) do
1752 |> cast(%{mascot: url}, [:mascot])
1753 |> validate_required([:mascot])
1754 |> update_and_set_cache()
1757 def mastodon_settings_update(user, settings) do
1759 |> cast(%{settings: settings}, [:settings])
1760 |> validate_required([:settings])
1761 |> update_and_set_cache()
1764 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1765 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1767 if need_confirmation? do
1769 confirmation_pending: true,
1770 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1774 confirmation_pending: false,
1775 confirmation_token: nil
1779 cast(user, params, [:confirmation_pending, :confirmation_token])
1782 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1783 if id not in user.pinned_activities do
1784 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1785 params = %{pinned_activities: user.pinned_activities ++ [id]}
1788 |> cast(params, [:pinned_activities])
1789 |> validate_length(:pinned_activities,
1790 max: max_pinned_statuses,
1791 message: "You have already pinned the maximum number of statuses"
1796 |> update_and_set_cache()
1799 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1800 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1803 |> cast(params, [:pinned_activities])
1804 |> update_and_set_cache()
1807 def update_email_notifications(user, settings) do
1808 email_notifications =
1809 user.email_notifications
1810 |> Map.merge(settings)
1811 |> Map.take(["digest"])
1813 params = %{email_notifications: email_notifications}
1814 fields = [:email_notifications]
1817 |> cast(params, fields)
1818 |> validate_required(fields)
1819 |> update_and_set_cache()
1822 defp set_subscribers(user, subscribers) do
1823 params = %{subscribers: subscribers}
1826 |> cast(params, [:subscribers])
1827 |> validate_required([:subscribers])
1828 |> update_and_set_cache()
1831 def add_to_subscribers(user, subscribed) do
1832 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1835 def remove_from_subscribers(user, subscribed) do
1836 set_subscribers(user, List.delete(user.subscribers, subscribed))
1839 defp set_domain_blocks(user, domain_blocks) do
1840 params = %{domain_blocks: domain_blocks}
1843 |> cast(params, [:domain_blocks])
1844 |> validate_required([:domain_blocks])
1845 |> update_and_set_cache()
1848 def block_domain(user, domain_blocked) do
1849 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1852 def unblock_domain(user, domain_blocked) do
1853 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1856 defp set_blocks(user, blocks) do
1857 params = %{blocks: blocks}
1860 |> cast(params, [:blocks])
1861 |> validate_required([:blocks])
1862 |> update_and_set_cache()
1865 def add_to_block(user, blocked) do
1866 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1869 def remove_from_block(user, blocked) do
1870 set_blocks(user, List.delete(user.blocks, blocked))
1873 defp set_mutes(user, mutes) do
1874 params = %{mutes: mutes}
1877 |> cast(params, [:mutes])
1878 |> validate_required([:mutes])
1879 |> update_and_set_cache()
1882 def add_to_mutes(user, muted, notifications?) do
1883 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1884 set_notification_mutes(
1886 Enum.uniq([muted | user.muted_notifications]),
1892 def remove_from_mutes(user, muted) do
1893 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1894 set_notification_mutes(
1896 List.delete(user.muted_notifications, muted),
1902 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1906 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1907 params = %{muted_notifications: muted_notifications}
1910 |> cast(params, [:muted_notifications])
1911 |> validate_required([:muted_notifications])
1912 |> update_and_set_cache()
1915 def add_reblog_mute(user, ap_id) do
1916 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1919 |> cast(params, [:muted_reblogs])
1920 |> update_and_set_cache()
1923 def remove_reblog_mute(user, ap_id) do
1924 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1927 |> cast(params, [:muted_reblogs])
1928 |> update_and_set_cache()
1931 def set_invisible(user, invisible) do
1932 params = %{invisible: invisible}
1935 |> cast(params, [:invisible])
1936 |> validate_required([:invisible])
1937 |> update_and_set_cache()