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
10 import Ecto, only: [assoc: 2]
14 alias Pleroma.Activity
15 alias Pleroma.Conversation.Participation
16 alias Pleroma.Delivery
17 alias Pleroma.FollowingRelationship
19 alias Pleroma.Notification
21 alias Pleroma.Registration
23 alias Pleroma.RepoStreamer
25 alias Pleroma.UserBlock
26 alias Pleroma.UserMute
28 alias Pleroma.Web.ActivityPub.ActivityPub
29 alias Pleroma.Web.ActivityPub.Utils
30 alias Pleroma.Web.CommonAPI
31 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
32 alias Pleroma.Web.OAuth
33 alias Pleroma.Web.RelMe
34 alias Pleroma.Workers.BackgroundWorker
38 @type t :: %__MODULE__{}
40 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
42 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
43 @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])?)*$/
45 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
46 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
50 field(:email, :string)
52 field(:nickname, :string)
53 field(:password_hash, :string)
54 field(:password, :string, virtual: true)
55 field(:password_confirmation, :string, virtual: true)
57 field(:ap_id, :string)
59 field(:local, :boolean, default: true)
60 field(:follower_address, :string)
61 field(:following_address, :string)
62 field(:search_rank, :float, virtual: true)
63 field(:search_type, :integer, virtual: true)
64 field(:tags, {:array, :string}, default: [])
65 field(:last_refreshed_at, :naive_datetime_usec)
66 field(:last_digest_emailed_at, :naive_datetime)
68 field(:banner, :map, default: %{})
69 field(:background, :map, default: %{})
70 field(:source_data, :map, default: %{})
71 field(:note_count, :integer, default: 0)
72 field(:follower_count, :integer, default: 0)
73 # Should be filled in only for remote users
74 field(:following_count, :integer, default: nil)
75 field(:locked, :boolean, default: false)
76 field(:confirmation_pending, :boolean, default: false)
77 field(:password_reset_pending, :boolean, default: false)
78 field(:confirmation_token, :string, default: nil)
79 field(:default_scope, :string, default: "public")
80 field(:domain_blocks, {:array, :string}, default: [])
81 field(:mutes, {:array, :string}, default: [])
82 field(:muted_reblogs, {:array, :string}, default: [])
83 field(:muted_notifications, {:array, :string}, default: [])
84 field(:subscribers, {:array, :string}, default: [])
85 field(:deactivated, :boolean, default: false)
86 field(:no_rich_text, :boolean, default: false)
87 field(:ap_enabled, :boolean, default: false)
88 field(:is_moderator, :boolean, default: false)
89 field(:is_admin, :boolean, default: false)
90 field(:show_role, :boolean, default: true)
91 field(:settings, :map, default: nil)
92 field(:magic_key, :string, default: nil)
93 field(:uri, :string, default: nil)
94 field(:hide_followers_count, :boolean, default: false)
95 field(:hide_follows_count, :boolean, default: false)
96 field(:hide_followers, :boolean, default: false)
97 field(:hide_follows, :boolean, default: false)
98 field(:hide_favorites, :boolean, default: true)
99 field(:unread_conversation_count, :integer, default: 0)
100 field(:pinned_activities, {:array, :string}, default: [])
101 field(:email_notifications, :map, default: %{"digest" => false})
102 field(:mascot, :map, default: nil)
103 field(:emoji, {:array, :map}, default: [])
104 field(:pleroma_settings_store, :map, default: %{})
105 field(:fields, {:array, :map}, default: [])
106 field(:raw_fields, {:array, :map}, default: [])
107 field(:discoverable, :boolean, default: false)
108 field(:invisible, :boolean, default: false)
109 field(:skip_thread_containment, :boolean, default: false)
111 field(:notification_settings, :map,
115 "non_follows" => true,
116 "non_followers" => true
120 has_many(:notifications, Notification)
121 has_many(:registrations, Registration)
122 has_many(:deliveries, Delivery)
124 has_many(:blocker_blocks, UserBlock, foreign_key: :blocker_id)
125 has_many(:blockee_blocks, UserBlock, foreign_key: :blockee_id)
126 has_many(:blocked_users, through: [:blocker_blocks, :blockee])
127 has_many(:blocker_users, through: [:blockee_blocks, :blocker])
129 has_many(:muter_mutes, UserMute, foreign_key: :muter_id)
130 has_many(:mutee_mutes, UserMute, foreign_key: :mutee_id)
131 has_many(:muted_users, through: [:muter_mutes, :mutee])
132 has_many(:muter_users, through: [:mutee_mutes, :muter])
134 field(:info, :map, default: %{})
136 # `:blocks` is deprecated (replaced with `blocked_users` relation)
137 field(:blocks, {:array, :string}, default: [])
142 def auth_active?(%User{confirmation_pending: true}),
143 do: !Pleroma.Config.get([:instance, :account_activation_required])
145 def auth_active?(%User{}), do: true
147 def visible_for?(user, for_user \\ nil)
149 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
151 def visible_for?(%User{} = user, for_user) do
152 auth_active?(user) || superuser?(for_user)
155 def visible_for?(_, _), do: false
157 def superuser?(%User{local: true, is_admin: true}), do: true
158 def superuser?(%User{local: true, is_moderator: true}), do: true
159 def superuser?(_), do: false
161 def invisible?(%User{invisible: true}), do: true
162 def invisible?(_), do: false
164 def avatar_url(user, options \\ []) do
166 %{"url" => [%{"href" => href} | _]} -> href
167 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
171 def banner_url(user, options \\ []) do
173 %{"url" => [%{"href" => href} | _]} -> href
174 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
178 def profile_url(%User{source_data: %{"url" => url}}), do: url
179 def profile_url(%User{ap_id: ap_id}), do: ap_id
180 def profile_url(_), do: nil
182 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
184 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
185 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
187 @spec ap_following(User.t()) :: Sring.t()
188 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
189 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
191 def user_info(%User{} = user, args \\ %{}) do
193 Map.get(args, :following_count, user.following_count || following_count(user))
195 follower_count = Map.get(args, :follower_count, user.follower_count)
198 note_count: user.note_count,
200 confirmation_pending: user.confirmation_pending,
201 default_scope: user.default_scope
203 |> Map.put(:following_count, following_count)
204 |> Map.put(:follower_count, follower_count)
207 def follow_state(%User{} = user, %User{} = target) do
208 case Utils.fetch_latest_follow(user, target) do
209 %{data: %{"state" => state}} -> state
210 # Ideally this would be nil, but then Cachex does not commit the value
215 def get_cached_follow_state(user, target) do
216 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
217 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
220 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
221 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
222 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
225 def set_info_cache(user, args) do
226 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
229 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
230 def restrict_deactivated(query) do
231 from(u in query, where: u.deactivated != ^true)
234 defdelegate following_count(user), to: FollowingRelationship
236 defp truncate_fields_param(params) do
237 if Map.has_key?(params, :fields) do
238 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
244 defp truncate_if_exists(params, key, max_length) do
245 if Map.has_key?(params, key) and is_binary(params[key]) do
246 {value, _chopped} = String.split_at(params[key], max_length)
247 Map.put(params, key, value)
253 def remote_user_creation(params) do
254 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
255 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
259 |> Map.put(:info, params[:info] || %{})
260 |> truncate_if_exists(:name, name_limit)
261 |> truncate_if_exists(:bio, bio_limit)
262 |> truncate_fields_param()
282 :hide_followers_count,
291 |> validate_required([:name, :ap_id])
292 |> unique_constraint(:nickname)
293 |> validate_format(:nickname, @email_regex)
294 |> validate_length(:bio, max: bio_limit)
295 |> validate_length(:name, max: name_limit)
296 |> validate_fields(true)
298 case params[:source_data] do
299 %{"followers" => followers, "following" => following} ->
301 |> put_change(:follower_address, followers)
302 |> put_change(:following_address, following)
305 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
306 put_change(changeset, :follower_address, followers)
310 def update_changeset(struct, params \\ %{}) do
311 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
312 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
327 :hide_followers_count,
332 :skip_thread_containment,
335 :pleroma_settings_store,
339 |> unique_constraint(:nickname)
340 |> validate_format(:nickname, local_nickname_regex())
341 |> validate_length(:bio, max: bio_limit)
342 |> validate_length(:name, min: 1, max: name_limit)
343 |> validate_fields(false)
346 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
347 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
348 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
350 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
352 params = if remote?, do: truncate_fields_param(params), else: params
375 :hide_followers_count,
379 |> unique_constraint(:nickname)
380 |> validate_format(:nickname, local_nickname_regex())
381 |> validate_length(:bio, max: bio_limit)
382 |> validate_length(:name, max: name_limit)
383 |> validate_fields(remote?)
386 def password_update_changeset(struct, params) do
388 |> cast(params, [:password, :password_confirmation])
389 |> validate_required([:password, :password_confirmation])
390 |> validate_confirmation(:password)
391 |> put_password_hash()
392 |> put_change(:password_reset_pending, false)
395 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
396 def reset_password(%User{id: user_id} = user, data) do
399 |> Multi.update(:user, password_update_changeset(user, data))
400 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
401 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
403 case Repo.transaction(multi) do
404 {:ok, %{user: user} = _} -> set_cache(user)
405 {:error, _, changeset, _} -> {:error, changeset}
409 def update_password_reset_pending(user, value) do
412 |> put_change(:password_reset_pending, value)
413 |> update_and_set_cache()
416 def force_password_reset_async(user) do
417 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
420 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
421 def force_password_reset(user), do: update_password_reset_pending(user, true)
423 def register_changeset(struct, params \\ %{}, opts \\ []) do
424 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
425 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
428 if is_nil(opts[:need_confirmation]) do
429 Pleroma.Config.get([:instance, :account_activation_required])
431 opts[:need_confirmation]
435 |> confirmation_changeset(need_confirmation: need_confirmation?)
436 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
437 |> validate_required([:name, :nickname, :password, :password_confirmation])
438 |> validate_confirmation(:password)
439 |> unique_constraint(:email)
440 |> unique_constraint(:nickname)
441 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
442 |> validate_format(:nickname, local_nickname_regex())
443 |> validate_format(:email, @email_regex)
444 |> validate_length(:bio, max: bio_limit)
445 |> validate_length(:name, min: 1, max: name_limit)
446 |> maybe_validate_required_email(opts[:external])
449 |> unique_constraint(:ap_id)
450 |> put_following_and_follower_address()
453 def maybe_validate_required_email(changeset, true), do: changeset
454 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
456 defp put_ap_id(changeset) do
457 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
458 put_change(changeset, :ap_id, ap_id)
461 defp put_following_and_follower_address(changeset) do
462 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
465 |> put_change(:follower_address, followers)
468 defp autofollow_users(user) do
469 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
472 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
475 follow_all(user, autofollowed_users)
478 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
479 def register(%Ecto.Changeset{} = changeset) do
480 with {:ok, user} <- Repo.insert(changeset) do
481 post_register_action(user)
485 def post_register_action(%User{} = user) do
486 with {:ok, user} <- autofollow_users(user),
487 {:ok, user} <- set_cache(user),
488 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
489 {:ok, _} <- try_send_confirmation_email(user) do
494 def try_send_confirmation_email(%User{} = user) do
495 if user.confirmation_pending &&
496 Pleroma.Config.get([:instance, :account_activation_required]) do
498 |> Pleroma.Emails.UserEmail.account_confirmation_email()
499 |> Pleroma.Emails.Mailer.deliver_async()
507 def needs_update?(%User{local: true}), do: false
509 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
511 def needs_update?(%User{local: false} = user) do
512 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
515 def needs_update?(_), do: true
517 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
518 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
519 follow(follower, followed, "pending")
522 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
523 follow(follower, followed)
526 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
527 if not ap_enabled?(followed) do
528 follow(follower, followed)
534 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
535 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
536 def follow_all(follower, followeds) do
538 Enum.reject(followeds, fn followed ->
539 blocks?(follower, followed) || blocks?(followed, follower)
542 Enum.each(followeds, &follow(follower, &1, "accept"))
544 Enum.each(followeds, &update_follower_count/1)
549 defdelegate following(user), to: FollowingRelationship
551 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
552 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
555 followed.deactivated ->
556 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
558 deny_follow_blocked and blocks?(followed, follower) ->
559 {:error, "Could not follow user: #{followed.nickname} blocked you."}
562 FollowingRelationship.follow(follower, followed, state)
564 follower = maybe_update_following_count(follower)
566 {:ok, _} = update_follower_count(followed)
572 def unfollow(%User{} = follower, %User{} = followed) do
573 if following?(follower, followed) and follower.ap_id != followed.ap_id do
574 FollowingRelationship.unfollow(follower, followed)
576 follower = maybe_update_following_count(follower)
578 {:ok, followed} = update_follower_count(followed)
582 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
584 {:error, "Not subscribed!"}
588 defdelegate following?(follower, followed), to: FollowingRelationship
590 def locked?(%User{} = user) do
595 Repo.get_by(User, id: id)
598 def get_by_ap_id(ap_id) do
599 Repo.get_by(User, ap_id: ap_id)
602 def get_all_by_ap_id(ap_ids) do
603 from(u in __MODULE__,
604 where: u.ap_id in ^ap_ids
609 def get_all_by_ids(ids) do
610 from(u in __MODULE__, where: u.id in ^ids)
614 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
615 # of the ap_id and the domain and tries to get that user
616 def get_by_guessed_nickname(ap_id) do
617 domain = URI.parse(ap_id).host
618 name = List.last(String.split(ap_id, "/"))
619 nickname = "#{name}@#{domain}"
621 get_cached_by_nickname(nickname)
624 def set_cache({:ok, user}), do: set_cache(user)
625 def set_cache({:error, err}), do: {:error, err}
627 def set_cache(%User{} = user) do
628 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
629 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
630 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
634 def update_and_set_cache(struct, params) do
636 |> update_changeset(params)
637 |> update_and_set_cache()
640 def update_and_set_cache(changeset) do
641 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
646 def invalidate_cache(user) do
647 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
648 Cachex.del(:user_cache, "nickname:#{user.nickname}")
649 Cachex.del(:user_cache, "user_info:#{user.id}")
652 def get_cached_by_ap_id(ap_id) do
653 key = "ap_id:#{ap_id}"
654 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
657 def get_cached_by_id(id) do
661 Cachex.fetch!(:user_cache, key, fn _ ->
665 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
666 {:commit, user.ap_id}
672 get_cached_by_ap_id(ap_id)
675 def get_cached_by_nickname(nickname) do
676 key = "nickname:#{nickname}"
678 Cachex.fetch!(:user_cache, key, fn ->
679 case get_or_fetch_by_nickname(nickname) do
680 {:ok, user} -> {:commit, user}
681 {:error, _error} -> {:ignore, nil}
686 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
687 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
690 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
691 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
693 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
694 get_cached_by_nickname(nickname_or_id)
696 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
697 get_cached_by_nickname(nickname_or_id)
704 def get_by_nickname(nickname) do
705 Repo.get_by(User, nickname: nickname) ||
706 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
707 Repo.get_by(User, nickname: local_nickname(nickname))
711 def get_by_email(email), do: Repo.get_by(User, email: email)
713 def get_by_nickname_or_email(nickname_or_email) do
714 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
717 def get_cached_user_info(user) do
718 key = "user_info:#{user.id}"
719 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
722 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
724 def get_or_fetch_by_nickname(nickname) do
725 with %User{} = user <- get_by_nickname(nickname) do
729 with [_nick, _domain] <- String.split(nickname, "@"),
730 {:ok, user} <- fetch_by_nickname(nickname) do
731 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
732 fetch_initial_posts(user)
737 _e -> {:error, "not found " <> nickname}
742 @doc "Fetch some posts when the user has just been federated with"
743 def fetch_initial_posts(user) do
744 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
747 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
748 def get_followers_query(%User{} = user, nil) do
749 User.Query.build(%{followers: user, deactivated: false})
752 def get_followers_query(user, page) do
754 |> get_followers_query(nil)
755 |> User.Query.paginate(page, 20)
758 @spec get_followers_query(User.t()) :: Ecto.Query.t()
759 def get_followers_query(user), do: get_followers_query(user, nil)
761 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
762 def get_followers(user, page \\ nil) do
764 |> get_followers_query(page)
768 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
769 def get_external_followers(user, page \\ nil) do
771 |> get_followers_query(page)
772 |> User.Query.build(%{external: true})
776 def get_followers_ids(user, page \\ nil) do
778 |> get_followers_query(page)
783 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
784 def get_friends_query(%User{} = user, nil) do
785 User.Query.build(%{friends: user, deactivated: false})
788 def get_friends_query(user, page) do
790 |> get_friends_query(nil)
791 |> User.Query.paginate(page, 20)
794 @spec get_friends_query(User.t()) :: Ecto.Query.t()
795 def get_friends_query(user), do: get_friends_query(user, nil)
797 def get_friends(user, page \\ nil) do
799 |> get_friends_query(page)
803 def get_friends_ids(user, page \\ nil) do
805 |> get_friends_query(page)
810 defdelegate get_follow_requests(user), to: FollowingRelationship
812 def increase_note_count(%User{} = user) do
814 |> where(id: ^user.id)
815 |> update([u], inc: [note_count: 1])
817 |> Repo.update_all([])
819 {1, [user]} -> set_cache(user)
824 def decrease_note_count(%User{} = user) do
826 |> where(id: ^user.id)
829 note_count: fragment("greatest(0, note_count - 1)")
833 |> Repo.update_all([])
835 {1, [user]} -> set_cache(user)
840 def update_note_count(%User{} = user, note_count \\ nil) do
845 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
851 |> cast(%{note_count: note_count}, [:note_count])
852 |> update_and_set_cache()
855 @spec maybe_fetch_follow_information(User.t()) :: User.t()
856 def maybe_fetch_follow_information(user) do
857 with {:ok, user} <- fetch_follow_information(user) do
861 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
867 def fetch_follow_information(user) do
868 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
870 |> follow_information_changeset(info)
871 |> update_and_set_cache()
875 defp follow_information_changeset(user, params) do
882 :hide_followers_count,
887 def update_follower_count(%User{} = user) do
888 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
889 follower_count_query =
890 User.Query.build(%{followers: user, deactivated: false})
891 |> select([u], %{count: count(u.id)})
894 |> where(id: ^user.id)
895 |> join(:inner, [u], s in subquery(follower_count_query))
897 set: [follower_count: s.count]
900 |> Repo.update_all([])
902 {1, [user]} -> set_cache(user)
906 {:ok, maybe_fetch_follow_information(user)}
910 @spec maybe_update_following_count(User.t()) :: User.t()
911 def maybe_update_following_count(%User{local: false} = user) do
912 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
913 maybe_fetch_follow_information(user)
919 def maybe_update_following_count(user), do: user
921 def set_unread_conversation_count(%User{local: true} = user) do
922 unread_query = Participation.unread_conversation_count_for_user(user)
925 |> join(:inner, [u], p in subquery(unread_query))
927 set: [unread_conversation_count: p.count]
929 |> where([u], u.id == ^user.id)
931 |> Repo.update_all([])
933 {1, [user]} -> set_cache(user)
938 def set_unread_conversation_count(user), do: {:ok, user}
940 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
942 Participation.unread_conversation_count_for_user(user)
943 |> where([p], p.conversation_id == ^conversation.id)
946 |> join(:inner, [u], p in subquery(unread_query))
948 inc: [unread_conversation_count: 1]
950 |> where([u], u.id == ^user.id)
951 |> where([u, p], p.count == 0)
953 |> Repo.update_all([])
955 {1, [user]} -> set_cache(user)
960 def increment_unread_conversation_count(_, user), do: {:ok, user}
962 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
963 def get_users_from_set(ap_ids, local_only \\ true) do
964 criteria = %{ap_id: ap_ids, deactivated: false}
965 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
967 User.Query.build(criteria)
971 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
972 def get_recipients_from_activity(%Activity{recipients: to}) do
973 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
977 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
978 def mute(muter, %User{} = mutee, notifications? \\ true) do
979 add_to_mutes(muter, mutee, notifications?)
982 def unmute(muter, %User{} = mutee) do
983 remove_from_mutes(muter, mutee)
986 def subscribe(subscriber, %{ap_id: ap_id}) do
987 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
988 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
990 if blocks?(subscribed, subscriber) and deny_follow_blocked do
991 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
993 User.add_to_subscribers(subscribed, subscriber.ap_id)
998 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
999 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1000 User.remove_from_subscribers(user, unsubscriber.ap_id)
1004 def block(blocker, %User{} = blocked) do
1005 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1007 if following?(blocker, blocked) do
1008 {:ok, blocker, _} = unfollow(blocker, blocked)
1014 # clear any requested follows as well
1016 case CommonAPI.reject_follow_request(blocked, blocker) do
1017 {:ok, %User{} = updated_blocked} -> updated_blocked
1022 if subscribed_to?(blocked, blocker) do
1023 {:ok, blocker} = unsubscribe(blocked, blocker)
1029 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1031 {:ok, blocker} = update_follower_count(blocker)
1032 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1033 add_to_block(blocker, blocked)
1036 # helper to handle the block given only an actor's AP id
1037 def block(blocker, %{ap_id: ap_id}) do
1038 block(blocker, get_cached_by_ap_id(ap_id))
1041 def unblock(blocker, %User{} = blocked) do
1042 remove_from_block(blocker, blocked)
1045 # helper to handle the block given only an actor's AP id
1046 def unblock(blocker, %{ap_id: ap_id}) do
1047 unblock(blocker, get_cached_by_ap_id(ap_id))
1050 def mutes?(nil, _), do: false
1051 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1053 def mutes_user?(%User{} = user, %User{} = target) do
1054 UserMute.exists?(user, target)
1057 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1058 def muted_notifications?(nil, _), do: false
1060 def muted_notifications?(user, %{ap_id: ap_id}),
1061 do: Enum.member?(user.muted_notifications, ap_id)
1063 def blocks?(nil, _), do: false
1065 def blocks?(%User{} = user, %User{} = target) do
1066 blocks_user?(user, target) || blocks_domain?(user, target)
1069 def blocks_user?(%User{} = user, %User{} = target) do
1070 UserBlock.exists?(user, target)
1073 def blocks_user?(_, _), do: false
1075 def blocks_domain?(%User{} = user, %User{} = target) do
1076 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1077 %{host: host} = URI.parse(target.ap_id)
1078 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1081 def blocks_domain?(_, _), do: false
1083 def subscribed_to?(user, %{ap_id: ap_id}) do
1084 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1085 Enum.member?(target.subscribers, user.ap_id)
1089 @spec muted_users(User.t()) :: [User.t()]
1090 def muted_users(user) do
1092 |> assoc(:muted_users)
1093 |> restrict_deactivated()
1097 def muted_ap_ids(user) do
1099 |> assoc(:muted_users)
1100 |> select([u], u.ap_id)
1104 @spec blocked_users(User.t()) :: [User.t()]
1105 def blocked_users(user) do
1107 |> assoc(:blocked_users)
1108 |> restrict_deactivated()
1112 def blocked_ap_ids(user) do
1114 |> assoc(:blocked_users)
1115 |> select([u], u.ap_id)
1119 defp related_ap_ids_sql(join_table, source_column, target_column) do
1120 "(SELECT array_agg(u.ap_id) FROM users as u " <>
1121 "INNER JOIN #{join_table} AS join_table " <>
1122 "ON join_table.#{source_column} = $1 " <>
1123 "WHERE u.id = join_table.#{target_column})"
1126 @related_ap_ids_sql_params %{
1127 blocked_users: ["user_blocks", "blocker_id", "blockee_id"],
1128 muted_users: ["user_mutes", "muter_id", "mutee_id"]
1131 def related_ap_ids(user, relations) when is_list(relations) do
1134 |> Enum.map(fn r -> @related_ap_ids_sql_params[r] end)
1135 |> Enum.filter(& &1)
1136 |> Enum.map(fn [join_table, source_column, target_column] ->
1137 related_ap_ids_sql(join_table, source_column, target_column)
1141 with {:ok, %{rows: [ap_ids_arrays]}} <-
1142 Repo.query("SELECT #{query}", [FlakeId.from_string(user.id)]) do
1143 ap_ids_arrays = Enum.map(ap_ids_arrays, &(&1 || []))
1144 {:ok, ap_ids_arrays}
1148 @spec subscribers(User.t()) :: [User.t()]
1149 def subscribers(user) do
1150 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1154 def deactivate_async(user, status \\ true) do
1155 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1158 def deactivate(user, status \\ true)
1160 def deactivate(users, status) when is_list(users) do
1161 Repo.transaction(fn ->
1162 for user <- users, do: deactivate(user, status)
1166 def deactivate(%User{} = user, status) do
1167 with {:ok, user} <- set_activation_status(user, status) do
1168 Enum.each(get_followers(user), &invalidate_cache/1)
1170 # Only update local user counts, remote will be update during the next pull.
1173 |> Enum.filter(& &1.local)
1174 |> Enum.each(&update_follower_count/1)
1180 def update_notification_settings(%User{} = user, settings) do
1183 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1186 notification_settings =
1187 user.notification_settings
1188 |> Map.merge(settings)
1189 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1191 params = %{notification_settings: notification_settings}
1194 |> cast(params, [:notification_settings])
1195 |> validate_required([:notification_settings])
1196 |> update_and_set_cache()
1199 def delete(users) when is_list(users) do
1200 for user <- users, do: delete(user)
1203 def delete(%User{} = user) do
1204 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1207 def perform(:force_password_reset, user), do: force_password_reset(user)
1209 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1210 def perform(:delete, %User{} = user) do
1211 {:ok, _user} = ActivityPub.delete(user)
1213 # Remove all relationships
1216 |> Enum.each(fn follower ->
1217 ActivityPub.unfollow(follower, user)
1218 unfollow(follower, user)
1223 |> Enum.each(fn followed ->
1224 ActivityPub.unfollow(user, followed)
1225 unfollow(user, followed)
1228 delete_user_activities(user)
1229 invalidate_cache(user)
1233 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1234 def perform(:fetch_initial_posts, %User{} = user) do
1235 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1237 # Insert all the posts in reverse order, so they're in the right order on the timeline
1238 user.source_data["outbox"]
1239 |> Utils.fetch_ordered_collection(pages)
1241 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1244 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1246 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1247 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1248 when is_list(blocked_identifiers) do
1250 blocked_identifiers,
1251 fn blocked_identifier ->
1252 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1253 {:ok, _user_block} <- block(blocker, blocked),
1254 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1258 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1265 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1266 def perform(:follow_import, %User{} = follower, followed_identifiers)
1267 when is_list(followed_identifiers) do
1269 followed_identifiers,
1270 fn followed_identifier ->
1271 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1272 {:ok, follower} <- maybe_direct_follow(follower, followed),
1273 {:ok, _} <- ActivityPub.follow(follower, followed) do
1277 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1284 @spec external_users_query() :: Ecto.Query.t()
1285 def external_users_query do
1293 @spec external_users(keyword()) :: [User.t()]
1294 def external_users(opts \\ []) do
1296 external_users_query()
1297 |> select([u], struct(u, [:id, :ap_id, :info]))
1301 do: where(query, [u], u.id > ^opts[:max_id]),
1306 do: limit(query, ^opts[:limit]),
1312 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1313 BackgroundWorker.enqueue("blocks_import", %{
1314 "blocker_id" => blocker.id,
1315 "blocked_identifiers" => blocked_identifiers
1319 def follow_import(%User{} = follower, followed_identifiers)
1320 when is_list(followed_identifiers) do
1321 BackgroundWorker.enqueue("follow_import", %{
1322 "follower_id" => follower.id,
1323 "followed_identifiers" => followed_identifiers
1327 def delete_user_activities(%User{ap_id: ap_id}) do
1329 |> Activity.Queries.by_actor()
1330 |> RepoStreamer.chunk_stream(50)
1331 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1335 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1337 |> Object.normalize()
1338 |> ActivityPub.delete()
1341 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1342 object = Object.normalize(activity)
1345 |> get_cached_by_ap_id()
1346 |> ActivityPub.unlike(object)
1349 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1350 object = Object.normalize(activity)
1353 |> get_cached_by_ap_id()
1354 |> ActivityPub.unannounce(object)
1357 defp delete_activity(_activity), do: "Doing nothing"
1359 def html_filter_policy(%User{no_rich_text: true}) do
1360 Pleroma.HTML.Scrubber.TwitterText
1363 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1365 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1367 def get_or_fetch_by_ap_id(ap_id) do
1368 user = get_cached_by_ap_id(ap_id)
1370 if !is_nil(user) and !needs_update?(user) do
1373 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1374 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1376 resp = fetch_by_ap_id(ap_id)
1378 if should_fetch_initial do
1379 with {:ok, %User{} = user} <- resp do
1380 fetch_initial_posts(user)
1388 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1389 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1390 with %User{} = user <- get_cached_by_ap_id(uri) do
1396 |> cast(%{}, [:ap_id, :nickname, :local])
1397 |> put_change(:ap_id, uri)
1398 |> put_change(:nickname, nickname)
1399 |> put_change(:local, true)
1400 |> put_change(:follower_address, uri <> "/followers")
1408 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1411 |> :public_key.pem_decode()
1413 |> :public_key.pem_entry_decode()
1418 def public_key(_), do: {:error, "not found key"}
1420 def get_public_key_for_ap_id(ap_id) do
1421 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1422 {:ok, public_key} <- public_key(user) do
1429 defp blank?(""), do: nil
1430 defp blank?(n), do: n
1432 def insert_or_update_user(data) do
1434 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1435 |> remote_user_creation()
1436 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1440 def ap_enabled?(%User{local: true}), do: true
1441 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1442 def ap_enabled?(_), do: false
1444 @doc "Gets or fetch a user by uri or nickname."
1445 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1446 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1447 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1449 # wait a period of time and return newest version of the User structs
1450 # this is because we have synchronous follow APIs and need to simulate them
1451 # with an async handshake
1452 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1453 with %User{} = a <- get_cached_by_id(a.id),
1454 %User{} = b <- get_cached_by_id(b.id) do
1461 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1462 with :ok <- :timer.sleep(timeout),
1463 %User{} = a <- get_cached_by_id(a.id),
1464 %User{} = b <- get_cached_by_id(b.id) do
1471 def parse_bio(bio) when is_binary(bio) and bio != "" do
1473 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1477 def parse_bio(_), do: ""
1479 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1480 # TODO: get profile URLs other than user.ap_id
1481 profile_urls = [user.ap_id]
1484 |> CommonUtils.format_input("text/plain",
1485 mentions_format: :full,
1486 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1491 def parse_bio(_, _), do: ""
1493 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1494 Repo.transaction(fn ->
1495 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1499 def tag(nickname, tags) when is_binary(nickname),
1500 do: tag(get_by_nickname(nickname), tags)
1502 def tag(%User{} = user, tags),
1503 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1505 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1506 Repo.transaction(fn ->
1507 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1511 def untag(nickname, tags) when is_binary(nickname),
1512 do: untag(get_by_nickname(nickname), tags)
1514 def untag(%User{} = user, tags),
1515 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1517 defp update_tags(%User{} = user, new_tags) do
1518 {:ok, updated_user} =
1520 |> change(%{tags: new_tags})
1521 |> update_and_set_cache()
1526 defp normalize_tags(tags) do
1529 |> Enum.map(&String.downcase/1)
1532 defp local_nickname_regex do
1533 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1534 @extended_local_nickname_regex
1536 @strict_local_nickname_regex
1540 def local_nickname(nickname_or_mention) do
1543 |> String.split("@")
1547 def full_nickname(nickname_or_mention),
1548 do: String.trim_leading(nickname_or_mention, "@")
1550 def error_user(ap_id) do
1554 nickname: "erroruser@example.com",
1555 inserted_at: NaiveDateTime.utc_now()
1559 @spec all_superusers() :: [User.t()]
1560 def all_superusers do
1561 User.Query.build(%{super_users: true, local: true, deactivated: false})
1565 def showing_reblogs?(%User{} = user, %User{} = target) do
1566 target.ap_id not in user.muted_reblogs
1570 The function returns a query to get users with no activity for given interval of days.
1571 Inactive users are those who didn't read any notification, or had any activity where
1572 the user is the activity's actor, during `inactivity_threshold` days.
1573 Deactivated users will not appear in this list.
1577 iex> Pleroma.User.list_inactive_users()
1580 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1581 def list_inactive_users_query(inactivity_threshold \\ 7) do
1582 negative_inactivity_threshold = -inactivity_threshold
1583 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1584 # Subqueries are not supported in `where` clauses, join gets too complicated.
1585 has_read_notifications =
1586 from(n in Pleroma.Notification,
1587 where: n.seen == true,
1589 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1592 |> Pleroma.Repo.all()
1594 from(u in Pleroma.User,
1595 left_join: a in Pleroma.Activity,
1596 on: u.ap_id == a.actor,
1597 where: not is_nil(u.nickname),
1598 where: u.deactivated != ^true,
1599 where: u.id not in ^has_read_notifications,
1602 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1603 is_nil(max(a.inserted_at))
1608 Enable or disable email notifications for user
1612 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1613 Pleroma.User{email_notifications: %{"digest" => true}}
1615 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1616 Pleroma.User{email_notifications: %{"digest" => false}}
1618 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1619 {:ok, t()} | {:error, Ecto.Changeset.t()}
1620 def switch_email_notifications(user, type, status) do
1621 User.update_email_notifications(user, %{type => status})
1625 Set `last_digest_emailed_at` value for the user to current time
1627 @spec touch_last_digest_emailed_at(t()) :: t()
1628 def touch_last_digest_emailed_at(user) do
1629 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1631 {:ok, updated_user} =
1633 |> change(%{last_digest_emailed_at: now})
1634 |> update_and_set_cache()
1639 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1640 def toggle_confirmation(%User{} = user) do
1642 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1643 |> update_and_set_cache()
1646 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1650 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1651 # use instance-default
1652 config = Pleroma.Config.get([:assets, :mascots])
1653 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1654 mascot = Keyword.get(config, default_mascot)
1657 "id" => "default-mascot",
1658 "url" => mascot[:url],
1659 "preview_url" => mascot[:url],
1661 "mime_type" => mascot[:mime_type]
1666 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1668 def ensure_keys_present(%User{} = user) do
1669 with {:ok, pem} <- Keys.generate_rsa_pem() do
1671 |> cast(%{keys: pem}, [:keys])
1672 |> validate_required([:keys])
1673 |> update_and_set_cache()
1677 def get_ap_ids_by_nicknames(nicknames) do
1679 where: u.nickname in ^nicknames,
1685 defdelegate search(query, opts \\ []), to: User.Search
1687 defp put_password_hash(
1688 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1690 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1693 defp put_password_hash(changeset), do: changeset
1695 def is_internal_user?(%User{nickname: nil}), do: true
1696 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1697 def is_internal_user?(_), do: false
1699 # A hack because user delete activities have a fake id for whatever reason
1700 # TODO: Get rid of this
1701 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1703 def get_delivered_users_by_object_id(object_id) do
1705 inner_join: delivery in assoc(u, :deliveries),
1706 where: delivery.object_id == ^object_id
1711 def change_email(user, email) do
1713 |> cast(%{email: email}, [:email])
1714 |> validate_required([:email])
1715 |> unique_constraint(:email)
1716 |> validate_format(:email, @email_regex)
1717 |> update_and_set_cache()
1720 # Internal function; public one is `deactivate/2`
1721 defp set_activation_status(user, deactivated) do
1723 |> cast(%{deactivated: deactivated}, [:deactivated])
1724 |> update_and_set_cache()
1727 def update_banner(user, banner) do
1729 |> cast(%{banner: banner}, [:banner])
1730 |> update_and_set_cache()
1733 def update_background(user, background) do
1735 |> cast(%{background: background}, [:background])
1736 |> update_and_set_cache()
1739 def update_source_data(user, source_data) do
1741 |> cast(%{source_data: source_data}, [:source_data])
1742 |> update_and_set_cache()
1745 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1748 moderator: is_moderator
1752 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1753 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1754 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1755 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1758 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1759 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1763 def fields(%{fields: nil}), do: []
1765 def fields(%{fields: fields}), do: fields
1767 def validate_fields(changeset, remote? \\ false) do
1768 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1769 limit = Pleroma.Config.get([:instance, limit_name], 0)
1772 |> validate_length(:fields, max: limit)
1773 |> validate_change(:fields, fn :fields, fields ->
1774 if Enum.all?(fields, &valid_field?/1) do
1782 defp valid_field?(%{"name" => name, "value" => value}) do
1783 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1784 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1786 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1787 String.length(value) <= value_limit
1790 defp valid_field?(_), do: false
1792 defp truncate_field(%{"name" => name, "value" => value}) do
1794 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1797 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1799 %{"name" => name, "value" => value}
1802 def admin_api_update(user, params) do
1809 |> update_and_set_cache()
1812 def mascot_update(user, url) do
1814 |> cast(%{mascot: url}, [:mascot])
1815 |> validate_required([:mascot])
1816 |> update_and_set_cache()
1819 def mastodon_settings_update(user, settings) do
1821 |> cast(%{settings: settings}, [:settings])
1822 |> validate_required([:settings])
1823 |> update_and_set_cache()
1826 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1827 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1829 if need_confirmation? do
1831 confirmation_pending: true,
1832 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1836 confirmation_pending: false,
1837 confirmation_token: nil
1841 cast(user, params, [:confirmation_pending, :confirmation_token])
1844 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1845 if id not in user.pinned_activities do
1846 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1847 params = %{pinned_activities: user.pinned_activities ++ [id]}
1850 |> cast(params, [:pinned_activities])
1851 |> validate_length(:pinned_activities,
1852 max: max_pinned_statuses,
1853 message: "You have already pinned the maximum number of statuses"
1858 |> update_and_set_cache()
1861 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1862 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1865 |> cast(params, [:pinned_activities])
1866 |> update_and_set_cache()
1869 def update_email_notifications(user, settings) do
1870 email_notifications =
1871 user.email_notifications
1872 |> Map.merge(settings)
1873 |> Map.take(["digest"])
1875 params = %{email_notifications: email_notifications}
1876 fields = [:email_notifications]
1879 |> cast(params, fields)
1880 |> validate_required(fields)
1881 |> update_and_set_cache()
1884 defp set_subscribers(user, subscribers) do
1885 params = %{subscribers: subscribers}
1888 |> cast(params, [:subscribers])
1889 |> validate_required([:subscribers])
1890 |> update_and_set_cache()
1893 def add_to_subscribers(user, subscribed) do
1894 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1897 def remove_from_subscribers(user, subscribed) do
1898 set_subscribers(user, List.delete(user.subscribers, subscribed))
1901 defp set_domain_blocks(user, domain_blocks) do
1902 params = %{domain_blocks: domain_blocks}
1905 |> cast(params, [:domain_blocks])
1906 |> validate_required([:domain_blocks])
1907 |> update_and_set_cache()
1910 def block_domain(user, domain_blocked) do
1911 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1914 def unblock_domain(user, domain_blocked) do
1915 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1918 @spec add_to_block(User.t(), User.t()) :: {:ok, UserBlock.t()} | {:error, Ecto.Changeset.t()}
1919 defp add_to_block(%User{} = user, %User{} = blocked) do
1920 UserBlock.create(user, blocked)
1923 @spec add_to_block(User.t(), User.t()) ::
1924 {:ok, UserBlock.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
1925 defp remove_from_block(%User{} = user, %User{} = blocked) do
1926 UserBlock.delete(user, blocked)
1929 defp add_to_mutes(%User{} = user, %User{ap_id: ap_id} = muted_user, notifications?) do
1930 with {:ok, user_mute} <- UserMute.create(user, muted_user),
1932 set_notification_mutes(
1934 Enum.uniq([ap_id | user.muted_notifications]),
1941 defp remove_from_mutes(user, %User{ap_id: ap_id} = muted_user) do
1942 with {:ok, user_mute} <- UserMute.delete(user, muted_user),
1944 set_notification_mutes(
1946 List.delete(user.muted_notifications, ap_id),
1953 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1957 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1958 params = %{muted_notifications: muted_notifications}
1961 |> cast(params, [:muted_notifications])
1962 |> validate_required([:muted_notifications])
1963 |> update_and_set_cache()
1966 def add_reblog_mute(user, ap_id) do
1967 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1970 |> cast(params, [:muted_reblogs])
1971 |> update_and_set_cache()
1974 def remove_reblog_mute(user, ap_id) do
1975 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1978 |> cast(params, [:muted_reblogs])
1979 |> update_and_set_cache()
1982 def set_invisible(user, invisible) do
1983 params = %{invisible: invisible}
1986 |> cast(params, [:invisible])
1987 |> validate_required([:invisible])
1988 |> update_and_set_cache()