1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.User do
13 alias Pleroma.Activity
14 alias Pleroma.Conversation.Participation
15 alias Pleroma.Delivery
16 alias Pleroma.FollowingRelationship
18 alias Pleroma.Notification
20 alias Pleroma.Registration
22 alias Pleroma.RepoStreamer
25 alias Pleroma.Web.ActivityPub.ActivityPub
26 alias Pleroma.Web.ActivityPub.Utils
27 alias Pleroma.Web.CommonAPI
28 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
29 alias Pleroma.Web.OAuth
30 alias Pleroma.Web.RelMe
31 alias Pleroma.Workers.BackgroundWorker
35 @type t :: %__MODULE__{}
37 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
39 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
40 @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
42 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
43 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
47 field(:email, :string)
49 field(:nickname, :string)
50 field(:password_hash, :string)
51 field(:password, :string, virtual: true)
52 field(:password_confirmation, :string, virtual: true)
54 field(:ap_id, :string)
56 field(:local, :boolean, default: true)
57 field(:follower_address, :string)
58 field(:following_address, :string)
59 field(:search_rank, :float, virtual: true)
60 field(:search_type, :integer, virtual: true)
61 field(:tags, {:array, :string}, default: [])
62 field(:last_refreshed_at, :naive_datetime_usec)
63 field(:last_digest_emailed_at, :naive_datetime)
65 field(:banner, :map, default: %{})
66 field(:background, :map, default: %{})
67 field(:source_data, :map, default: %{})
68 field(:note_count, :integer, default: 0)
69 field(:follower_count, :integer, default: 0)
70 field(:following_count, :integer, default: 0)
71 field(:locked, :boolean, default: false)
72 field(:confirmation_pending, :boolean, default: false)
73 field(:password_reset_pending, :boolean, default: false)
74 field(:confirmation_token, :string, default: nil)
75 field(:default_scope, :string, default: "public")
76 field(:blocks, {:array, :string}, default: [])
77 field(:domain_blocks, {:array, :string}, default: [])
78 field(:mutes, {:array, :string}, default: [])
79 field(:muted_reblogs, {:array, :string}, default: [])
80 field(:muted_notifications, {:array, :string}, default: [])
81 field(:subscribers, {:array, :string}, default: [])
82 field(:deactivated, :boolean, default: false)
83 field(:no_rich_text, :boolean, default: false)
84 field(:ap_enabled, :boolean, default: false)
85 field(:is_moderator, :boolean, default: false)
86 field(:is_admin, :boolean, default: false)
87 field(:show_role, :boolean, default: true)
88 field(:settings, :map, default: nil)
89 field(:magic_key, :string, default: nil)
90 field(:uri, :string, default: nil)
91 field(:hide_followers_count, :boolean, default: false)
92 field(:hide_follows_count, :boolean, default: false)
93 field(:hide_followers, :boolean, default: false)
94 field(:hide_follows, :boolean, default: false)
95 field(:hide_favorites, :boolean, default: true)
96 field(:unread_conversation_count, :integer, default: 0)
97 field(:pinned_activities, {:array, :string}, default: [])
98 field(:email_notifications, :map, default: %{"digest" => false})
99 field(:mascot, :map, default: nil)
100 field(:emoji, {:array, :map}, default: [])
101 field(:pleroma_settings_store, :map, default: %{})
102 field(:fields, {:array, :map}, default: [])
103 field(:raw_fields, {:array, :map}, default: [])
104 field(:discoverable, :boolean, default: false)
105 field(:invisible, :boolean, default: false)
106 field(:skip_thread_containment, :boolean, default: false)
108 field(:notification_settings, :map,
112 "non_follows" => true,
113 "non_followers" => true
117 has_many(:notifications, Notification)
118 has_many(:registrations, Registration)
119 has_many(:deliveries, Delivery)
121 field(:info, :map, default: %{})
126 @doc "Returns if the user should be allowed to authenticate"
127 def auth_active?(%User{deactivated: true}), do: false
129 def auth_active?(%User{confirmation_pending: true}),
130 do: !Pleroma.Config.get([:instance, :account_activation_required])
132 def auth_active?(%User{}), do: true
134 def visible_for?(user, for_user \\ nil)
136 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
138 def visible_for?(%User{} = user, for_user) do
139 auth_active?(user) || superuser?(for_user)
142 def visible_for?(_, _), do: false
144 def superuser?(%User{local: true, is_admin: true}), do: true
145 def superuser?(%User{local: true, is_moderator: true}), do: true
146 def superuser?(_), do: false
148 def invisible?(%User{invisible: true}), do: true
149 def invisible?(_), do: false
151 def avatar_url(user, options \\ []) do
153 %{"url" => [%{"href" => href} | _]} -> href
154 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
158 def banner_url(user, options \\ []) do
160 %{"url" => [%{"href" => href} | _]} -> href
161 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
165 def profile_url(%User{source_data: %{"url" => url}}), do: url
166 def profile_url(%User{ap_id: ap_id}), do: ap_id
167 def profile_url(_), do: nil
169 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
171 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
172 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
174 @spec ap_following(User.t()) :: Sring.t()
175 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
176 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
178 def user_info(%User{} = user, args \\ %{}) do
179 following_count = Map.get(args, :following_count, user.following_count)
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,
187 follower_count: follower_count,
188 following_count: following_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 needs_update?(%User{local: true}), do: false
494 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
496 def needs_update?(%User{local: false} = user) do
497 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
500 def needs_update?(_), do: true
502 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
503 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
504 follow(follower, followed, "pending")
507 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
508 follow(follower, followed)
511 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
512 if not ap_enabled?(followed) do
513 follow(follower, followed)
519 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
520 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
521 def follow_all(follower, followeds) do
523 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
524 |> Enum.each(&follow(follower, &1, "accept"))
529 defdelegate following(user), to: FollowingRelationship
531 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
532 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
535 followed.deactivated ->
536 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
538 deny_follow_blocked and blocks?(followed, follower) ->
539 {:error, "Could not follow user: #{followed.nickname} blocked you."}
542 FollowingRelationship.follow(follower, followed, state)
544 {:ok, _} = update_follower_count(followed)
547 |> update_following_count()
552 def unfollow(%User{} = follower, %User{} = followed) do
553 if following?(follower, followed) and follower.ap_id != followed.ap_id do
554 FollowingRelationship.unfollow(follower, followed)
556 {:ok, followed} = update_follower_count(followed)
560 |> update_following_count()
563 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
565 {:error, "Not subscribed!"}
569 defdelegate following?(follower, followed), to: FollowingRelationship
571 def locked?(%User{} = user) do
576 Repo.get_by(User, id: id)
579 def get_by_ap_id(ap_id) do
580 Repo.get_by(User, ap_id: ap_id)
583 def get_all_by_ap_id(ap_ids) do
584 from(u in __MODULE__,
585 where: u.ap_id in ^ap_ids
590 def get_all_by_ids(ids) do
591 from(u in __MODULE__, where: u.id in ^ids)
595 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
596 # of the ap_id and the domain and tries to get that user
597 def get_by_guessed_nickname(ap_id) do
598 domain = URI.parse(ap_id).host
599 name = List.last(String.split(ap_id, "/"))
600 nickname = "#{name}@#{domain}"
602 get_cached_by_nickname(nickname)
605 def set_cache({:ok, user}), do: set_cache(user)
606 def set_cache({:error, err}), do: {:error, err}
608 def set_cache(%User{} = user) do
609 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
610 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
611 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
615 def update_and_set_cache(struct, params) do
617 |> update_changeset(params)
618 |> update_and_set_cache()
621 def update_and_set_cache(changeset) do
622 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
627 def invalidate_cache(user) do
628 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
629 Cachex.del(:user_cache, "nickname:#{user.nickname}")
630 Cachex.del(:user_cache, "user_info:#{user.id}")
633 def get_cached_by_ap_id(ap_id) do
634 key = "ap_id:#{ap_id}"
635 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
638 def get_cached_by_id(id) do
642 Cachex.fetch!(:user_cache, key, fn _ ->
646 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
647 {:commit, user.ap_id}
653 get_cached_by_ap_id(ap_id)
656 def get_cached_by_nickname(nickname) do
657 key = "nickname:#{nickname}"
659 Cachex.fetch!(:user_cache, key, fn ->
660 case get_or_fetch_by_nickname(nickname) do
661 {:ok, user} -> {:commit, user}
662 {:error, _error} -> {:ignore, nil}
667 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
668 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
671 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
672 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
674 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
675 get_cached_by_nickname(nickname_or_id)
677 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
678 get_cached_by_nickname(nickname_or_id)
685 def get_by_nickname(nickname) do
686 Repo.get_by(User, nickname: nickname) ||
687 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
688 Repo.get_by(User, nickname: local_nickname(nickname))
692 def get_by_email(email), do: Repo.get_by(User, email: email)
694 def get_by_nickname_or_email(nickname_or_email) do
695 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
698 def get_cached_user_info(user) do
699 key = "user_info:#{user.id}"
700 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
703 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
705 def get_or_fetch_by_nickname(nickname) do
706 with %User{} = user <- get_by_nickname(nickname) do
710 with [_nick, _domain] <- String.split(nickname, "@"),
711 {:ok, user} <- fetch_by_nickname(nickname) do
712 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
713 fetch_initial_posts(user)
718 _e -> {:error, "not found " <> nickname}
723 @doc "Fetch some posts when the user has just been federated with"
724 def fetch_initial_posts(user) do
725 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
728 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
729 def get_followers_query(%User{} = user, nil) do
730 User.Query.build(%{followers: user, deactivated: false})
733 def get_followers_query(user, page) do
735 |> get_followers_query(nil)
736 |> User.Query.paginate(page, 20)
739 @spec get_followers_query(User.t()) :: Ecto.Query.t()
740 def get_followers_query(user), do: get_followers_query(user, nil)
742 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
743 def get_followers(user, page \\ nil) do
745 |> get_followers_query(page)
749 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
750 def get_external_followers(user, page \\ nil) do
752 |> get_followers_query(page)
753 |> User.Query.build(%{external: true})
757 def get_followers_ids(user, page \\ nil) do
759 |> get_followers_query(page)
764 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
765 def get_friends_query(%User{} = user, nil) do
766 User.Query.build(%{friends: user, deactivated: false})
769 def get_friends_query(user, page) do
771 |> get_friends_query(nil)
772 |> User.Query.paginate(page, 20)
775 @spec get_friends_query(User.t()) :: Ecto.Query.t()
776 def get_friends_query(user), do: get_friends_query(user, nil)
778 def get_friends(user, page \\ nil) do
780 |> get_friends_query(page)
784 def get_friends_ids(user, page \\ nil) do
786 |> get_friends_query(page)
791 defdelegate get_follow_requests(user), to: FollowingRelationship
793 def increase_note_count(%User{} = user) do
795 |> where(id: ^user.id)
796 |> update([u], inc: [note_count: 1])
798 |> Repo.update_all([])
800 {1, [user]} -> set_cache(user)
805 def decrease_note_count(%User{} = user) do
807 |> where(id: ^user.id)
810 note_count: fragment("greatest(0, note_count - 1)")
814 |> Repo.update_all([])
816 {1, [user]} -> set_cache(user)
821 def update_note_count(%User{} = user, note_count \\ nil) do
826 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
832 |> cast(%{note_count: note_count}, [:note_count])
833 |> update_and_set_cache()
836 @spec maybe_fetch_follow_information(User.t()) :: User.t()
837 def maybe_fetch_follow_information(user) do
838 with {:ok, user} <- fetch_follow_information(user) do
842 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
848 def fetch_follow_information(user) do
849 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
851 |> follow_information_changeset(info)
852 |> update_and_set_cache()
856 defp follow_information_changeset(user, params) do
863 :hide_followers_count,
868 def update_follower_count(%User{} = user) do
869 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
870 follower_count_query =
871 User.Query.build(%{followers: user, deactivated: false})
872 |> select([u], %{count: count(u.id)})
875 |> where(id: ^user.id)
876 |> join(:inner, [u], s in subquery(follower_count_query))
878 set: [follower_count: s.count]
881 |> Repo.update_all([])
883 {1, [user]} -> set_cache(user)
887 {:ok, maybe_fetch_follow_information(user)}
891 @spec update_following_count(User.t()) :: User.t()
892 def update_following_count(%User{local: false} = user) do
893 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
894 maybe_fetch_follow_information(user)
900 def update_following_count(%User{local: true} = user) do
901 following_count = FollowingRelationship.following_count(user)
904 |> follow_information_changeset(%{following_count: following_count})
908 def set_unread_conversation_count(%User{local: true} = user) do
909 unread_query = Participation.unread_conversation_count_for_user(user)
912 |> join(:inner, [u], p in subquery(unread_query))
914 set: [unread_conversation_count: p.count]
916 |> where([u], u.id == ^user.id)
918 |> Repo.update_all([])
920 {1, [user]} -> set_cache(user)
925 def set_unread_conversation_count(user), do: {:ok, user}
927 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
929 Participation.unread_conversation_count_for_user(user)
930 |> where([p], p.conversation_id == ^conversation.id)
933 |> join(:inner, [u], p in subquery(unread_query))
935 inc: [unread_conversation_count: 1]
937 |> where([u], u.id == ^user.id)
938 |> where([u, p], p.count == 0)
940 |> Repo.update_all([])
942 {1, [user]} -> set_cache(user)
947 def increment_unread_conversation_count(_, user), do: {:ok, user}
949 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
950 def get_users_from_set(ap_ids, local_only \\ true) do
951 criteria = %{ap_id: ap_ids, deactivated: false}
952 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
954 User.Query.build(criteria)
958 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
959 def get_recipients_from_activity(%Activity{recipients: to}) do
960 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
964 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
965 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
966 add_to_mutes(muter, ap_id, notifications?)
969 def unmute(muter, %{ap_id: ap_id}) do
970 remove_from_mutes(muter, ap_id)
973 def subscribe(subscriber, %{ap_id: ap_id}) do
974 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
975 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
977 if blocks?(subscribed, subscriber) and deny_follow_blocked do
978 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
980 User.add_to_subscribers(subscribed, subscriber.ap_id)
985 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
986 with %User{} = user <- get_cached_by_ap_id(ap_id) do
987 User.remove_from_subscribers(user, unsubscriber.ap_id)
991 def block(blocker, %User{ap_id: ap_id} = blocked) do
992 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
994 if following?(blocker, blocked) do
995 {:ok, blocker, _} = unfollow(blocker, blocked)
1001 # clear any requested follows as well
1003 case CommonAPI.reject_follow_request(blocked, blocker) do
1004 {:ok, %User{} = updated_blocked} -> updated_blocked
1009 if subscribed_to?(blocked, blocker) do
1010 {:ok, blocker} = unsubscribe(blocked, blocker)
1016 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1018 {:ok, blocker} = update_follower_count(blocker)
1019 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1020 add_to_block(blocker, ap_id)
1023 # helper to handle the block given only an actor's AP id
1024 def block(blocker, %{ap_id: ap_id}) do
1025 block(blocker, get_cached_by_ap_id(ap_id))
1028 def unblock(blocker, %{ap_id: ap_id}) do
1029 remove_from_block(blocker, ap_id)
1032 def mutes?(nil, _), do: false
1033 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1035 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1036 def muted_notifications?(nil, _), do: false
1038 def muted_notifications?(user, %{ap_id: ap_id}),
1039 do: Enum.member?(user.muted_notifications, ap_id)
1041 def blocks?(%User{} = user, %User{} = target) do
1042 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1045 def blocks?(nil, _), do: false
1047 def blocks_ap_id?(%User{} = user, %User{} = target) do
1048 Enum.member?(user.blocks, target.ap_id)
1051 def blocks_ap_id?(_, _), do: false
1053 def blocks_domain?(%User{} = user, %User{} = target) do
1054 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1055 %{host: host} = URI.parse(target.ap_id)
1056 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1059 def blocks_domain?(_, _), do: false
1061 def subscribed_to?(user, %{ap_id: ap_id}) do
1062 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1063 Enum.member?(target.subscribers, user.ap_id)
1067 @spec muted_users(User.t()) :: [User.t()]
1068 def muted_users(user) do
1069 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1073 @spec blocked_users(User.t()) :: [User.t()]
1074 def blocked_users(user) do
1075 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1079 @spec subscribers(User.t()) :: [User.t()]
1080 def subscribers(user) do
1081 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1085 def deactivate_async(user, status \\ true) do
1086 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1089 def deactivate(user, status \\ true)
1091 def deactivate(users, status) when is_list(users) do
1092 Repo.transaction(fn ->
1093 for user <- users, do: deactivate(user, status)
1097 def deactivate(%User{} = user, status) do
1098 with {:ok, user} <- set_activation_status(user, status) do
1101 |> Enum.filter(& &1.local)
1102 |> Enum.each(fn follower ->
1103 follower |> update_following_count() |> set_cache()
1106 # Only update local user counts, remote will be update during the next pull.
1109 |> Enum.filter(& &1.local)
1110 |> Enum.each(&update_follower_count/1)
1116 def update_notification_settings(%User{} = user, settings) do
1119 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1122 notification_settings =
1123 user.notification_settings
1124 |> Map.merge(settings)
1125 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1127 params = %{notification_settings: notification_settings}
1130 |> cast(params, [:notification_settings])
1131 |> validate_required([:notification_settings])
1132 |> update_and_set_cache()
1135 def delete(users) when is_list(users) do
1136 for user <- users, do: delete(user)
1139 def delete(%User{} = user) do
1140 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1143 def perform(:force_password_reset, user), do: force_password_reset(user)
1145 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1146 def perform(:delete, %User{} = user) do
1147 {:ok, _user} = ActivityPub.delete(user)
1149 # Remove all relationships
1152 |> Enum.each(fn follower ->
1153 ActivityPub.unfollow(follower, user)
1154 unfollow(follower, user)
1159 |> Enum.each(fn followed ->
1160 ActivityPub.unfollow(user, followed)
1161 unfollow(user, followed)
1164 delete_user_activities(user)
1165 invalidate_cache(user)
1169 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1170 def perform(:fetch_initial_posts, %User{} = user) do
1171 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1173 # Insert all the posts in reverse order, so they're in the right order on the timeline
1174 user.source_data["outbox"]
1175 |> Utils.fetch_ordered_collection(pages)
1177 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1180 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1182 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1183 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1184 when is_list(blocked_identifiers) do
1186 blocked_identifiers,
1187 fn blocked_identifier ->
1188 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1189 {:ok, blocker} <- block(blocker, blocked),
1190 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1194 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1201 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1202 def perform(:follow_import, %User{} = follower, followed_identifiers)
1203 when is_list(followed_identifiers) do
1205 followed_identifiers,
1206 fn followed_identifier ->
1207 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1208 {:ok, follower} <- maybe_direct_follow(follower, followed),
1209 {:ok, _} <- ActivityPub.follow(follower, followed) do
1213 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1220 @spec external_users_query() :: Ecto.Query.t()
1221 def external_users_query do
1229 @spec external_users(keyword()) :: [User.t()]
1230 def external_users(opts \\ []) do
1232 external_users_query()
1233 |> select([u], struct(u, [:id, :ap_id, :info]))
1237 do: where(query, [u], u.id > ^opts[:max_id]),
1242 do: limit(query, ^opts[:limit]),
1248 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1249 BackgroundWorker.enqueue("blocks_import", %{
1250 "blocker_id" => blocker.id,
1251 "blocked_identifiers" => blocked_identifiers
1255 def follow_import(%User{} = follower, followed_identifiers)
1256 when is_list(followed_identifiers) do
1257 BackgroundWorker.enqueue("follow_import", %{
1258 "follower_id" => follower.id,
1259 "followed_identifiers" => followed_identifiers
1263 def delete_user_activities(%User{ap_id: ap_id}) do
1265 |> Activity.Queries.by_actor()
1266 |> RepoStreamer.chunk_stream(50)
1267 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1271 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1273 |> Object.normalize()
1274 |> ActivityPub.delete()
1277 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1278 object = Object.normalize(activity)
1281 |> get_cached_by_ap_id()
1282 |> ActivityPub.unlike(object)
1285 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1286 object = Object.normalize(activity)
1289 |> get_cached_by_ap_id()
1290 |> ActivityPub.unannounce(object)
1293 defp delete_activity(_activity), do: "Doing nothing"
1295 def html_filter_policy(%User{no_rich_text: true}) do
1296 Pleroma.HTML.Scrubber.TwitterText
1299 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1301 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1303 def get_or_fetch_by_ap_id(ap_id) do
1304 user = get_cached_by_ap_id(ap_id)
1306 if !is_nil(user) and !needs_update?(user) do
1309 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1310 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1312 resp = fetch_by_ap_id(ap_id)
1314 if should_fetch_initial do
1315 with {:ok, %User{} = user} <- resp do
1316 fetch_initial_posts(user)
1324 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1325 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1326 with %User{} = user <- get_cached_by_ap_id(uri) do
1332 |> cast(%{}, [:ap_id, :nickname, :local])
1333 |> put_change(:ap_id, uri)
1334 |> put_change(:nickname, nickname)
1335 |> put_change(:local, true)
1336 |> put_change(:follower_address, uri <> "/followers")
1344 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1347 |> :public_key.pem_decode()
1349 |> :public_key.pem_entry_decode()
1354 def public_key(_), do: {:error, "not found key"}
1356 def get_public_key_for_ap_id(ap_id) do
1357 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1358 {:ok, public_key} <- public_key(user) do
1365 defp blank?(""), do: nil
1366 defp blank?(n), do: n
1368 def insert_or_update_user(data) do
1370 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1371 |> remote_user_creation()
1372 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1376 def ap_enabled?(%User{local: true}), do: true
1377 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1378 def ap_enabled?(_), do: false
1380 @doc "Gets or fetch a user by uri or nickname."
1381 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1382 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1383 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1385 # wait a period of time and return newest version of the User structs
1386 # this is because we have synchronous follow APIs and need to simulate them
1387 # with an async handshake
1388 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1389 with %User{} = a <- get_cached_by_id(a.id),
1390 %User{} = b <- get_cached_by_id(b.id) do
1397 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1398 with :ok <- :timer.sleep(timeout),
1399 %User{} = a <- get_cached_by_id(a.id),
1400 %User{} = b <- get_cached_by_id(b.id) do
1407 def parse_bio(bio) when is_binary(bio) and bio != "" do
1409 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1413 def parse_bio(_), do: ""
1415 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1416 # TODO: get profile URLs other than user.ap_id
1417 profile_urls = [user.ap_id]
1420 |> CommonUtils.format_input("text/plain",
1421 mentions_format: :full,
1422 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1427 def parse_bio(_, _), do: ""
1429 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1430 Repo.transaction(fn ->
1431 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1435 def tag(nickname, tags) when is_binary(nickname),
1436 do: tag(get_by_nickname(nickname), tags)
1438 def tag(%User{} = user, tags),
1439 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1441 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1442 Repo.transaction(fn ->
1443 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1447 def untag(nickname, tags) when is_binary(nickname),
1448 do: untag(get_by_nickname(nickname), tags)
1450 def untag(%User{} = user, tags),
1451 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1453 defp update_tags(%User{} = user, new_tags) do
1454 {:ok, updated_user} =
1456 |> change(%{tags: new_tags})
1457 |> update_and_set_cache()
1462 defp normalize_tags(tags) do
1465 |> Enum.map(&String.downcase/1)
1468 defp local_nickname_regex do
1469 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1470 @extended_local_nickname_regex
1472 @strict_local_nickname_regex
1476 def local_nickname(nickname_or_mention) do
1479 |> String.split("@")
1483 def full_nickname(nickname_or_mention),
1484 do: String.trim_leading(nickname_or_mention, "@")
1486 def error_user(ap_id) do
1490 nickname: "erroruser@example.com",
1491 inserted_at: NaiveDateTime.utc_now()
1495 @spec all_superusers() :: [User.t()]
1496 def all_superusers do
1497 User.Query.build(%{super_users: true, local: true, deactivated: false})
1501 def showing_reblogs?(%User{} = user, %User{} = target) do
1502 target.ap_id not in user.muted_reblogs
1506 The function returns a query to get users with no activity for given interval of days.
1507 Inactive users are those who didn't read any notification, or had any activity where
1508 the user is the activity's actor, during `inactivity_threshold` days.
1509 Deactivated users will not appear in this list.
1513 iex> Pleroma.User.list_inactive_users()
1516 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1517 def list_inactive_users_query(inactivity_threshold \\ 7) do
1518 negative_inactivity_threshold = -inactivity_threshold
1519 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1520 # Subqueries are not supported in `where` clauses, join gets too complicated.
1521 has_read_notifications =
1522 from(n in Pleroma.Notification,
1523 where: n.seen == true,
1525 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1528 |> Pleroma.Repo.all()
1530 from(u in Pleroma.User,
1531 left_join: a in Pleroma.Activity,
1532 on: u.ap_id == a.actor,
1533 where: not is_nil(u.nickname),
1534 where: u.deactivated != ^true,
1535 where: u.id not in ^has_read_notifications,
1538 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1539 is_nil(max(a.inserted_at))
1544 Enable or disable email notifications for user
1548 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1549 Pleroma.User{email_notifications: %{"digest" => true}}
1551 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1552 Pleroma.User{email_notifications: %{"digest" => false}}
1554 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1555 {:ok, t()} | {:error, Ecto.Changeset.t()}
1556 def switch_email_notifications(user, type, status) do
1557 User.update_email_notifications(user, %{type => status})
1561 Set `last_digest_emailed_at` value for the user to current time
1563 @spec touch_last_digest_emailed_at(t()) :: t()
1564 def touch_last_digest_emailed_at(user) do
1565 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1567 {:ok, updated_user} =
1569 |> change(%{last_digest_emailed_at: now})
1570 |> update_and_set_cache()
1575 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1576 def toggle_confirmation(%User{} = user) do
1578 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1579 |> update_and_set_cache()
1582 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1586 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1587 # use instance-default
1588 config = Pleroma.Config.get([:assets, :mascots])
1589 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1590 mascot = Keyword.get(config, default_mascot)
1593 "id" => "default-mascot",
1594 "url" => mascot[:url],
1595 "preview_url" => mascot[:url],
1597 "mime_type" => mascot[:mime_type]
1602 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1604 def ensure_keys_present(%User{} = user) do
1605 with {:ok, pem} <- Keys.generate_rsa_pem() do
1607 |> cast(%{keys: pem}, [:keys])
1608 |> validate_required([:keys])
1609 |> update_and_set_cache()
1613 def get_ap_ids_by_nicknames(nicknames) do
1615 where: u.nickname in ^nicknames,
1621 defdelegate search(query, opts \\ []), to: User.Search
1623 defp put_password_hash(
1624 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1626 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1629 defp put_password_hash(changeset), do: changeset
1631 def is_internal_user?(%User{nickname: nil}), do: true
1632 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1633 def is_internal_user?(_), do: false
1635 # A hack because user delete activities have a fake id for whatever reason
1636 # TODO: Get rid of this
1637 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1639 def get_delivered_users_by_object_id(object_id) do
1641 inner_join: delivery in assoc(u, :deliveries),
1642 where: delivery.object_id == ^object_id
1647 def change_email(user, email) do
1649 |> cast(%{email: email}, [:email])
1650 |> validate_required([:email])
1651 |> unique_constraint(:email)
1652 |> validate_format(:email, @email_regex)
1653 |> update_and_set_cache()
1656 # Internal function; public one is `deactivate/2`
1657 defp set_activation_status(user, deactivated) do
1659 |> cast(%{deactivated: deactivated}, [:deactivated])
1660 |> update_and_set_cache()
1663 def update_banner(user, banner) do
1665 |> cast(%{banner: banner}, [:banner])
1666 |> update_and_set_cache()
1669 def update_background(user, background) do
1671 |> cast(%{background: background}, [:background])
1672 |> update_and_set_cache()
1675 def update_source_data(user, source_data) do
1677 |> cast(%{source_data: source_data}, [:source_data])
1678 |> update_and_set_cache()
1681 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1684 moderator: is_moderator
1688 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1689 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1690 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1691 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1694 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1695 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1699 def fields(%{fields: nil}), do: []
1701 def fields(%{fields: fields}), do: fields
1703 def validate_fields(changeset, remote? \\ false) do
1704 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1705 limit = Pleroma.Config.get([:instance, limit_name], 0)
1708 |> validate_length(:fields, max: limit)
1709 |> validate_change(:fields, fn :fields, fields ->
1710 if Enum.all?(fields, &valid_field?/1) do
1718 defp valid_field?(%{"name" => name, "value" => value}) do
1719 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1720 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1722 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1723 String.length(value) <= value_limit
1726 defp valid_field?(_), do: false
1728 defp truncate_field(%{"name" => name, "value" => value}) do
1730 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1733 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1735 %{"name" => name, "value" => value}
1738 def admin_api_update(user, params) do
1745 |> update_and_set_cache()
1748 def mascot_update(user, url) do
1750 |> cast(%{mascot: url}, [:mascot])
1751 |> validate_required([:mascot])
1752 |> update_and_set_cache()
1755 def mastodon_settings_update(user, settings) do
1757 |> cast(%{settings: settings}, [:settings])
1758 |> validate_required([:settings])
1759 |> update_and_set_cache()
1762 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1763 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1765 if need_confirmation? do
1767 confirmation_pending: true,
1768 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1772 confirmation_pending: false,
1773 confirmation_token: nil
1777 cast(user, params, [:confirmation_pending, :confirmation_token])
1780 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1781 if id not in user.pinned_activities do
1782 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1783 params = %{pinned_activities: user.pinned_activities ++ [id]}
1786 |> cast(params, [:pinned_activities])
1787 |> validate_length(:pinned_activities,
1788 max: max_pinned_statuses,
1789 message: "You have already pinned the maximum number of statuses"
1794 |> update_and_set_cache()
1797 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1798 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1801 |> cast(params, [:pinned_activities])
1802 |> update_and_set_cache()
1805 def update_email_notifications(user, settings) do
1806 email_notifications =
1807 user.email_notifications
1808 |> Map.merge(settings)
1809 |> Map.take(["digest"])
1811 params = %{email_notifications: email_notifications}
1812 fields = [:email_notifications]
1815 |> cast(params, fields)
1816 |> validate_required(fields)
1817 |> update_and_set_cache()
1820 defp set_subscribers(user, subscribers) do
1821 params = %{subscribers: subscribers}
1824 |> cast(params, [:subscribers])
1825 |> validate_required([:subscribers])
1826 |> update_and_set_cache()
1829 def add_to_subscribers(user, subscribed) do
1830 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1833 def remove_from_subscribers(user, subscribed) do
1834 set_subscribers(user, List.delete(user.subscribers, subscribed))
1837 defp set_domain_blocks(user, domain_blocks) do
1838 params = %{domain_blocks: domain_blocks}
1841 |> cast(params, [:domain_blocks])
1842 |> validate_required([:domain_blocks])
1843 |> update_and_set_cache()
1846 def block_domain(user, domain_blocked) do
1847 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1850 def unblock_domain(user, domain_blocked) do
1851 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1854 defp set_blocks(user, blocks) do
1855 params = %{blocks: blocks}
1858 |> cast(params, [:blocks])
1859 |> validate_required([:blocks])
1860 |> update_and_set_cache()
1863 def add_to_block(user, blocked) do
1864 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1867 def remove_from_block(user, blocked) do
1868 set_blocks(user, List.delete(user.blocks, blocked))
1871 defp set_mutes(user, mutes) do
1872 params = %{mutes: mutes}
1875 |> cast(params, [:mutes])
1876 |> validate_required([:mutes])
1877 |> update_and_set_cache()
1880 def add_to_mutes(user, muted, notifications?) do
1881 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1882 set_notification_mutes(
1884 Enum.uniq([muted | user.muted_notifications]),
1890 def remove_from_mutes(user, muted) do
1891 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1892 set_notification_mutes(
1894 List.delete(user.muted_notifications, muted),
1900 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1904 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1905 params = %{muted_notifications: muted_notifications}
1908 |> cast(params, [:muted_notifications])
1909 |> validate_required([:muted_notifications])
1910 |> update_and_set_cache()
1913 def add_reblog_mute(user, ap_id) do
1914 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1917 |> cast(params, [:muted_reblogs])
1918 |> update_and_set_cache()
1921 def remove_reblog_mute(user, ap_id) do
1922 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1925 |> cast(params, [:muted_reblogs])
1926 |> update_and_set_cache()
1929 def set_invisible(user, invisible) do
1930 params = %{invisible: invisible}
1933 |> cast(params, [:invisible])
1934 |> validate_required([:invisible])
1935 |> update_and_set_cache()