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(:allow_following_move, :boolean, default: true)
107 field(:skip_thread_containment, :boolean, default: false)
108 field(:also_known_as, {:array, :string}, default: [])
111 :notification_settings,
112 Pleroma.User.NotificationSetting,
116 has_many(:notifications, Notification)
117 has_many(:registrations, Registration)
118 has_many(:deliveries, Delivery)
123 @doc "Returns if the user should be allowed to authenticate"
124 def auth_active?(%User{deactivated: true}), do: false
126 def auth_active?(%User{confirmation_pending: true}),
127 do: !Pleroma.Config.get([:instance, :account_activation_required])
129 def auth_active?(%User{}), do: true
131 def visible_for?(user, for_user \\ nil)
133 def visible_for?(%User{invisible: true}, _), do: false
135 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
137 def visible_for?(%User{} = user, for_user) do
138 auth_active?(user) || superuser?(for_user)
141 def visible_for?(_, _), do: false
143 def superuser?(%User{local: true, is_admin: true}), do: true
144 def superuser?(%User{local: true, is_moderator: true}), do: true
145 def superuser?(_), do: false
147 def invisible?(%User{invisible: true}), do: true
148 def invisible?(_), do: false
150 def avatar_url(user, options \\ []) do
152 %{"url" => [%{"href" => href} | _]} -> href
153 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
157 def banner_url(user, options \\ []) do
159 %{"url" => [%{"href" => href} | _]} -> href
160 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
164 def profile_url(%User{source_data: %{"url" => url}}), do: url
165 def profile_url(%User{ap_id: ap_id}), do: ap_id
166 def profile_url(_), do: nil
168 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
170 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
171 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
173 @spec ap_following(User.t()) :: Sring.t()
174 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
175 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
177 def follow_state(%User{} = user, %User{} = target) do
178 case Utils.fetch_latest_follow(user, target) do
179 %{data: %{"state" => state}} -> state
180 # Ideally this would be nil, but then Cachex does not commit the value
185 def get_cached_follow_state(user, target) do
186 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
187 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
190 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
191 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
192 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
195 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
196 def restrict_deactivated(query) do
197 from(u in query, where: u.deactivated != ^true)
200 defdelegate following_count(user), to: FollowingRelationship
202 defp truncate_fields_param(params) do
203 if Map.has_key?(params, :fields) do
204 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
210 defp truncate_if_exists(params, key, max_length) do
211 if Map.has_key?(params, key) and is_binary(params[key]) do
212 {value, _chopped} = String.split_at(params[key], max_length)
213 Map.put(params, key, value)
219 def remote_user_creation(params) do
220 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
221 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
225 |> truncate_if_exists(:name, name_limit)
226 |> truncate_if_exists(:bio, bio_limit)
227 |> truncate_fields_param()
247 :hide_followers_count,
257 |> validate_required([:name, :ap_id])
258 |> unique_constraint(:nickname)
259 |> validate_format(:nickname, @email_regex)
260 |> validate_length(:bio, max: bio_limit)
261 |> validate_length(:name, max: name_limit)
262 |> validate_fields(true)
264 case params[:source_data] do
265 %{"followers" => followers, "following" => following} ->
267 |> put_change(:follower_address, followers)
268 |> put_change(:following_address, following)
271 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
272 put_change(changeset, :follower_address, followers)
276 def update_changeset(struct, params \\ %{}) do
277 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
278 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
293 :hide_followers_count,
296 :allow_following_move,
299 :skip_thread_containment,
302 :pleroma_settings_store,
307 |> unique_constraint(:nickname)
308 |> validate_format(:nickname, local_nickname_regex())
309 |> validate_length(:bio, max: bio_limit)
310 |> validate_length(:name, min: 1, max: name_limit)
311 |> validate_fields(false)
314 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
315 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
316 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
318 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
320 params = if remote?, do: truncate_fields_param(params), else: params
342 :allow_following_move,
344 :hide_followers_count,
349 |> unique_constraint(:nickname)
350 |> validate_format(:nickname, local_nickname_regex())
351 |> validate_length(:bio, max: bio_limit)
352 |> validate_length(:name, max: name_limit)
353 |> validate_fields(remote?)
356 def password_update_changeset(struct, params) do
358 |> cast(params, [:password, :password_confirmation])
359 |> validate_required([:password, :password_confirmation])
360 |> validate_confirmation(:password)
361 |> put_password_hash()
362 |> put_change(:password_reset_pending, false)
365 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
366 def reset_password(%User{id: user_id} = user, data) do
369 |> Multi.update(:user, password_update_changeset(user, data))
370 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
371 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
373 case Repo.transaction(multi) do
374 {:ok, %{user: user} = _} -> set_cache(user)
375 {:error, _, changeset, _} -> {:error, changeset}
379 def update_password_reset_pending(user, value) do
382 |> put_change(:password_reset_pending, value)
383 |> update_and_set_cache()
386 def force_password_reset_async(user) do
387 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
390 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
391 def force_password_reset(user), do: update_password_reset_pending(user, true)
393 def register_changeset(struct, params \\ %{}, opts \\ []) do
394 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
395 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
398 if is_nil(opts[:need_confirmation]) do
399 Pleroma.Config.get([:instance, :account_activation_required])
401 opts[:need_confirmation]
405 |> confirmation_changeset(need_confirmation: need_confirmation?)
406 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
407 |> validate_required([:name, :nickname, :password, :password_confirmation])
408 |> validate_confirmation(:password)
409 |> unique_constraint(:email)
410 |> unique_constraint(:nickname)
411 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
412 |> validate_format(:nickname, local_nickname_regex())
413 |> validate_format(:email, @email_regex)
414 |> validate_length(:bio, max: bio_limit)
415 |> validate_length(:name, min: 1, max: name_limit)
416 |> maybe_validate_required_email(opts[:external])
419 |> unique_constraint(:ap_id)
420 |> put_following_and_follower_address()
423 def maybe_validate_required_email(changeset, true), do: changeset
424 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
426 defp put_ap_id(changeset) do
427 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
428 put_change(changeset, :ap_id, ap_id)
431 defp put_following_and_follower_address(changeset) do
432 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
435 |> put_change(:follower_address, followers)
438 defp autofollow_users(user) do
439 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
442 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
445 follow_all(user, autofollowed_users)
448 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
449 def register(%Ecto.Changeset{} = changeset) do
450 with {:ok, user} <- Repo.insert(changeset) do
451 post_register_action(user)
455 def post_register_action(%User{} = user) do
456 with {:ok, user} <- autofollow_users(user),
457 {:ok, user} <- set_cache(user),
458 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
459 {:ok, _} <- try_send_confirmation_email(user) do
464 def try_send_confirmation_email(%User{} = user) do
465 if user.confirmation_pending &&
466 Pleroma.Config.get([:instance, :account_activation_required]) do
468 |> Pleroma.Emails.UserEmail.account_confirmation_email()
469 |> Pleroma.Emails.Mailer.deliver_async()
477 def try_send_confirmation_email(users) do
478 Enum.each(users, &try_send_confirmation_email/1)
481 def needs_update?(%User{local: true}), do: false
483 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
485 def needs_update?(%User{local: false} = user) do
486 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
489 def needs_update?(_), do: true
491 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
492 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
493 follow(follower, followed, "pending")
496 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
497 follow(follower, followed)
500 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
501 if not ap_enabled?(followed) do
502 follow(follower, followed)
508 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
509 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
510 def follow_all(follower, followeds) do
512 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
513 |> Enum.each(&follow(follower, &1, "accept"))
518 defdelegate following(user), to: FollowingRelationship
520 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
521 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
524 followed.deactivated ->
525 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
527 deny_follow_blocked and blocks?(followed, follower) ->
528 {:error, "Could not follow user: #{followed.nickname} blocked you."}
531 FollowingRelationship.follow(follower, followed, state)
533 {:ok, _} = update_follower_count(followed)
536 |> update_following_count()
541 def unfollow(%User{} = follower, %User{} = followed) do
542 if following?(follower, followed) and follower.ap_id != followed.ap_id do
543 FollowingRelationship.unfollow(follower, followed)
545 {:ok, followed} = update_follower_count(followed)
549 |> update_following_count()
552 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
554 {:error, "Not subscribed!"}
558 defdelegate following?(follower, followed), to: FollowingRelationship
560 def locked?(%User{} = user) do
565 Repo.get_by(User, id: id)
568 def get_by_ap_id(ap_id) do
569 Repo.get_by(User, ap_id: ap_id)
572 def get_all_by_ap_id(ap_ids) do
573 from(u in __MODULE__,
574 where: u.ap_id in ^ap_ids
579 def get_all_by_ids(ids) do
580 from(u in __MODULE__, where: u.id in ^ids)
584 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
585 # of the ap_id and the domain and tries to get that user
586 def get_by_guessed_nickname(ap_id) do
587 domain = URI.parse(ap_id).host
588 name = List.last(String.split(ap_id, "/"))
589 nickname = "#{name}@#{domain}"
591 get_cached_by_nickname(nickname)
594 def set_cache({:ok, user}), do: set_cache(user)
595 def set_cache({:error, err}), do: {:error, err}
597 def set_cache(%User{} = user) do
598 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
599 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
603 def update_and_set_cache(struct, params) do
605 |> update_changeset(params)
606 |> update_and_set_cache()
609 def update_and_set_cache(changeset) do
610 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
615 def invalidate_cache(user) do
616 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
617 Cachex.del(:user_cache, "nickname:#{user.nickname}")
620 def get_cached_by_ap_id(ap_id) do
621 key = "ap_id:#{ap_id}"
622 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
625 def get_cached_by_id(id) do
629 Cachex.fetch!(:user_cache, key, fn _ ->
633 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
634 {:commit, user.ap_id}
640 get_cached_by_ap_id(ap_id)
643 def get_cached_by_nickname(nickname) do
644 key = "nickname:#{nickname}"
646 Cachex.fetch!(:user_cache, key, fn ->
647 case get_or_fetch_by_nickname(nickname) do
648 {:ok, user} -> {:commit, user}
649 {:error, _error} -> {:ignore, nil}
654 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
655 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
658 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
659 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
661 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
662 get_cached_by_nickname(nickname_or_id)
664 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
665 get_cached_by_nickname(nickname_or_id)
672 def get_by_nickname(nickname) do
673 Repo.get_by(User, nickname: nickname) ||
674 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
675 Repo.get_by(User, nickname: local_nickname(nickname))
679 def get_by_email(email), do: Repo.get_by(User, email: email)
681 def get_by_nickname_or_email(nickname_or_email) do
682 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
685 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
687 def get_or_fetch_by_nickname(nickname) do
688 with %User{} = user <- get_by_nickname(nickname) do
692 with [_nick, _domain] <- String.split(nickname, "@"),
693 {:ok, user} <- fetch_by_nickname(nickname) do
694 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
695 fetch_initial_posts(user)
700 _e -> {:error, "not found " <> nickname}
705 @doc "Fetch some posts when the user has just been federated with"
706 def fetch_initial_posts(user) do
707 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
710 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
711 def get_followers_query(%User{} = user, nil) do
712 User.Query.build(%{followers: user, deactivated: false})
715 def get_followers_query(user, page) do
717 |> get_followers_query(nil)
718 |> User.Query.paginate(page, 20)
721 @spec get_followers_query(User.t()) :: Ecto.Query.t()
722 def get_followers_query(user), do: get_followers_query(user, nil)
724 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
725 def get_followers(user, page \\ nil) do
727 |> get_followers_query(page)
731 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
732 def get_external_followers(user, page \\ nil) do
734 |> get_followers_query(page)
735 |> User.Query.build(%{external: true})
739 def get_followers_ids(user, page \\ nil) do
741 |> get_followers_query(page)
746 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
747 def get_friends_query(%User{} = user, nil) do
748 User.Query.build(%{friends: user, deactivated: false})
751 def get_friends_query(user, page) do
753 |> get_friends_query(nil)
754 |> User.Query.paginate(page, 20)
757 @spec get_friends_query(User.t()) :: Ecto.Query.t()
758 def get_friends_query(user), do: get_friends_query(user, nil)
760 def get_friends(user, page \\ nil) do
762 |> get_friends_query(page)
766 def get_friends_ids(user, page \\ nil) do
768 |> get_friends_query(page)
773 defdelegate get_follow_requests(user), to: FollowingRelationship
775 def increase_note_count(%User{} = user) do
777 |> where(id: ^user.id)
778 |> update([u], inc: [note_count: 1])
780 |> Repo.update_all([])
782 {1, [user]} -> set_cache(user)
787 def decrease_note_count(%User{} = user) do
789 |> where(id: ^user.id)
792 note_count: fragment("greatest(0, note_count - 1)")
796 |> Repo.update_all([])
798 {1, [user]} -> set_cache(user)
803 def update_note_count(%User{} = user, note_count \\ nil) do
808 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
814 |> cast(%{note_count: note_count}, [:note_count])
815 |> update_and_set_cache()
818 @spec maybe_fetch_follow_information(User.t()) :: User.t()
819 def maybe_fetch_follow_information(user) do
820 with {:ok, user} <- fetch_follow_information(user) do
824 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
830 def fetch_follow_information(user) do
831 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
833 |> follow_information_changeset(info)
834 |> update_and_set_cache()
838 defp follow_information_changeset(user, params) do
845 :hide_followers_count,
850 def update_follower_count(%User{} = user) do
851 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
852 follower_count_query =
853 User.Query.build(%{followers: user, deactivated: false})
854 |> select([u], %{count: count(u.id)})
857 |> where(id: ^user.id)
858 |> join(:inner, [u], s in subquery(follower_count_query))
860 set: [follower_count: s.count]
863 |> Repo.update_all([])
865 {1, [user]} -> set_cache(user)
869 {:ok, maybe_fetch_follow_information(user)}
873 @spec update_following_count(User.t()) :: User.t()
874 def update_following_count(%User{local: false} = user) do
875 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
876 maybe_fetch_follow_information(user)
882 def update_following_count(%User{local: true} = user) do
883 following_count = FollowingRelationship.following_count(user)
886 |> follow_information_changeset(%{following_count: following_count})
890 def set_unread_conversation_count(%User{local: true} = user) do
891 unread_query = Participation.unread_conversation_count_for_user(user)
894 |> join(:inner, [u], p in subquery(unread_query))
896 set: [unread_conversation_count: p.count]
898 |> where([u], u.id == ^user.id)
900 |> Repo.update_all([])
902 {1, [user]} -> set_cache(user)
907 def set_unread_conversation_count(user), do: {:ok, user}
909 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
911 Participation.unread_conversation_count_for_user(user)
912 |> where([p], p.conversation_id == ^conversation.id)
915 |> join(:inner, [u], p in subquery(unread_query))
917 inc: [unread_conversation_count: 1]
919 |> where([u], u.id == ^user.id)
920 |> where([u, p], p.count == 0)
922 |> Repo.update_all([])
924 {1, [user]} -> set_cache(user)
929 def increment_unread_conversation_count(_, user), do: {:ok, user}
931 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
932 def get_users_from_set(ap_ids, local_only \\ true) do
933 criteria = %{ap_id: ap_ids, deactivated: false}
934 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
936 User.Query.build(criteria)
940 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
941 def get_recipients_from_activity(%Activity{recipients: to}) do
942 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
946 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
947 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
948 add_to_mutes(muter, ap_id, notifications?)
951 def unmute(muter, %{ap_id: ap_id}) do
952 remove_from_mutes(muter, ap_id)
955 def subscribe(subscriber, %{ap_id: ap_id}) do
956 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
957 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
959 if blocks?(subscribed, subscriber) and deny_follow_blocked do
960 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
962 User.add_to_subscribers(subscribed, subscriber.ap_id)
967 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
968 with %User{} = user <- get_cached_by_ap_id(ap_id) do
969 User.remove_from_subscribers(user, unsubscriber.ap_id)
973 def block(blocker, %User{ap_id: ap_id} = blocked) do
974 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
976 if following?(blocker, blocked) do
977 {:ok, blocker, _} = unfollow(blocker, blocked)
983 # clear any requested follows as well
985 case CommonAPI.reject_follow_request(blocked, blocker) do
986 {:ok, %User{} = updated_blocked} -> updated_blocked
991 if subscribed_to?(blocked, blocker) do
992 {:ok, blocker} = unsubscribe(blocked, blocker)
998 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1000 {:ok, blocker} = update_follower_count(blocker)
1001 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1002 add_to_block(blocker, ap_id)
1005 # helper to handle the block given only an actor's AP id
1006 def block(blocker, %{ap_id: ap_id}) do
1007 block(blocker, get_cached_by_ap_id(ap_id))
1010 def unblock(blocker, %{ap_id: ap_id}) do
1011 remove_from_block(blocker, ap_id)
1014 def mutes?(nil, _), do: false
1015 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1017 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1018 def muted_notifications?(nil, _), do: false
1020 def muted_notifications?(user, %{ap_id: ap_id}),
1021 do: Enum.member?(user.muted_notifications, ap_id)
1023 def blocks?(%User{} = user, %User{} = target) do
1024 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1027 def blocks?(nil, _), do: false
1029 def blocks_ap_id?(%User{} = user, %User{} = target) do
1030 Enum.member?(user.blocks, target.ap_id)
1033 def blocks_ap_id?(_, _), do: false
1035 def blocks_domain?(%User{} = user, %User{} = target) do
1036 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1037 %{host: host} = URI.parse(target.ap_id)
1038 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1041 def blocks_domain?(_, _), do: false
1043 def subscribed_to?(user, %{ap_id: ap_id}) do
1044 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1045 Enum.member?(target.subscribers, user.ap_id)
1049 @spec muted_users(User.t()) :: [User.t()]
1050 def muted_users(user) do
1051 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1055 @spec blocked_users(User.t()) :: [User.t()]
1056 def blocked_users(user) do
1057 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1061 @spec subscribers(User.t()) :: [User.t()]
1062 def subscribers(user) do
1063 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1067 def deactivate_async(user, status \\ true) do
1068 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1071 def deactivate(user, status \\ true)
1073 def deactivate(users, status) when is_list(users) do
1074 Repo.transaction(fn ->
1075 for user <- users, do: deactivate(user, status)
1079 def deactivate(%User{} = user, status) do
1080 with {:ok, user} <- set_activation_status(user, status) do
1083 |> Enum.filter(& &1.local)
1084 |> Enum.each(fn follower ->
1085 follower |> update_following_count() |> set_cache()
1088 # Only update local user counts, remote will be update during the next pull.
1091 |> Enum.filter(& &1.local)
1092 |> Enum.each(&update_follower_count/1)
1098 def update_notification_settings(%User{} = user, settings) do
1100 |> cast(%{notification_settings: settings}, [])
1101 |> cast_embed(:notification_settings)
1102 |> validate_required([:notification_settings])
1103 |> update_and_set_cache()
1106 def delete(users) when is_list(users) do
1107 for user <- users, do: delete(user)
1110 def delete(%User{} = user) do
1111 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1114 def perform(:force_password_reset, user), do: force_password_reset(user)
1116 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1117 def perform(:delete, %User{} = user) do
1118 {:ok, _user} = ActivityPub.delete(user)
1120 # Remove all relationships
1123 |> Enum.each(fn follower ->
1124 ActivityPub.unfollow(follower, user)
1125 unfollow(follower, user)
1130 |> Enum.each(fn followed ->
1131 ActivityPub.unfollow(user, followed)
1132 unfollow(user, followed)
1135 delete_user_activities(user)
1136 invalidate_cache(user)
1140 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1141 def perform(:fetch_initial_posts, %User{} = user) do
1142 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1144 # Insert all the posts in reverse order, so they're in the right order on the timeline
1145 user.source_data["outbox"]
1146 |> Utils.fetch_ordered_collection(pages)
1148 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1151 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1153 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1154 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1155 when is_list(blocked_identifiers) do
1157 blocked_identifiers,
1158 fn blocked_identifier ->
1159 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1160 {:ok, blocker} <- block(blocker, blocked),
1161 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1165 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1172 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1173 def perform(:follow_import, %User{} = follower, followed_identifiers)
1174 when is_list(followed_identifiers) do
1176 followed_identifiers,
1177 fn followed_identifier ->
1178 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1179 {:ok, follower} <- maybe_direct_follow(follower, followed),
1180 {:ok, _} <- ActivityPub.follow(follower, followed) do
1184 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1191 @spec external_users_query() :: Ecto.Query.t()
1192 def external_users_query do
1200 @spec external_users(keyword()) :: [User.t()]
1201 def external_users(opts \\ []) do
1203 external_users_query()
1204 |> select([u], struct(u, [:id, :ap_id]))
1208 do: where(query, [u], u.id > ^opts[:max_id]),
1213 do: limit(query, ^opts[:limit]),
1219 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1220 BackgroundWorker.enqueue("blocks_import", %{
1221 "blocker_id" => blocker.id,
1222 "blocked_identifiers" => blocked_identifiers
1226 def follow_import(%User{} = follower, followed_identifiers)
1227 when is_list(followed_identifiers) do
1228 BackgroundWorker.enqueue("follow_import", %{
1229 "follower_id" => follower.id,
1230 "followed_identifiers" => followed_identifiers
1234 def delete_user_activities(%User{ap_id: ap_id}) do
1236 |> Activity.Queries.by_actor()
1237 |> RepoStreamer.chunk_stream(50)
1238 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1242 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1244 |> Object.normalize()
1245 |> ActivityPub.delete()
1248 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1249 object = Object.normalize(activity)
1252 |> get_cached_by_ap_id()
1253 |> ActivityPub.unlike(object)
1256 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1257 object = Object.normalize(activity)
1260 |> get_cached_by_ap_id()
1261 |> ActivityPub.unannounce(object)
1264 defp delete_activity(_activity), do: "Doing nothing"
1266 def html_filter_policy(%User{no_rich_text: true}) do
1267 Pleroma.HTML.Scrubber.TwitterText
1270 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1272 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1274 def get_or_fetch_by_ap_id(ap_id) do
1275 user = get_cached_by_ap_id(ap_id)
1277 if !is_nil(user) and !needs_update?(user) do
1280 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1281 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1283 resp = fetch_by_ap_id(ap_id)
1285 if should_fetch_initial do
1286 with {:ok, %User{} = user} <- resp do
1287 fetch_initial_posts(user)
1296 Creates an internal service actor by URI if missing.
1297 Optionally takes nickname for addressing.
1299 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1300 with user when is_nil(user) <- get_cached_by_ap_id(uri) do
1307 follower_address: uri <> "/followers"
1316 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1319 |> :public_key.pem_decode()
1321 |> :public_key.pem_entry_decode()
1326 def public_key(_), do: {:error, "not found key"}
1328 def get_public_key_for_ap_id(ap_id) do
1329 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1330 {:ok, public_key} <- public_key(user) do
1337 defp blank?(""), do: nil
1338 defp blank?(n), do: n
1340 def insert_or_update_user(data) do
1342 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1343 |> remote_user_creation()
1344 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1348 def ap_enabled?(%User{local: true}), do: true
1349 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1350 def ap_enabled?(_), do: false
1352 @doc "Gets or fetch a user by uri or nickname."
1353 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1354 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1355 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1357 # wait a period of time and return newest version of the User structs
1358 # this is because we have synchronous follow APIs and need to simulate them
1359 # with an async handshake
1360 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1361 with %User{} = a <- get_cached_by_id(a.id),
1362 %User{} = b <- get_cached_by_id(b.id) do
1369 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1370 with :ok <- :timer.sleep(timeout),
1371 %User{} = a <- get_cached_by_id(a.id),
1372 %User{} = b <- get_cached_by_id(b.id) do
1379 def parse_bio(bio) when is_binary(bio) and bio != "" do
1381 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1385 def parse_bio(_), do: ""
1387 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1388 # TODO: get profile URLs other than user.ap_id
1389 profile_urls = [user.ap_id]
1392 |> CommonUtils.format_input("text/plain",
1393 mentions_format: :full,
1394 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1399 def parse_bio(_, _), do: ""
1401 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1402 Repo.transaction(fn ->
1403 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1407 def tag(nickname, tags) when is_binary(nickname),
1408 do: tag(get_by_nickname(nickname), tags)
1410 def tag(%User{} = user, tags),
1411 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1413 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1414 Repo.transaction(fn ->
1415 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1419 def untag(nickname, tags) when is_binary(nickname),
1420 do: untag(get_by_nickname(nickname), tags)
1422 def untag(%User{} = user, tags),
1423 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1425 defp update_tags(%User{} = user, new_tags) do
1426 {:ok, updated_user} =
1428 |> change(%{tags: new_tags})
1429 |> update_and_set_cache()
1434 defp normalize_tags(tags) do
1437 |> Enum.map(&String.downcase/1)
1440 defp local_nickname_regex do
1441 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1442 @extended_local_nickname_regex
1444 @strict_local_nickname_regex
1448 def local_nickname(nickname_or_mention) do
1451 |> String.split("@")
1455 def full_nickname(nickname_or_mention),
1456 do: String.trim_leading(nickname_or_mention, "@")
1458 def error_user(ap_id) do
1462 nickname: "erroruser@example.com",
1463 inserted_at: NaiveDateTime.utc_now()
1467 @spec all_superusers() :: [User.t()]
1468 def all_superusers do
1469 User.Query.build(%{super_users: true, local: true, deactivated: false})
1473 def showing_reblogs?(%User{} = user, %User{} = target) do
1474 target.ap_id not in user.muted_reblogs
1478 The function returns a query to get users with no activity for given interval of days.
1479 Inactive users are those who didn't read any notification, or had any activity where
1480 the user is the activity's actor, during `inactivity_threshold` days.
1481 Deactivated users will not appear in this list.
1485 iex> Pleroma.User.list_inactive_users()
1488 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1489 def list_inactive_users_query(inactivity_threshold \\ 7) do
1490 negative_inactivity_threshold = -inactivity_threshold
1491 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1492 # Subqueries are not supported in `where` clauses, join gets too complicated.
1493 has_read_notifications =
1494 from(n in Pleroma.Notification,
1495 where: n.seen == true,
1497 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1500 |> Pleroma.Repo.all()
1502 from(u in Pleroma.User,
1503 left_join: a in Pleroma.Activity,
1504 on: u.ap_id == a.actor,
1505 where: not is_nil(u.nickname),
1506 where: u.deactivated != ^true,
1507 where: u.id not in ^has_read_notifications,
1510 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1511 is_nil(max(a.inserted_at))
1516 Enable or disable email notifications for user
1520 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1521 Pleroma.User{email_notifications: %{"digest" => true}}
1523 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1524 Pleroma.User{email_notifications: %{"digest" => false}}
1526 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1527 {:ok, t()} | {:error, Ecto.Changeset.t()}
1528 def switch_email_notifications(user, type, status) do
1529 User.update_email_notifications(user, %{type => status})
1533 Set `last_digest_emailed_at` value for the user to current time
1535 @spec touch_last_digest_emailed_at(t()) :: t()
1536 def touch_last_digest_emailed_at(user) do
1537 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1539 {:ok, updated_user} =
1541 |> change(%{last_digest_emailed_at: now})
1542 |> update_and_set_cache()
1547 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1548 def toggle_confirmation(%User{} = user) do
1550 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1551 |> update_and_set_cache()
1554 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1555 def toggle_confirmation(users) do
1556 Enum.map(users, &toggle_confirmation/1)
1559 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1563 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1564 # use instance-default
1565 config = Pleroma.Config.get([:assets, :mascots])
1566 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1567 mascot = Keyword.get(config, default_mascot)
1570 "id" => "default-mascot",
1571 "url" => mascot[:url],
1572 "preview_url" => mascot[:url],
1574 "mime_type" => mascot[:mime_type]
1579 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1581 def ensure_keys_present(%User{} = user) do
1582 with {:ok, pem} <- Keys.generate_rsa_pem() do
1584 |> cast(%{keys: pem}, [:keys])
1585 |> validate_required([:keys])
1586 |> update_and_set_cache()
1590 def get_ap_ids_by_nicknames(nicknames) do
1592 where: u.nickname in ^nicknames,
1598 defdelegate search(query, opts \\ []), to: User.Search
1600 defp put_password_hash(
1601 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1603 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1606 defp put_password_hash(changeset), do: changeset
1608 def is_internal_user?(%User{nickname: nil}), do: true
1609 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1610 def is_internal_user?(_), do: false
1612 # A hack because user delete activities have a fake id for whatever reason
1613 # TODO: Get rid of this
1614 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1616 def get_delivered_users_by_object_id(object_id) do
1618 inner_join: delivery in assoc(u, :deliveries),
1619 where: delivery.object_id == ^object_id
1624 def change_email(user, email) do
1626 |> cast(%{email: email}, [:email])
1627 |> validate_required([:email])
1628 |> unique_constraint(:email)
1629 |> validate_format(:email, @email_regex)
1630 |> update_and_set_cache()
1633 # Internal function; public one is `deactivate/2`
1634 defp set_activation_status(user, deactivated) do
1636 |> cast(%{deactivated: deactivated}, [:deactivated])
1637 |> update_and_set_cache()
1640 def update_banner(user, banner) do
1642 |> cast(%{banner: banner}, [:banner])
1643 |> update_and_set_cache()
1646 def update_background(user, background) do
1648 |> cast(%{background: background}, [:background])
1649 |> update_and_set_cache()
1652 def update_source_data(user, source_data) do
1654 |> cast(%{source_data: source_data}, [:source_data])
1655 |> update_and_set_cache()
1658 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1661 moderator: is_moderator
1665 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1666 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1667 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1668 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1671 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1672 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1676 def fields(%{fields: nil}), do: []
1678 def fields(%{fields: fields}), do: fields
1680 def validate_fields(changeset, remote? \\ false) do
1681 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1682 limit = Pleroma.Config.get([:instance, limit_name], 0)
1685 |> validate_length(:fields, max: limit)
1686 |> validate_change(:fields, fn :fields, fields ->
1687 if Enum.all?(fields, &valid_field?/1) do
1695 defp valid_field?(%{"name" => name, "value" => value}) do
1696 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1697 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1699 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1700 String.length(value) <= value_limit
1703 defp valid_field?(_), do: false
1705 defp truncate_field(%{"name" => name, "value" => value}) do
1707 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1710 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1712 %{"name" => name, "value" => value}
1715 def admin_api_update(user, params) do
1722 |> update_and_set_cache()
1725 def mascot_update(user, url) do
1727 |> cast(%{mascot: url}, [:mascot])
1728 |> validate_required([:mascot])
1729 |> update_and_set_cache()
1732 def mastodon_settings_update(user, settings) do
1734 |> cast(%{settings: settings}, [:settings])
1735 |> validate_required([:settings])
1736 |> update_and_set_cache()
1739 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1740 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1742 if need_confirmation? do
1744 confirmation_pending: true,
1745 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1749 confirmation_pending: false,
1750 confirmation_token: nil
1754 cast(user, params, [:confirmation_pending, :confirmation_token])
1757 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1758 if id not in user.pinned_activities do
1759 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1760 params = %{pinned_activities: user.pinned_activities ++ [id]}
1763 |> cast(params, [:pinned_activities])
1764 |> validate_length(:pinned_activities,
1765 max: max_pinned_statuses,
1766 message: "You have already pinned the maximum number of statuses"
1771 |> update_and_set_cache()
1774 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1775 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1778 |> cast(params, [:pinned_activities])
1779 |> update_and_set_cache()
1782 def update_email_notifications(user, settings) do
1783 email_notifications =
1784 user.email_notifications
1785 |> Map.merge(settings)
1786 |> Map.take(["digest"])
1788 params = %{email_notifications: email_notifications}
1789 fields = [:email_notifications]
1792 |> cast(params, fields)
1793 |> validate_required(fields)
1794 |> update_and_set_cache()
1797 defp set_subscribers(user, subscribers) do
1798 params = %{subscribers: subscribers}
1801 |> cast(params, [:subscribers])
1802 |> validate_required([:subscribers])
1803 |> update_and_set_cache()
1806 def add_to_subscribers(user, subscribed) do
1807 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1810 def remove_from_subscribers(user, subscribed) do
1811 set_subscribers(user, List.delete(user.subscribers, subscribed))
1814 defp set_domain_blocks(user, domain_blocks) do
1815 params = %{domain_blocks: domain_blocks}
1818 |> cast(params, [:domain_blocks])
1819 |> validate_required([:domain_blocks])
1820 |> update_and_set_cache()
1823 def block_domain(user, domain_blocked) do
1824 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1827 def unblock_domain(user, domain_blocked) do
1828 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1831 defp set_blocks(user, blocks) do
1832 params = %{blocks: blocks}
1835 |> cast(params, [:blocks])
1836 |> validate_required([:blocks])
1837 |> update_and_set_cache()
1840 def add_to_block(user, blocked) do
1841 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1844 def remove_from_block(user, blocked) do
1845 set_blocks(user, List.delete(user.blocks, blocked))
1848 defp set_mutes(user, mutes) do
1849 params = %{mutes: mutes}
1852 |> cast(params, [:mutes])
1853 |> validate_required([:mutes])
1854 |> update_and_set_cache()
1857 def add_to_mutes(user, muted, notifications?) do
1858 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1859 set_notification_mutes(
1861 Enum.uniq([muted | user.muted_notifications]),
1867 def remove_from_mutes(user, muted) do
1868 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1869 set_notification_mutes(
1871 List.delete(user.muted_notifications, muted),
1877 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1881 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1882 params = %{muted_notifications: muted_notifications}
1885 |> cast(params, [:muted_notifications])
1886 |> validate_required([:muted_notifications])
1887 |> update_and_set_cache()
1890 def add_reblog_mute(user, ap_id) do
1891 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1894 |> cast(params, [:muted_reblogs])
1895 |> update_and_set_cache()
1898 def remove_reblog_mute(user, ap_id) do
1899 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1902 |> cast(params, [:muted_reblogs])
1903 |> update_and_set_cache()
1906 def set_invisible(user, invisible) do
1907 params = %{invisible: invisible}
1910 |> cast(params, [:invisible])
1911 |> validate_required([:invisible])
1912 |> update_and_set_cache()