1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.User do
13 alias Pleroma.Activity
14 alias Pleroma.Conversation.Participation
15 alias Pleroma.Delivery
16 alias Pleroma.FollowingRelationship
18 alias Pleroma.Notification
20 alias Pleroma.Registration
22 alias Pleroma.RepoStreamer
25 alias Pleroma.Web.ActivityPub.ActivityPub
26 alias Pleroma.Web.ActivityPub.Utils
27 alias Pleroma.Web.CommonAPI
28 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
29 alias Pleroma.Web.OAuth
30 alias Pleroma.Web.RelMe
31 alias Pleroma.Workers.BackgroundWorker
35 @type t :: %__MODULE__{}
37 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
39 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
40 @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
42 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
43 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
47 field(:email, :string)
49 field(:nickname, :string)
50 field(:password_hash, :string)
51 field(:password, :string, virtual: true)
52 field(:password_confirmation, :string, virtual: true)
54 field(:ap_id, :string)
56 field(:local, :boolean, default: true)
57 field(:follower_address, :string)
58 field(:following_address, :string)
59 field(:search_rank, :float, virtual: true)
60 field(:search_type, :integer, virtual: true)
61 field(:tags, {:array, :string}, default: [])
62 field(:last_refreshed_at, :naive_datetime_usec)
63 field(:last_digest_emailed_at, :naive_datetime)
65 field(:banner, :map, default: %{})
66 field(:background, :map, default: %{})
67 field(:source_data, :map, default: %{})
68 field(:note_count, :integer, default: 0)
69 field(:follower_count, :integer, default: 0)
70 # Should be filled in only for remote users
71 field(:following_count, :integer, default: nil)
72 field(:locked, :boolean, default: false)
73 field(:confirmation_pending, :boolean, default: false)
74 field(:password_reset_pending, :boolean, default: false)
75 field(:confirmation_token, :string, default: nil)
76 field(:default_scope, :string, default: "public")
77 field(:blocks, {:array, :string}, default: [])
78 field(:domain_blocks, {:array, :string}, default: [])
79 field(:mutes, {:array, :string}, default: [])
80 field(:muted_reblogs, {:array, :string}, default: [])
81 field(:muted_notifications, {:array, :string}, default: [])
82 field(:subscribers, {:array, :string}, default: [])
83 field(:deactivated, :boolean, default: false)
84 field(:no_rich_text, :boolean, default: false)
85 field(:ap_enabled, :boolean, default: false)
86 field(:is_moderator, :boolean, default: false)
87 field(:is_admin, :boolean, default: false)
88 field(:show_role, :boolean, default: true)
89 field(:settings, :map, default: nil)
90 field(:magic_key, :string, default: nil)
91 field(:uri, :string, default: nil)
92 field(:hide_followers_count, :boolean, default: false)
93 field(:hide_follows_count, :boolean, default: false)
94 field(:hide_followers, :boolean, default: false)
95 field(:hide_follows, :boolean, default: false)
96 field(:hide_favorites, :boolean, default: true)
97 field(:unread_conversation_count, :integer, default: 0)
98 field(:pinned_activities, {:array, :string}, default: [])
99 field(:email_notifications, :map, default: %{"digest" => false})
100 field(:mascot, :map, default: nil)
101 field(:emoji, {:array, :map}, default: [])
102 field(:pleroma_settings_store, :map, default: %{})
103 field(:fields, {:array, :map}, default: [])
104 field(:raw_fields, {:array, :map}, default: [])
105 field(:discoverable, :boolean, default: false)
106 field(:invisible, :boolean, default: false)
107 field(:skip_thread_containment, :boolean, default: false)
109 field(:notification_settings, :map,
113 "non_follows" => true,
114 "non_followers" => true
118 has_many(:notifications, Notification)
119 has_many(:registrations, Registration)
120 has_many(:deliveries, Delivery)
122 field(:info, :map, default: %{})
127 @doc "Returns if the user should be allowed to authenticate"
128 def auth_active?(%User{deactivated: true}), do: false
130 def auth_active?(%User{confirmation_pending: true}),
131 do: !Pleroma.Config.get([:instance, :account_activation_required])
133 def auth_active?(%User{}), do: true
135 def visible_for?(user, for_user \\ nil)
137 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
139 def visible_for?(%User{} = user, for_user) do
140 auth_active?(user) || superuser?(for_user)
143 def visible_for?(_, _), do: false
145 def superuser?(%User{local: true, is_admin: true}), do: true
146 def superuser?(%User{local: true, is_moderator: true}), do: true
147 def superuser?(_), do: false
149 def invisible?(%User{invisible: true}), do: true
150 def invisible?(_), do: false
152 def avatar_url(user, options \\ []) do
154 %{"url" => [%{"href" => href} | _]} -> href
155 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
159 def banner_url(user, options \\ []) do
161 %{"url" => [%{"href" => href} | _]} -> href
162 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
166 def profile_url(%User{source_data: %{"url" => url}}), do: url
167 def profile_url(%User{ap_id: ap_id}), do: ap_id
168 def profile_url(_), do: nil
170 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
172 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
173 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
175 @spec ap_following(User.t()) :: Sring.t()
176 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
177 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
179 def user_info(%User{} = user, args \\ %{}) do
181 Map.get(args, :following_count, user.following_count || following_count(user))
183 follower_count = Map.get(args, :follower_count, user.follower_count)
186 note_count: user.note_count,
188 confirmation_pending: user.confirmation_pending,
189 default_scope: user.default_scope
191 |> Map.put(:following_count, following_count)
192 |> Map.put(:follower_count, follower_count)
195 def follow_state(%User{} = user, %User{} = target) do
196 case Utils.fetch_latest_follow(user, target) do
197 %{data: %{"state" => state}} -> state
198 # Ideally this would be nil, but then Cachex does not commit the value
203 def get_cached_follow_state(user, target) do
204 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
205 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
208 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
209 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
210 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
213 def set_info_cache(user, args) do
214 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
217 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
218 def restrict_deactivated(query) do
219 from(u in query, where: u.deactivated != ^true)
222 defdelegate following_count(user), to: FollowingRelationship
224 defp truncate_fields_param(params) do
225 if Map.has_key?(params, :fields) do
226 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
232 defp truncate_if_exists(params, key, max_length) do
233 if Map.has_key?(params, key) and is_binary(params[key]) do
234 {value, _chopped} = String.split_at(params[key], max_length)
235 Map.put(params, key, value)
241 def remote_user_creation(params) do
242 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
243 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
247 |> Map.put(:info, params[:info] || %{})
248 |> truncate_if_exists(:name, name_limit)
249 |> truncate_if_exists(:bio, bio_limit)
250 |> truncate_fields_param()
270 :hide_followers_count,
279 |> validate_required([:name, :ap_id])
280 |> unique_constraint(:nickname)
281 |> validate_format(:nickname, @email_regex)
282 |> validate_length(:bio, max: bio_limit)
283 |> validate_length(:name, max: name_limit)
284 |> validate_fields(true)
286 case params[:source_data] do
287 %{"followers" => followers, "following" => following} ->
289 |> put_change(:follower_address, followers)
290 |> put_change(:following_address, following)
293 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
294 put_change(changeset, :follower_address, followers)
298 def update_changeset(struct, params \\ %{}) do
299 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
300 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
315 :hide_followers_count,
320 :skip_thread_containment,
323 :pleroma_settings_store,
327 |> unique_constraint(:nickname)
328 |> validate_format(:nickname, local_nickname_regex())
329 |> validate_length(:bio, max: bio_limit)
330 |> validate_length(:name, min: 1, max: name_limit)
331 |> validate_fields(false)
334 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
335 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
336 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
338 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
340 params = if remote?, do: truncate_fields_param(params), else: params
363 :hide_followers_count,
367 |> unique_constraint(:nickname)
368 |> validate_format(:nickname, local_nickname_regex())
369 |> validate_length(:bio, max: bio_limit)
370 |> validate_length(:name, max: name_limit)
371 |> validate_fields(remote?)
374 def password_update_changeset(struct, params) do
376 |> cast(params, [:password, :password_confirmation])
377 |> validate_required([:password, :password_confirmation])
378 |> validate_confirmation(:password)
379 |> put_password_hash()
380 |> put_change(:password_reset_pending, false)
383 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
384 def reset_password(%User{id: user_id} = user, data) do
387 |> Multi.update(:user, password_update_changeset(user, data))
388 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
389 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
391 case Repo.transaction(multi) do
392 {:ok, %{user: user} = _} -> set_cache(user)
393 {:error, _, changeset, _} -> {:error, changeset}
397 def update_password_reset_pending(user, value) do
400 |> put_change(:password_reset_pending, value)
401 |> update_and_set_cache()
404 def force_password_reset_async(user) do
405 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
408 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
409 def force_password_reset(user), do: update_password_reset_pending(user, true)
411 def register_changeset(struct, params \\ %{}, opts \\ []) do
412 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
413 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
416 if is_nil(opts[:need_confirmation]) do
417 Pleroma.Config.get([:instance, :account_activation_required])
419 opts[:need_confirmation]
423 |> confirmation_changeset(need_confirmation: need_confirmation?)
424 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
425 |> validate_required([:name, :nickname, :password, :password_confirmation])
426 |> validate_confirmation(:password)
427 |> unique_constraint(:email)
428 |> unique_constraint(:nickname)
429 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
430 |> validate_format(:nickname, local_nickname_regex())
431 |> validate_format(:email, @email_regex)
432 |> validate_length(:bio, max: bio_limit)
433 |> validate_length(:name, min: 1, max: name_limit)
434 |> maybe_validate_required_email(opts[:external])
437 |> unique_constraint(:ap_id)
438 |> put_following_and_follower_address()
441 def maybe_validate_required_email(changeset, true), do: changeset
442 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
444 defp put_ap_id(changeset) do
445 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
446 put_change(changeset, :ap_id, ap_id)
449 defp put_following_and_follower_address(changeset) do
450 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
453 |> put_change(:follower_address, followers)
456 defp autofollow_users(user) do
457 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
460 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
463 follow_all(user, autofollowed_users)
466 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
467 def register(%Ecto.Changeset{} = changeset) do
468 with {:ok, user} <- Repo.insert(changeset) do
469 post_register_action(user)
473 def post_register_action(%User{} = user) do
474 with {:ok, user} <- autofollow_users(user),
475 {:ok, user} <- set_cache(user),
476 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
477 {:ok, _} <- try_send_confirmation_email(user) do
482 def try_send_confirmation_email(%User{} = user) do
483 if user.confirmation_pending &&
484 Pleroma.Config.get([:instance, :account_activation_required]) do
486 |> Pleroma.Emails.UserEmail.account_confirmation_email()
487 |> Pleroma.Emails.Mailer.deliver_async()
495 def needs_update?(%User{local: true}), do: false
497 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
499 def needs_update?(%User{local: false} = user) do
500 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
503 def needs_update?(_), do: true
505 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
506 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
507 follow(follower, followed, "pending")
510 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
511 follow(follower, followed)
514 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
515 if not ap_enabled?(followed) do
516 follow(follower, followed)
522 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
523 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
524 def follow_all(follower, followeds) do
526 Enum.reject(followeds, fn followed ->
527 blocks?(follower, followed) || blocks?(followed, follower)
530 Enum.each(followeds, &follow(follower, &1, "accept"))
532 Enum.each(followeds, &update_follower_count/1)
537 defdelegate following(user), to: FollowingRelationship
539 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
540 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
543 followed.deactivated ->
544 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
546 deny_follow_blocked and blocks?(followed, follower) ->
547 {:error, "Could not follow user: #{followed.nickname} blocked you."}
550 FollowingRelationship.follow(follower, followed, state)
552 follower = maybe_update_following_count(follower)
554 {:ok, _} = update_follower_count(followed)
560 def unfollow(%User{} = follower, %User{} = followed) do
561 if following?(follower, followed) and follower.ap_id != followed.ap_id do
562 FollowingRelationship.unfollow(follower, followed)
564 follower = maybe_update_following_count(follower)
566 {:ok, followed} = update_follower_count(followed)
570 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
572 {:error, "Not subscribed!"}
576 defdelegate following?(follower, followed), to: FollowingRelationship
578 def locked?(%User{} = user) do
583 Repo.get_by(User, id: id)
586 def get_by_ap_id(ap_id) do
587 Repo.get_by(User, ap_id: ap_id)
590 def get_all_by_ap_id(ap_ids) do
591 from(u in __MODULE__,
592 where: u.ap_id in ^ap_ids
597 def get_all_by_ids(ids) do
598 from(u in __MODULE__, where: u.id in ^ids)
602 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
603 # of the ap_id and the domain and tries to get that user
604 def get_by_guessed_nickname(ap_id) do
605 domain = URI.parse(ap_id).host
606 name = List.last(String.split(ap_id, "/"))
607 nickname = "#{name}@#{domain}"
609 get_cached_by_nickname(nickname)
612 def set_cache({:ok, user}), do: set_cache(user)
613 def set_cache({:error, err}), do: {:error, err}
615 def set_cache(%User{} = user) do
616 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
617 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
618 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
622 def update_and_set_cache(struct, params) do
624 |> update_changeset(params)
625 |> update_and_set_cache()
628 def update_and_set_cache(changeset) do
629 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
634 def invalidate_cache(user) do
635 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
636 Cachex.del(:user_cache, "nickname:#{user.nickname}")
637 Cachex.del(:user_cache, "user_info:#{user.id}")
640 def get_cached_by_ap_id(ap_id) do
641 key = "ap_id:#{ap_id}"
642 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
645 def get_cached_by_id(id) do
649 Cachex.fetch!(:user_cache, key, fn _ ->
653 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
654 {:commit, user.ap_id}
660 get_cached_by_ap_id(ap_id)
663 def get_cached_by_nickname(nickname) do
664 key = "nickname:#{nickname}"
666 Cachex.fetch!(:user_cache, key, fn ->
667 case get_or_fetch_by_nickname(nickname) do
668 {:ok, user} -> {:commit, user}
669 {:error, _error} -> {:ignore, nil}
674 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
675 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
678 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
679 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
681 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
682 get_cached_by_nickname(nickname_or_id)
684 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
685 get_cached_by_nickname(nickname_or_id)
692 def get_by_nickname(nickname) do
693 Repo.get_by(User, nickname: nickname) ||
694 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
695 Repo.get_by(User, nickname: local_nickname(nickname))
699 def get_by_email(email), do: Repo.get_by(User, email: email)
701 def get_by_nickname_or_email(nickname_or_email) do
702 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
705 def get_cached_user_info(user) do
706 key = "user_info:#{user.id}"
707 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
710 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
712 def get_or_fetch_by_nickname(nickname) do
713 with %User{} = user <- get_by_nickname(nickname) do
717 with [_nick, _domain] <- String.split(nickname, "@"),
718 {:ok, user} <- fetch_by_nickname(nickname) do
719 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
720 fetch_initial_posts(user)
725 _e -> {:error, "not found " <> nickname}
730 @doc "Fetch some posts when the user has just been federated with"
731 def fetch_initial_posts(user) do
732 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
735 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
736 def get_followers_query(%User{} = user, nil) do
737 User.Query.build(%{followers: user, deactivated: false})
740 def get_followers_query(user, page) do
742 |> get_followers_query(nil)
743 |> User.Query.paginate(page, 20)
746 @spec get_followers_query(User.t()) :: Ecto.Query.t()
747 def get_followers_query(user), do: get_followers_query(user, nil)
749 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
750 def get_followers(user, page \\ nil) do
752 |> get_followers_query(page)
756 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
757 def get_external_followers(user, page \\ nil) do
759 |> get_followers_query(page)
760 |> User.Query.build(%{external: true})
764 def get_followers_ids(user, page \\ nil) do
766 |> get_followers_query(page)
771 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
772 def get_friends_query(%User{} = user, nil) do
773 User.Query.build(%{friends: user, deactivated: false})
776 def get_friends_query(user, page) do
778 |> get_friends_query(nil)
779 |> User.Query.paginate(page, 20)
782 @spec get_friends_query(User.t()) :: Ecto.Query.t()
783 def get_friends_query(user), do: get_friends_query(user, nil)
785 def get_friends(user, page \\ nil) do
787 |> get_friends_query(page)
791 def get_friends_ids(user, page \\ nil) do
793 |> get_friends_query(page)
798 defdelegate get_follow_requests(user), to: FollowingRelationship
800 def increase_note_count(%User{} = user) do
802 |> where(id: ^user.id)
803 |> update([u], inc: [note_count: 1])
805 |> Repo.update_all([])
807 {1, [user]} -> set_cache(user)
812 def decrease_note_count(%User{} = user) do
814 |> where(id: ^user.id)
817 note_count: fragment("greatest(0, note_count - 1)")
821 |> Repo.update_all([])
823 {1, [user]} -> set_cache(user)
828 def update_note_count(%User{} = user, note_count \\ nil) do
833 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
839 |> cast(%{note_count: note_count}, [:note_count])
840 |> update_and_set_cache()
843 @spec maybe_fetch_follow_information(User.t()) :: User.t()
844 def maybe_fetch_follow_information(user) do
845 with {:ok, user} <- fetch_follow_information(user) do
849 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
855 def fetch_follow_information(user) do
856 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
858 |> follow_information_changeset(info)
859 |> update_and_set_cache()
863 defp follow_information_changeset(user, params) do
870 :hide_followers_count,
875 def update_follower_count(%User{} = user) do
876 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
877 follower_count_query =
878 User.Query.build(%{followers: user, deactivated: false})
879 |> select([u], %{count: count(u.id)})
882 |> where(id: ^user.id)
883 |> join(:inner, [u], s in subquery(follower_count_query))
885 set: [follower_count: s.count]
888 |> Repo.update_all([])
890 {1, [user]} -> set_cache(user)
894 {:ok, maybe_fetch_follow_information(user)}
898 @spec maybe_update_following_count(User.t()) :: User.t()
899 def maybe_update_following_count(%User{local: false} = user) do
900 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
901 maybe_fetch_follow_information(user)
907 def maybe_update_following_count(user), do: user
909 def set_unread_conversation_count(%User{local: true} = user) do
910 unread_query = Participation.unread_conversation_count_for_user(user)
913 |> join(:inner, [u], p in subquery(unread_query))
915 set: [unread_conversation_count: p.count]
917 |> where([u], u.id == ^user.id)
919 |> Repo.update_all([])
921 {1, [user]} -> set_cache(user)
926 def set_unread_conversation_count(user), do: {:ok, user}
928 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
930 Participation.unread_conversation_count_for_user(user)
931 |> where([p], p.conversation_id == ^conversation.id)
934 |> join(:inner, [u], p in subquery(unread_query))
936 inc: [unread_conversation_count: 1]
938 |> where([u], u.id == ^user.id)
939 |> where([u, p], p.count == 0)
941 |> Repo.update_all([])
943 {1, [user]} -> set_cache(user)
948 def increment_unread_conversation_count(_, user), do: {:ok, user}
950 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
951 def get_users_from_set(ap_ids, local_only \\ true) do
952 criteria = %{ap_id: ap_ids, deactivated: false}
953 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
955 User.Query.build(criteria)
959 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
960 def get_recipients_from_activity(%Activity{recipients: to}) do
961 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
965 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
966 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
967 add_to_mutes(muter, ap_id, notifications?)
970 def unmute(muter, %{ap_id: ap_id}) do
971 remove_from_mutes(muter, ap_id)
974 def subscribe(subscriber, %{ap_id: ap_id}) do
975 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
976 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
978 if blocks?(subscribed, subscriber) and deny_follow_blocked do
979 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
981 User.add_to_subscribers(subscribed, subscriber.ap_id)
986 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
987 with %User{} = user <- get_cached_by_ap_id(ap_id) do
988 User.remove_from_subscribers(user, unsubscriber.ap_id)
992 def block(blocker, %User{ap_id: ap_id} = blocked) do
993 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
995 if following?(blocker, blocked) do
996 {:ok, blocker, _} = unfollow(blocker, blocked)
1002 # clear any requested follows as well
1004 case CommonAPI.reject_follow_request(blocked, blocker) do
1005 {:ok, %User{} = updated_blocked} -> updated_blocked
1010 if subscribed_to?(blocked, blocker) do
1011 {:ok, blocker} = unsubscribe(blocked, blocker)
1017 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1019 {:ok, blocker} = update_follower_count(blocker)
1020 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1021 add_to_block(blocker, ap_id)
1024 # helper to handle the block given only an actor's AP id
1025 def block(blocker, %{ap_id: ap_id}) do
1026 block(blocker, get_cached_by_ap_id(ap_id))
1029 def unblock(blocker, %{ap_id: ap_id}) do
1030 remove_from_block(blocker, ap_id)
1033 def mutes?(nil, _), do: false
1034 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1036 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1037 def muted_notifications?(nil, _), do: false
1039 def muted_notifications?(user, %{ap_id: ap_id}),
1040 do: Enum.member?(user.muted_notifications, ap_id)
1042 def blocks?(%User{} = user, %User{} = target) do
1043 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1046 def blocks?(nil, _), do: false
1048 def blocks_ap_id?(%User{} = user, %User{} = target) do
1049 Enum.member?(user.blocks, target.ap_id)
1052 def blocks_ap_id?(_, _), do: false
1054 def blocks_domain?(%User{} = user, %User{} = target) do
1055 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1056 %{host: host} = URI.parse(target.ap_id)
1057 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1060 def blocks_domain?(_, _), do: false
1062 def subscribed_to?(user, %{ap_id: ap_id}) do
1063 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1064 Enum.member?(target.subscribers, user.ap_id)
1068 @spec muted_users(User.t()) :: [User.t()]
1069 def muted_users(user) do
1070 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1074 @spec blocked_users(User.t()) :: [User.t()]
1075 def blocked_users(user) do
1076 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1080 @spec subscribers(User.t()) :: [User.t()]
1081 def subscribers(user) do
1082 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1086 def deactivate_async(user, status \\ true) do
1087 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1090 def deactivate(user, status \\ true)
1092 def deactivate(users, status) when is_list(users) do
1093 Repo.transaction(fn ->
1094 for user <- users, do: deactivate(user, status)
1098 def deactivate(%User{} = user, status) do
1099 with {:ok, user} <- set_activation_status(user, status) do
1100 Enum.each(get_followers(user), &invalidate_cache/1)
1102 # Only update local user counts, remote will be update during the next pull.
1105 |> Enum.filter(& &1.local)
1106 |> Enum.each(&update_follower_count/1)
1112 def update_notification_settings(%User{} = user, settings) do
1115 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1118 notification_settings =
1119 user.notification_settings
1120 |> Map.merge(settings)
1121 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1123 params = %{notification_settings: notification_settings}
1126 |> cast(params, [:notification_settings])
1127 |> validate_required([:notification_settings])
1128 |> update_and_set_cache()
1131 def delete(users) when is_list(users) do
1132 for user <- users, do: delete(user)
1135 def delete(%User{} = user) do
1136 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1139 def perform(:force_password_reset, user), do: force_password_reset(user)
1141 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1142 def perform(:delete, %User{} = user) do
1143 {:ok, _user} = ActivityPub.delete(user)
1145 # Remove all relationships
1148 |> Enum.each(fn follower ->
1149 ActivityPub.unfollow(follower, user)
1150 unfollow(follower, user)
1155 |> Enum.each(fn followed ->
1156 ActivityPub.unfollow(user, followed)
1157 unfollow(user, followed)
1160 delete_user_activities(user)
1161 invalidate_cache(user)
1165 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1166 def perform(:fetch_initial_posts, %User{} = user) do
1167 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1169 # Insert all the posts in reverse order, so they're in the right order on the timeline
1170 user.source_data["outbox"]
1171 |> Utils.fetch_ordered_collection(pages)
1173 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1176 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1178 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1179 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1180 when is_list(blocked_identifiers) do
1182 blocked_identifiers,
1183 fn blocked_identifier ->
1184 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1185 {:ok, blocker} <- block(blocker, blocked),
1186 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1190 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1197 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1198 def perform(:follow_import, %User{} = follower, followed_identifiers)
1199 when is_list(followed_identifiers) do
1201 followed_identifiers,
1202 fn followed_identifier ->
1203 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1204 {:ok, follower} <- maybe_direct_follow(follower, followed),
1205 {:ok, _} <- ActivityPub.follow(follower, followed) do
1209 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1216 @spec external_users_query() :: Ecto.Query.t()
1217 def external_users_query do
1225 @spec external_users(keyword()) :: [User.t()]
1226 def external_users(opts \\ []) do
1228 external_users_query()
1229 |> select([u], struct(u, [:id, :ap_id, :info]))
1233 do: where(query, [u], u.id > ^opts[:max_id]),
1238 do: limit(query, ^opts[:limit]),
1244 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1245 BackgroundWorker.enqueue("blocks_import", %{
1246 "blocker_id" => blocker.id,
1247 "blocked_identifiers" => blocked_identifiers
1251 def follow_import(%User{} = follower, followed_identifiers)
1252 when is_list(followed_identifiers) do
1253 BackgroundWorker.enqueue("follow_import", %{
1254 "follower_id" => follower.id,
1255 "followed_identifiers" => followed_identifiers
1259 def delete_user_activities(%User{ap_id: ap_id}) do
1261 |> Activity.Queries.by_actor()
1262 |> RepoStreamer.chunk_stream(50)
1263 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1267 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1269 |> Object.normalize()
1270 |> ActivityPub.delete()
1273 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1274 object = Object.normalize(activity)
1277 |> get_cached_by_ap_id()
1278 |> ActivityPub.unlike(object)
1281 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1282 object = Object.normalize(activity)
1285 |> get_cached_by_ap_id()
1286 |> ActivityPub.unannounce(object)
1289 defp delete_activity(_activity), do: "Doing nothing"
1291 def html_filter_policy(%User{no_rich_text: true}) do
1292 Pleroma.HTML.Scrubber.TwitterText
1295 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1297 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1299 def get_or_fetch_by_ap_id(ap_id) do
1300 user = get_cached_by_ap_id(ap_id)
1302 if !is_nil(user) and !needs_update?(user) do
1305 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1306 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1308 resp = fetch_by_ap_id(ap_id)
1310 if should_fetch_initial do
1311 with {:ok, %User{} = user} <- resp do
1312 fetch_initial_posts(user)
1320 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1321 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1322 with %User{} = user <- get_cached_by_ap_id(uri) do
1328 |> cast(%{}, [:ap_id, :nickname, :local])
1329 |> put_change(:ap_id, uri)
1330 |> put_change(:nickname, nickname)
1331 |> put_change(:local, true)
1332 |> put_change(:follower_address, uri <> "/followers")
1340 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1343 |> :public_key.pem_decode()
1345 |> :public_key.pem_entry_decode()
1350 def public_key(_), do: {:error, "not found key"}
1352 def get_public_key_for_ap_id(ap_id) do
1353 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1354 {:ok, public_key} <- public_key(user) do
1361 defp blank?(""), do: nil
1362 defp blank?(n), do: n
1364 def insert_or_update_user(data) do
1366 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1367 |> remote_user_creation()
1368 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1372 def ap_enabled?(%User{local: true}), do: true
1373 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1374 def ap_enabled?(_), do: false
1376 @doc "Gets or fetch a user by uri or nickname."
1377 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1378 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1379 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1381 # wait a period of time and return newest version of the User structs
1382 # this is because we have synchronous follow APIs and need to simulate them
1383 # with an async handshake
1384 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1385 with %User{} = a <- get_cached_by_id(a.id),
1386 %User{} = b <- get_cached_by_id(b.id) do
1393 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1394 with :ok <- :timer.sleep(timeout),
1395 %User{} = a <- get_cached_by_id(a.id),
1396 %User{} = b <- get_cached_by_id(b.id) do
1403 def parse_bio(bio) when is_binary(bio) and bio != "" do
1405 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1409 def parse_bio(_), do: ""
1411 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1412 # TODO: get profile URLs other than user.ap_id
1413 profile_urls = [user.ap_id]
1416 |> CommonUtils.format_input("text/plain",
1417 mentions_format: :full,
1418 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1423 def parse_bio(_, _), do: ""
1425 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1426 Repo.transaction(fn ->
1427 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1431 def tag(nickname, tags) when is_binary(nickname),
1432 do: tag(get_by_nickname(nickname), tags)
1434 def tag(%User{} = user, tags),
1435 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1437 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1438 Repo.transaction(fn ->
1439 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1443 def untag(nickname, tags) when is_binary(nickname),
1444 do: untag(get_by_nickname(nickname), tags)
1446 def untag(%User{} = user, tags),
1447 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1449 defp update_tags(%User{} = user, new_tags) do
1450 {:ok, updated_user} =
1452 |> change(%{tags: new_tags})
1453 |> update_and_set_cache()
1458 defp normalize_tags(tags) do
1461 |> Enum.map(&String.downcase/1)
1464 defp local_nickname_regex do
1465 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1466 @extended_local_nickname_regex
1468 @strict_local_nickname_regex
1472 def local_nickname(nickname_or_mention) do
1475 |> String.split("@")
1479 def full_nickname(nickname_or_mention),
1480 do: String.trim_leading(nickname_or_mention, "@")
1482 def error_user(ap_id) do
1486 nickname: "erroruser@example.com",
1487 inserted_at: NaiveDateTime.utc_now()
1491 @spec all_superusers() :: [User.t()]
1492 def all_superusers do
1493 User.Query.build(%{super_users: true, local: true, deactivated: false})
1497 def showing_reblogs?(%User{} = user, %User{} = target) do
1498 target.ap_id not in user.muted_reblogs
1502 The function returns a query to get users with no activity for given interval of days.
1503 Inactive users are those who didn't read any notification, or had any activity where
1504 the user is the activity's actor, during `inactivity_threshold` days.
1505 Deactivated users will not appear in this list.
1509 iex> Pleroma.User.list_inactive_users()
1512 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1513 def list_inactive_users_query(inactivity_threshold \\ 7) do
1514 negative_inactivity_threshold = -inactivity_threshold
1515 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1516 # Subqueries are not supported in `where` clauses, join gets too complicated.
1517 has_read_notifications =
1518 from(n in Pleroma.Notification,
1519 where: n.seen == true,
1521 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1524 |> Pleroma.Repo.all()
1526 from(u in Pleroma.User,
1527 left_join: a in Pleroma.Activity,
1528 on: u.ap_id == a.actor,
1529 where: not is_nil(u.nickname),
1530 where: u.deactivated != ^true,
1531 where: u.id not in ^has_read_notifications,
1534 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1535 is_nil(max(a.inserted_at))
1540 Enable or disable email notifications for user
1544 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1545 Pleroma.User{email_notifications: %{"digest" => true}}
1547 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1548 Pleroma.User{email_notifications: %{"digest" => false}}
1550 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1551 {:ok, t()} | {:error, Ecto.Changeset.t()}
1552 def switch_email_notifications(user, type, status) do
1553 User.update_email_notifications(user, %{type => status})
1557 Set `last_digest_emailed_at` value for the user to current time
1559 @spec touch_last_digest_emailed_at(t()) :: t()
1560 def touch_last_digest_emailed_at(user) do
1561 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1563 {:ok, updated_user} =
1565 |> change(%{last_digest_emailed_at: now})
1566 |> update_and_set_cache()
1571 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1572 def toggle_confirmation(%User{} = user) do
1574 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1575 |> update_and_set_cache()
1578 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1582 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1583 # use instance-default
1584 config = Pleroma.Config.get([:assets, :mascots])
1585 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1586 mascot = Keyword.get(config, default_mascot)
1589 "id" => "default-mascot",
1590 "url" => mascot[:url],
1591 "preview_url" => mascot[:url],
1593 "mime_type" => mascot[:mime_type]
1598 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1600 def ensure_keys_present(%User{} = user) do
1601 with {:ok, pem} <- Keys.generate_rsa_pem() do
1603 |> cast(%{keys: pem}, [:keys])
1604 |> validate_required([:keys])
1605 |> update_and_set_cache()
1609 def get_ap_ids_by_nicknames(nicknames) do
1611 where: u.nickname in ^nicknames,
1617 defdelegate search(query, opts \\ []), to: User.Search
1619 defp put_password_hash(
1620 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1622 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1625 defp put_password_hash(changeset), do: changeset
1627 def is_internal_user?(%User{nickname: nil}), do: true
1628 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1629 def is_internal_user?(_), do: false
1631 # A hack because user delete activities have a fake id for whatever reason
1632 # TODO: Get rid of this
1633 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1635 def get_delivered_users_by_object_id(object_id) do
1637 inner_join: delivery in assoc(u, :deliveries),
1638 where: delivery.object_id == ^object_id
1643 def change_email(user, email) do
1645 |> cast(%{email: email}, [:email])
1646 |> validate_required([:email])
1647 |> unique_constraint(:email)
1648 |> validate_format(:email, @email_regex)
1649 |> update_and_set_cache()
1652 # Internal function; public one is `deactivate/2`
1653 defp set_activation_status(user, deactivated) do
1655 |> cast(%{deactivated: deactivated}, [:deactivated])
1656 |> update_and_set_cache()
1659 def update_banner(user, banner) do
1661 |> cast(%{banner: banner}, [:banner])
1662 |> update_and_set_cache()
1665 def update_background(user, background) do
1667 |> cast(%{background: background}, [:background])
1668 |> update_and_set_cache()
1671 def update_source_data(user, source_data) do
1673 |> cast(%{source_data: source_data}, [:source_data])
1674 |> update_and_set_cache()
1677 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1680 moderator: is_moderator
1684 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1685 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1686 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1687 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1690 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1691 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1695 def fields(%{fields: nil}), do: []
1697 def fields(%{fields: fields}), do: fields
1699 def validate_fields(changeset, remote? \\ false) do
1700 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1701 limit = Pleroma.Config.get([:instance, limit_name], 0)
1704 |> validate_length(:fields, max: limit)
1705 |> validate_change(:fields, fn :fields, fields ->
1706 if Enum.all?(fields, &valid_field?/1) do
1714 defp valid_field?(%{"name" => name, "value" => value}) do
1715 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1716 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1718 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1719 String.length(value) <= value_limit
1722 defp valid_field?(_), do: false
1724 defp truncate_field(%{"name" => name, "value" => value}) do
1726 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1729 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1731 %{"name" => name, "value" => value}
1734 def admin_api_update(user, params) do
1741 |> update_and_set_cache()
1744 def mascot_update(user, url) do
1746 |> cast(%{mascot: url}, [:mascot])
1747 |> validate_required([:mascot])
1748 |> update_and_set_cache()
1751 def mastodon_settings_update(user, settings) do
1753 |> cast(%{settings: settings}, [:settings])
1754 |> validate_required([:settings])
1755 |> update_and_set_cache()
1758 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1759 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1761 if need_confirmation? do
1763 confirmation_pending: true,
1764 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1768 confirmation_pending: false,
1769 confirmation_token: nil
1773 cast(user, params, [:confirmation_pending, :confirmation_token])
1776 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1777 if id not in user.pinned_activities do
1778 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1779 params = %{pinned_activities: user.pinned_activities ++ [id]}
1782 |> cast(params, [:pinned_activities])
1783 |> validate_length(:pinned_activities,
1784 max: max_pinned_statuses,
1785 message: "You have already pinned the maximum number of statuses"
1790 |> update_and_set_cache()
1793 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1794 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1797 |> cast(params, [:pinned_activities])
1798 |> update_and_set_cache()
1801 def update_email_notifications(user, settings) do
1802 email_notifications =
1803 user.email_notifications
1804 |> Map.merge(settings)
1805 |> Map.take(["digest"])
1807 params = %{email_notifications: email_notifications}
1808 fields = [:email_notifications]
1811 |> cast(params, fields)
1812 |> validate_required(fields)
1813 |> update_and_set_cache()
1816 defp set_subscribers(user, subscribers) do
1817 params = %{subscribers: subscribers}
1820 |> cast(params, [:subscribers])
1821 |> validate_required([:subscribers])
1822 |> update_and_set_cache()
1825 def add_to_subscribers(user, subscribed) do
1826 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1829 def remove_from_subscribers(user, subscribed) do
1830 set_subscribers(user, List.delete(user.subscribers, subscribed))
1833 defp set_domain_blocks(user, domain_blocks) do
1834 params = %{domain_blocks: domain_blocks}
1837 |> cast(params, [:domain_blocks])
1838 |> validate_required([:domain_blocks])
1839 |> update_and_set_cache()
1842 def block_domain(user, domain_blocked) do
1843 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1846 def unblock_domain(user, domain_blocked) do
1847 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1850 defp set_blocks(user, blocks) do
1851 params = %{blocks: blocks}
1854 |> cast(params, [:blocks])
1855 |> validate_required([:blocks])
1856 |> update_and_set_cache()
1859 def add_to_block(user, blocked) do
1860 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1863 def remove_from_block(user, blocked) do
1864 set_blocks(user, List.delete(user.blocks, blocked))
1867 defp set_mutes(user, mutes) do
1868 params = %{mutes: mutes}
1871 |> cast(params, [:mutes])
1872 |> validate_required([:mutes])
1873 |> update_and_set_cache()
1876 def add_to_mutes(user, muted, notifications?) do
1877 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1878 set_notification_mutes(
1880 Enum.uniq([muted | user.muted_notifications]),
1886 def remove_from_mutes(user, muted) do
1887 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1888 set_notification_mutes(
1890 List.delete(user.muted_notifications, muted),
1896 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1900 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1901 params = %{muted_notifications: muted_notifications}
1904 |> cast(params, [:muted_notifications])
1905 |> validate_required([:muted_notifications])
1906 |> update_and_set_cache()
1909 def add_reblog_mute(user, ap_id) do
1910 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1913 |> cast(params, [:muted_reblogs])
1914 |> update_and_set_cache()
1917 def remove_reblog_mute(user, ap_id) do
1918 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1921 |> cast(params, [:muted_reblogs])
1922 |> update_and_set_cache()
1925 def set_invisible(user, invisible) do
1926 params = %{invisible: invisible}
1929 |> cast(params, [:invisible])
1930 |> validate_required([:invisible])
1931 |> update_and_set_cache()