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
17 alias Pleroma.Notification
19 alias Pleroma.Registration
21 alias Pleroma.RepoStreamer
24 alias Pleroma.Web.ActivityPub.ActivityPub
25 alias Pleroma.Web.ActivityPub.Utils
26 alias Pleroma.Web.CommonAPI
27 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
28 alias Pleroma.Web.OAuth
29 alias Pleroma.Web.OStatus
30 alias Pleroma.Web.RelMe
31 alias Pleroma.Web.Websub
32 alias Pleroma.Workers.BackgroundWorker
36 @type t :: %__MODULE__{}
38 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
40 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
41 @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])?)*$/
43 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
44 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
48 field(:email, :string)
50 field(:nickname, :string)
51 field(:password_hash, :string)
52 field(:password, :string, virtual: true)
53 field(:password_confirmation, :string, virtual: true)
54 field(:following, {:array, :string}, default: [])
55 field(:ap_id, :string)
57 field(:local, :boolean, default: true)
58 field(:follower_address, :string)
59 field(:following_address, :string)
60 field(:search_rank, :float, virtual: true)
61 field(:search_type, :integer, virtual: true)
62 field(:tags, {:array, :string}, default: [])
63 field(:last_refreshed_at, :naive_datetime_usec)
64 field(:last_digest_emailed_at, :naive_datetime)
65 has_many(:notifications, Notification)
66 has_many(:registrations, Registration)
67 has_many(:deliveries, Delivery)
68 embeds_one(:info, User.Info)
73 def auth_active?(%User{info: %User.Info{confirmation_pending: true}}),
74 do: !Pleroma.Config.get([:instance, :account_activation_required])
76 def auth_active?(%User{}), do: true
78 def visible_for?(user, for_user \\ nil)
80 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
82 def visible_for?(%User{} = user, for_user) do
83 auth_active?(user) || superuser?(for_user)
86 def visible_for?(_, _), do: false
88 def superuser?(%User{local: true, info: %User.Info{is_admin: true}}), do: true
89 def superuser?(%User{local: true, info: %User.Info{is_moderator: true}}), do: true
90 def superuser?(_), do: false
92 def avatar_url(user, options \\ []) do
94 %{"url" => [%{"href" => href} | _]} -> href
95 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
99 def banner_url(user, options \\ []) do
100 case user.info.banner do
101 %{"url" => [%{"href" => href} | _]} -> href
102 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
106 def profile_url(%User{info: %{source_data: %{"url" => url}}}), do: url
107 def profile_url(%User{ap_id: ap_id}), do: ap_id
108 def profile_url(_), do: nil
110 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
112 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
113 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
115 @spec ap_following(User.t()) :: Sring.t()
116 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
117 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
119 def user_info(%User{} = user, args \\ %{}) do
121 Map.get(args, :following_count, user.info.following_count || following_count(user))
123 follower_count = Map.get(args, :follower_count, user.info.follower_count)
126 note_count: user.info.note_count,
127 locked: user.info.locked,
128 confirmation_pending: user.info.confirmation_pending,
129 default_scope: user.info.default_scope
131 |> Map.put(:following_count, following_count)
132 |> Map.put(:follower_count, follower_count)
135 def follow_state(%User{} = user, %User{} = target) do
136 case Utils.fetch_latest_follow(user, target) do
137 %{data: %{"state" => state}} -> state
138 # Ideally this would be nil, but then Cachex does not commit the value
143 def get_cached_follow_state(user, target) do
144 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
145 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
148 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
149 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
150 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
153 def set_info_cache(user, args) do
154 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
157 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
158 def restrict_deactivated(query) do
160 where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info)
164 def following_count(%User{following: []}), do: 0
166 def following_count(%User{} = user) do
168 |> get_friends_query()
169 |> Repo.aggregate(:count, :id)
172 defp truncate_if_exists(params, key, max_length) do
173 if Map.has_key?(params, key) and is_binary(params[key]) do
174 {value, _chopped} = String.split_at(params[key], max_length)
175 Map.put(params, key, value)
181 def remote_user_creation(params) do
182 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
183 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
187 |> Map.put(:info, params[:info] || %{})
188 |> truncate_if_exists(:name, name_limit)
189 |> truncate_if_exists(:bio, bio_limit)
193 |> cast(params, [:bio, :name, :ap_id, :nickname, :avatar])
194 |> validate_required([:name, :ap_id])
195 |> unique_constraint(:nickname)
196 |> validate_format(:nickname, @email_regex)
197 |> validate_length(:bio, max: bio_limit)
198 |> validate_length(:name, max: name_limit)
199 |> change_info(&User.Info.remote_user_creation(&1, params[:info]))
201 case params[:info][:source_data] do
202 %{"followers" => followers, "following" => following} ->
204 |> put_change(:follower_address, followers)
205 |> put_change(:following_address, following)
208 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
209 put_change(changeset, :follower_address, followers)
213 def update_changeset(struct, params \\ %{}) do
214 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
215 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
218 |> cast(params, [:bio, :name, :avatar, :following])
219 |> unique_constraint(:nickname)
220 |> validate_format(:nickname, local_nickname_regex())
221 |> validate_length(:bio, max: bio_limit)
222 |> validate_length(:name, min: 1, max: name_limit)
225 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
226 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
227 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
229 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
240 |> unique_constraint(:nickname)
241 |> validate_format(:nickname, local_nickname_regex())
242 |> validate_length(:bio, max: bio_limit)
243 |> validate_length(:name, max: name_limit)
244 |> change_info(&User.Info.user_upgrade(&1, params[:info], remote?))
247 def password_update_changeset(struct, params) do
249 |> cast(params, [:password, :password_confirmation])
250 |> validate_required([:password, :password_confirmation])
251 |> validate_confirmation(:password)
253 |> put_embed(:info, User.Info.set_password_reset_pending(struct.info, false))
256 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
257 def reset_password(%User{id: user_id} = user, data) do
260 |> Multi.update(:user, password_update_changeset(user, data))
261 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
262 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
264 case Repo.transaction(multi) do
265 {:ok, %{user: user} = _} -> set_cache(user)
266 {:error, _, changeset, _} -> {:error, changeset}
270 def force_password_reset_async(user) do
271 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
274 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
275 def force_password_reset(user) do
276 info_cng = User.Info.set_password_reset_pending(user.info, true)
280 |> put_embed(:info, info_cng)
281 |> update_and_set_cache()
284 def register_changeset(struct, params \\ %{}, opts \\ []) do
285 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
286 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
289 if is_nil(opts[:need_confirmation]) do
290 Pleroma.Config.get([:instance, :account_activation_required])
292 opts[:need_confirmation]
296 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
297 |> validate_required([:name, :nickname, :password, :password_confirmation])
298 |> validate_confirmation(:password)
299 |> unique_constraint(:email)
300 |> unique_constraint(:nickname)
301 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
302 |> validate_format(:nickname, local_nickname_regex())
303 |> validate_format(:email, @email_regex)
304 |> validate_length(:bio, max: bio_limit)
305 |> validate_length(:name, min: 1, max: name_limit)
306 |> change_info(&User.Info.confirmation_changeset(&1, need_confirmation: need_confirmation?))
307 |> maybe_validate_required_email(opts[:external])
310 |> unique_constraint(:ap_id)
311 |> put_following_and_follower_address()
314 def maybe_validate_required_email(changeset, true), do: changeset
315 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
317 defp put_ap_id(changeset) do
318 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
319 put_change(changeset, :ap_id, ap_id)
322 defp put_following_and_follower_address(changeset) do
323 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
326 |> put_change(:following, [followers])
327 |> put_change(:follower_address, followers)
330 defp autofollow_users(user) do
331 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
334 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
337 follow_all(user, autofollowed_users)
340 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
341 def register(%Ecto.Changeset{} = changeset) do
342 with {:ok, user} <- Repo.insert(changeset) do
343 post_register_action(user)
347 def post_register_action(%User{} = user) do
348 with {:ok, user} <- autofollow_users(user),
349 {:ok, user} <- set_cache(user),
350 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
351 {:ok, _} <- try_send_confirmation_email(user) do
356 def try_send_confirmation_email(%User{} = user) do
357 if user.info.confirmation_pending &&
358 Pleroma.Config.get([:instance, :account_activation_required]) do
360 |> Pleroma.Emails.UserEmail.account_confirmation_email()
361 |> Pleroma.Emails.Mailer.deliver_async()
369 def needs_update?(%User{local: true}), do: false
371 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
373 def needs_update?(%User{local: false} = user) do
374 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
377 def needs_update?(_), do: true
379 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
380 def maybe_direct_follow(%User{} = follower, %User{local: true, info: %{locked: true}}) do
384 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
385 follow(follower, followed)
388 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
389 if not ap_enabled?(followed) do
390 follow(follower, followed)
396 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
397 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
398 def follow_all(follower, followeds) do
401 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
402 |> Enum.map(fn %{follower_address: fa} -> fa end)
406 where: u.id == ^follower.id,
411 "array(select distinct unnest (array_cat(?, ?)))",
420 {1, [follower]} = Repo.update_all(q, [])
422 Enum.each(followeds, &update_follower_count/1)
427 def follow(%User{} = follower, %User{info: info} = followed) do
428 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
429 ap_followers = followed.follower_address
433 {:error, "Could not follow user: You are deactivated."}
435 deny_follow_blocked and blocks?(followed, follower) ->
436 {:error, "Could not follow user: #{followed.nickname} blocked you."}
439 if !followed.local && follower.local && !ap_enabled?(followed) do
440 Websub.subscribe(follower, followed)
445 where: u.id == ^follower.id,
446 update: [push: [following: ^ap_followers]],
450 {1, [follower]} = Repo.update_all(q, [])
452 follower = maybe_update_following_count(follower)
454 {:ok, _} = update_follower_count(followed)
460 def unfollow(%User{} = follower, %User{} = followed) do
461 ap_followers = followed.follower_address
463 if following?(follower, followed) and follower.ap_id != followed.ap_id do
466 where: u.id == ^follower.id,
467 update: [pull: [following: ^ap_followers]],
471 {1, [follower]} = Repo.update_all(q, [])
473 follower = maybe_update_following_count(follower)
475 {:ok, followed} = update_follower_count(followed)
479 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
481 {:error, "Not subscribed!"}
485 @spec following?(User.t(), User.t()) :: boolean
486 def following?(%User{} = follower, %User{} = followed) do
487 Enum.member?(follower.following, followed.follower_address)
490 def locked?(%User{} = user) do
491 user.info.locked || false
495 Repo.get_by(User, id: id)
498 def get_by_ap_id(ap_id) do
499 Repo.get_by(User, ap_id: ap_id)
502 def get_all_by_ap_id(ap_ids) do
503 from(u in __MODULE__,
504 where: u.ap_id in ^ap_ids
509 def get_all_by_ids(ids) do
510 from(u in __MODULE__, where: u.id in ^ids)
514 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
515 # of the ap_id and the domain and tries to get that user
516 def get_by_guessed_nickname(ap_id) do
517 domain = URI.parse(ap_id).host
518 name = List.last(String.split(ap_id, "/"))
519 nickname = "#{name}@#{domain}"
521 get_cached_by_nickname(nickname)
524 def set_cache({:ok, user}), do: set_cache(user)
525 def set_cache({:error, err}), do: {:error, err}
527 def set_cache(%User{} = user) do
528 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
529 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
530 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
534 def update_and_set_cache(changeset) do
535 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
540 def invalidate_cache(user) do
541 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
542 Cachex.del(:user_cache, "nickname:#{user.nickname}")
543 Cachex.del(:user_cache, "user_info:#{user.id}")
546 def get_cached_by_ap_id(ap_id) do
547 key = "ap_id:#{ap_id}"
548 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
551 def get_cached_by_id(id) do
555 Cachex.fetch!(:user_cache, key, fn _ ->
559 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
560 {:commit, user.ap_id}
566 get_cached_by_ap_id(ap_id)
569 def get_cached_by_nickname(nickname) do
570 key = "nickname:#{nickname}"
572 Cachex.fetch!(:user_cache, key, fn ->
573 case get_or_fetch_by_nickname(nickname) do
574 {:ok, user} -> {:commit, user}
575 {:error, _error} -> {:ignore, nil}
580 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
581 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
584 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
585 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
587 restrict_to_local == false ->
588 get_cached_by_nickname(nickname_or_id)
590 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
591 get_cached_by_nickname(nickname_or_id)
598 def get_by_nickname(nickname) do
599 Repo.get_by(User, nickname: nickname) ||
600 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
601 Repo.get_by(User, nickname: local_nickname(nickname))
605 def get_by_email(email), do: Repo.get_by(User, email: email)
607 def get_by_nickname_or_email(nickname_or_email) do
608 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
611 def get_cached_user_info(user) do
612 key = "user_info:#{user.id}"
613 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
616 def fetch_by_nickname(nickname) do
617 case ActivityPub.make_user_from_nickname(nickname) do
618 {:ok, user} -> {:ok, user}
619 _ -> OStatus.make_user(nickname)
623 def get_or_fetch_by_nickname(nickname) do
624 with %User{} = user <- get_by_nickname(nickname) do
628 with [_nick, _domain] <- String.split(nickname, "@"),
629 {:ok, user} <- fetch_by_nickname(nickname) do
630 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
631 fetch_initial_posts(user)
636 _e -> {:error, "not found " <> nickname}
641 @doc "Fetch some posts when the user has just been federated with"
642 def fetch_initial_posts(user) do
643 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
646 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
647 def get_followers_query(%User{} = user, nil) do
648 User.Query.build(%{followers: user, deactivated: false})
651 def get_followers_query(user, page) do
653 |> get_followers_query(nil)
654 |> User.Query.paginate(page, 20)
657 @spec get_followers_query(User.t()) :: Ecto.Query.t()
658 def get_followers_query(user), do: get_followers_query(user, nil)
660 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
661 def get_followers(user, page \\ nil) do
663 |> get_followers_query(page)
667 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
668 def get_external_followers(user, page \\ nil) do
670 |> get_followers_query(page)
671 |> User.Query.build(%{external: true})
675 def get_followers_ids(user, page \\ nil) do
677 |> get_followers_query(page)
682 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
683 def get_friends_query(%User{} = user, nil) do
684 User.Query.build(%{friends: user, deactivated: false})
687 def get_friends_query(user, page) do
689 |> get_friends_query(nil)
690 |> User.Query.paginate(page, 20)
693 @spec get_friends_query(User.t()) :: Ecto.Query.t()
694 def get_friends_query(user), do: get_friends_query(user, nil)
696 def get_friends(user, page \\ nil) do
698 |> get_friends_query(page)
702 def get_friends_ids(user, page \\ nil) do
704 |> get_friends_query(page)
709 @spec get_follow_requests(User.t()) :: {:ok, [User.t()]}
710 def get_follow_requests(%User{} = user) do
712 |> Activity.follow_requests_for_actor()
713 |> join(:inner, [a], u in User, on: a.actor == u.ap_id)
714 |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
715 |> group_by([a, u], u.id)
720 def increase_note_count(%User{} = user) do
722 |> where(id: ^user.id)
727 "jsonb_set(?, '{note_count}', ((?->>'note_count')::int + 1)::varchar::jsonb, true)",
734 |> Repo.update_all([])
736 {1, [user]} -> set_cache(user)
741 def decrease_note_count(%User{} = user) do
743 |> where(id: ^user.id)
748 "jsonb_set(?, '{note_count}', (greatest(0, (?->>'note_count')::int - 1))::varchar::jsonb, true)",
755 |> Repo.update_all([])
757 {1, [user]} -> set_cache(user)
762 def update_note_count(%User{} = user) do
766 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
771 update_info(user, &User.Info.set_note_count(&1, note_count))
774 def update_mascot(user, url) do
776 User.Info.mascot_update(
783 |> put_embed(:info, info_changeset)
784 |> update_and_set_cache()
787 @spec maybe_fetch_follow_information(User.t()) :: User.t()
788 def maybe_fetch_follow_information(user) do
789 with {:ok, user} <- fetch_follow_information(user) do
793 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
799 def fetch_follow_information(user) do
800 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
801 update_info(user, &User.Info.follow_information_update(&1, info))
805 def update_follower_count(%User{} = user) do
806 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
807 follower_count_query =
808 User.Query.build(%{followers: user, deactivated: false})
809 |> select([u], %{count: count(u.id)})
812 |> where(id: ^user.id)
813 |> join(:inner, [u], s in subquery(follower_count_query))
818 "jsonb_set(?, '{follower_count}', ?::varchar::jsonb, true)",
825 |> Repo.update_all([])
827 {1, [user]} -> set_cache(user)
831 {:ok, maybe_fetch_follow_information(user)}
835 @spec maybe_update_following_count(User.t()) :: User.t()
836 def maybe_update_following_count(%User{local: false} = user) do
837 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
838 maybe_fetch_follow_information(user)
844 def maybe_update_following_count(user), do: user
846 def set_unread_conversation_count(%User{local: true} = user) do
847 unread_query = Participation.unread_conversation_count_for_user(user)
850 |> where([u], u.id == ^user.id)
851 |> join(:inner, [u], p in subquery(unread_query))
856 "jsonb_set(?, '{unread_conversation_count}', ?::varchar::jsonb, true)",
863 |> Repo.update_all([])
865 {1, [%{info: %User.Info{}} = user]} -> set_cache(user)
870 def set_unread_conversation_count(_), do: :noop
872 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
874 Participation.unread_conversation_count_for_user(user)
875 |> where([p], p.conversation_id == ^conversation.id)
878 |> join(:inner, [u], p in subquery(unread_query))
883 "jsonb_set(?, '{unread_conversation_count}', ((?->>'unread_conversation_count')::int + 1)::varchar::jsonb, true)",
889 |> where([u], u.id == ^user.id)
890 |> where([u, p], p.count == 0)
892 |> Repo.update_all([])
894 {1, [%{info: %User.Info{}} = user]} -> set_cache(user)
899 def increment_unread_conversation_count(_, _), do: :noop
901 def remove_duplicated_following(%User{following: following} = user) do
902 uniq_following = Enum.uniq(following)
904 if length(following) == length(uniq_following) do
908 |> update_changeset(%{following: uniq_following})
909 |> update_and_set_cache()
913 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
914 def get_users_from_set(ap_ids, local_only \\ true) do
915 criteria = %{ap_id: ap_ids, deactivated: false}
916 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
918 User.Query.build(criteria)
922 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
923 def get_recipients_from_activity(%Activity{recipients: to}) do
924 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
928 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
929 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
930 update_info(muter, &User.Info.add_to_mutes(&1, ap_id, notifications?))
933 def unmute(muter, %{ap_id: ap_id}) do
934 update_info(muter, &User.Info.remove_from_mutes(&1, ap_id))
937 def subscribe(subscriber, %{ap_id: ap_id}) do
938 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
939 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
941 if blocks?(subscribed, subscriber) and deny_follow_blocked do
942 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
944 update_info(subscribed, &User.Info.add_to_subscribers(&1, subscriber.ap_id))
949 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
950 with %User{} = user <- get_cached_by_ap_id(ap_id) do
951 update_info(user, &User.Info.remove_from_subscribers(&1, unsubscriber.ap_id))
955 def block(blocker, %User{ap_id: ap_id} = blocked) do
956 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
958 if following?(blocker, blocked) do
959 {:ok, blocker, _} = unfollow(blocker, blocked)
965 # clear any requested follows as well
967 case CommonAPI.reject_follow_request(blocked, blocker) do
968 {:ok, %User{} = updated_blocked} -> updated_blocked
973 if subscribed_to?(blocked, blocker) do
974 {:ok, blocker} = unsubscribe(blocked, blocker)
980 if following?(blocked, blocker), do: unfollow(blocked, blocker)
982 {:ok, blocker} = update_follower_count(blocker)
984 update_info(blocker, &User.Info.add_to_block(&1, ap_id))
987 # helper to handle the block given only an actor's AP id
988 def block(blocker, %{ap_id: ap_id}) do
989 block(blocker, get_cached_by_ap_id(ap_id))
992 def unblock(blocker, %{ap_id: ap_id}) do
993 update_info(blocker, &User.Info.remove_from_block(&1, ap_id))
996 def mutes?(nil, _), do: false
997 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
999 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1000 def muted_notifications?(nil, _), do: false
1002 def muted_notifications?(user, %{ap_id: ap_id}),
1003 do: Enum.member?(user.info.muted_notifications, ap_id)
1005 def blocks?(%User{} = user, %User{} = target) do
1006 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1009 def blocks?(nil, _), do: false
1011 def blocks_ap_id?(%User{} = user, %User{} = target) do
1012 Enum.member?(user.info.blocks, target.ap_id)
1015 def blocks_ap_id?(_, _), do: false
1017 def blocks_domain?(%User{} = user, %User{} = target) do
1018 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks)
1019 %{host: host} = URI.parse(target.ap_id)
1020 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1023 def blocks_domain?(_, _), do: false
1025 def subscribed_to?(user, %{ap_id: ap_id}) do
1026 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1027 Enum.member?(target.info.subscribers, user.ap_id)
1031 @spec muted_users(User.t()) :: [User.t()]
1032 def muted_users(user) do
1033 User.Query.build(%{ap_id: user.info.mutes, deactivated: false})
1037 @spec blocked_users(User.t()) :: [User.t()]
1038 def blocked_users(user) do
1039 User.Query.build(%{ap_id: user.info.blocks, deactivated: false})
1043 @spec subscribers(User.t()) :: [User.t()]
1044 def subscribers(user) do
1045 User.Query.build(%{ap_id: user.info.subscribers, deactivated: false})
1049 def block_domain(user, domain) do
1050 update_info(user, &User.Info.add_to_domain_block(&1, domain))
1053 def unblock_domain(user, domain) do
1054 update_info(user, &User.Info.remove_from_domain_block(&1, domain))
1057 def deactivate_async(user, status \\ true) do
1058 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1061 def deactivate(%User{} = user, status \\ true) do
1062 with {:ok, user} <- update_info(user, &User.Info.set_activation_status(&1, status)) do
1063 Enum.each(get_followers(user), &invalidate_cache/1)
1064 Enum.each(get_friends(user), &update_follower_count/1)
1070 def update_notification_settings(%User{} = user, settings \\ %{}) do
1071 update_info(user, &User.Info.update_notification_settings(&1, settings))
1074 def delete(%User{} = user) do
1075 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1078 def perform(:force_password_reset, user), do: force_password_reset(user)
1080 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1081 def perform(:delete, %User{} = user) do
1082 {:ok, _user} = ActivityPub.delete(user)
1084 # Remove all relationships
1087 |> Enum.each(fn follower ->
1088 ActivityPub.unfollow(follower, user)
1089 unfollow(follower, user)
1094 |> Enum.each(fn followed ->
1095 ActivityPub.unfollow(user, followed)
1096 unfollow(user, followed)
1099 delete_user_activities(user)
1100 invalidate_cache(user)
1104 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1105 def perform(:fetch_initial_posts, %User{} = user) do
1106 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1108 # Insert all the posts in reverse order, so they're in the right order on the timeline
1109 user.info.source_data["outbox"]
1110 |> Utils.fetch_ordered_collection(pages)
1112 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1115 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1117 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1118 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1119 when is_list(blocked_identifiers) do
1121 blocked_identifiers,
1122 fn blocked_identifier ->
1123 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1124 {:ok, blocker} <- block(blocker, blocked),
1125 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1129 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1136 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1137 def perform(:follow_import, %User{} = follower, followed_identifiers)
1138 when is_list(followed_identifiers) do
1140 followed_identifiers,
1141 fn followed_identifier ->
1142 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1143 {:ok, follower} <- maybe_direct_follow(follower, followed),
1144 {:ok, _} <- ActivityPub.follow(follower, followed) do
1148 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1155 @spec external_users_query() :: Ecto.Query.t()
1156 def external_users_query do
1164 @spec external_users(keyword()) :: [User.t()]
1165 def external_users(opts \\ []) do
1167 external_users_query()
1168 |> select([u], struct(u, [:id, :ap_id, :info]))
1172 do: where(query, [u], u.id > ^opts[:max_id]),
1177 do: limit(query, ^opts[:limit]),
1183 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1184 BackgroundWorker.enqueue("blocks_import", %{
1185 "blocker_id" => blocker.id,
1186 "blocked_identifiers" => blocked_identifiers
1190 def follow_import(%User{} = follower, followed_identifiers)
1191 when is_list(followed_identifiers) do
1192 BackgroundWorker.enqueue("follow_import", %{
1193 "follower_id" => follower.id,
1194 "followed_identifiers" => followed_identifiers
1198 def delete_user_activities(%User{ap_id: ap_id}) do
1200 |> Activity.Queries.by_actor()
1201 |> RepoStreamer.chunk_stream(50)
1202 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1206 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1208 |> Object.normalize()
1209 |> ActivityPub.delete()
1212 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1213 object = Object.normalize(activity)
1216 |> get_cached_by_ap_id()
1217 |> ActivityPub.unlike(object)
1220 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1221 object = Object.normalize(activity)
1224 |> get_cached_by_ap_id()
1225 |> ActivityPub.unannounce(object)
1228 defp delete_activity(_activity), do: "Doing nothing"
1230 def html_filter_policy(%User{info: %{no_rich_text: true}}) do
1231 Pleroma.HTML.Scrubber.TwitterText
1234 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1236 def fetch_by_ap_id(ap_id) do
1237 case ActivityPub.make_user_from_ap_id(ap_id) do
1242 case OStatus.make_user(ap_id) do
1243 {:ok, user} -> {:ok, user}
1244 _ -> {:error, "Could not fetch by AP id"}
1249 def get_or_fetch_by_ap_id(ap_id) do
1250 user = get_cached_by_ap_id(ap_id)
1252 if !is_nil(user) and !needs_update?(user) do
1255 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1256 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1258 resp = fetch_by_ap_id(ap_id)
1260 if should_fetch_initial do
1261 with {:ok, %User{} = user} <- resp do
1262 fetch_initial_posts(user)
1270 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1271 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1272 with %User{} = user <- get_cached_by_ap_id(uri) do
1277 %User{info: %User.Info{}}
1278 |> cast(%{}, [:ap_id, :nickname, :local])
1279 |> put_change(:ap_id, uri)
1280 |> put_change(:nickname, nickname)
1281 |> put_change(:local, true)
1282 |> put_change(:follower_address, uri <> "/followers")
1290 def public_key_from_info(%{
1291 source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}
1295 |> :public_key.pem_decode()
1297 |> :public_key.pem_entry_decode()
1303 def public_key_from_info(%{magic_key: magic_key}) when not is_nil(magic_key) do
1304 {:ok, Pleroma.Web.Salmon.decode_key(magic_key)}
1307 def public_key_from_info(_), do: {:error, "not found key"}
1309 def get_public_key_for_ap_id(ap_id) do
1310 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1311 {:ok, public_key} <- public_key_from_info(user.info) do
1318 defp blank?(""), do: nil
1319 defp blank?(n), do: n
1321 def insert_or_update_user(data) do
1323 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1324 |> remote_user_creation()
1325 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1329 def ap_enabled?(%User{local: true}), do: true
1330 def ap_enabled?(%User{info: info}), do: info.ap_enabled
1331 def ap_enabled?(_), do: false
1333 @doc "Gets or fetch a user by uri or nickname."
1334 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1335 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1336 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1338 # wait a period of time and return newest version of the User structs
1339 # this is because we have synchronous follow APIs and need to simulate them
1340 # with an async handshake
1341 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1342 with %User{} = a <- get_cached_by_id(a.id),
1343 %User{} = b <- get_cached_by_id(b.id) do
1350 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1351 with :ok <- :timer.sleep(timeout),
1352 %User{} = a <- get_cached_by_id(a.id),
1353 %User{} = b <- get_cached_by_id(b.id) do
1360 def parse_bio(bio) when is_binary(bio) and bio != "" do
1362 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1366 def parse_bio(_), do: ""
1368 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1369 # TODO: get profile URLs other than user.ap_id
1370 profile_urls = [user.ap_id]
1373 |> CommonUtils.format_input("text/plain",
1374 mentions_format: :full,
1375 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1380 def parse_bio(_, _), do: ""
1382 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1383 Repo.transaction(fn ->
1384 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1388 def tag(nickname, tags) when is_binary(nickname),
1389 do: tag(get_by_nickname(nickname), tags)
1391 def tag(%User{} = user, tags),
1392 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1394 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1395 Repo.transaction(fn ->
1396 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1400 def untag(nickname, tags) when is_binary(nickname),
1401 do: untag(get_by_nickname(nickname), tags)
1403 def untag(%User{} = user, tags),
1404 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1406 defp update_tags(%User{} = user, new_tags) do
1407 {:ok, updated_user} =
1409 |> change(%{tags: new_tags})
1410 |> update_and_set_cache()
1415 defp normalize_tags(tags) do
1418 |> Enum.map(&String.downcase/1)
1421 defp local_nickname_regex do
1422 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1423 @extended_local_nickname_regex
1425 @strict_local_nickname_regex
1429 def local_nickname(nickname_or_mention) do
1432 |> String.split("@")
1436 def full_nickname(nickname_or_mention),
1437 do: String.trim_leading(nickname_or_mention, "@")
1439 def error_user(ap_id) do
1444 nickname: "erroruser@example.com",
1445 inserted_at: NaiveDateTime.utc_now()
1449 @spec all_superusers() :: [User.t()]
1450 def all_superusers do
1451 User.Query.build(%{super_users: true, local: true, deactivated: false})
1455 def showing_reblogs?(%User{} = user, %User{} = target) do
1456 target.ap_id not in user.info.muted_reblogs
1460 The function returns a query to get users with no activity for given interval of days.
1461 Inactive users are those who didn't read any notification, or had any activity where
1462 the user is the activity's actor, during `inactivity_threshold` days.
1463 Deactivated users will not appear in this list.
1467 iex> Pleroma.User.list_inactive_users()
1470 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1471 def list_inactive_users_query(inactivity_threshold \\ 7) do
1472 negative_inactivity_threshold = -inactivity_threshold
1473 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1474 # Subqueries are not supported in `where` clauses, join gets too complicated.
1475 has_read_notifications =
1476 from(n in Pleroma.Notification,
1477 where: n.seen == true,
1479 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1482 |> Pleroma.Repo.all()
1484 from(u in Pleroma.User,
1485 left_join: a in Pleroma.Activity,
1486 on: u.ap_id == a.actor,
1487 where: not is_nil(u.nickname),
1488 where: fragment("not (?->'deactivated' @> 'true')", u.info),
1489 where: u.id not in ^has_read_notifications,
1492 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1493 is_nil(max(a.inserted_at))
1498 Enable or disable email notifications for user
1502 iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => false}}}, "digest", true)
1503 Pleroma.User{info: %{email_notifications: %{"digest" => true}}}
1505 iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => true}}}, "digest", false)
1506 Pleroma.User{info: %{email_notifications: %{"digest" => false}}}
1508 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1509 {:ok, t()} | {:error, Ecto.Changeset.t()}
1510 def switch_email_notifications(user, type, status) do
1511 update_info(user, &User.Info.update_email_notifications(&1, %{type => status}))
1515 Set `last_digest_emailed_at` value for the user to current time
1517 @spec touch_last_digest_emailed_at(t()) :: t()
1518 def touch_last_digest_emailed_at(user) do
1519 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1521 {:ok, updated_user} =
1523 |> change(%{last_digest_emailed_at: now})
1524 |> update_and_set_cache()
1529 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1530 def toggle_confirmation(%User{} = user) do
1531 need_confirmation? = !user.info.confirmation_pending
1534 |> update_info(&User.Info.confirmation_changeset(&1, need_confirmation: need_confirmation?))
1537 def get_mascot(%{info: %{mascot: %{} = mascot}}) when not is_nil(mascot) do
1541 def get_mascot(%{info: %{mascot: mascot}}) when is_nil(mascot) do
1542 # use instance-default
1543 config = Pleroma.Config.get([:assets, :mascots])
1544 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1545 mascot = Keyword.get(config, default_mascot)
1548 "id" => "default-mascot",
1549 "url" => mascot[:url],
1550 "preview_url" => mascot[:url],
1552 "mime_type" => mascot[:mime_type]
1557 def ensure_keys_present(%{info: %{keys: keys}} = user) when not is_nil(keys), do: {:ok, user}
1559 def ensure_keys_present(%User{} = user) do
1560 with {:ok, pem} <- Keys.generate_rsa_pem() do
1561 update_info(user, &User.Info.set_keys(&1, pem))
1565 def get_ap_ids_by_nicknames(nicknames) do
1567 where: u.nickname in ^nicknames,
1573 defdelegate search(query, opts \\ []), to: User.Search
1575 defp put_password_hash(
1576 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1578 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1581 defp put_password_hash(changeset), do: changeset
1583 def is_internal_user?(%User{nickname: nil}), do: true
1584 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1585 def is_internal_user?(_), do: false
1587 # A hack because user delete activities have a fake id for whatever reason
1588 # TODO: Get rid of this
1589 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1591 def get_delivered_users_by_object_id(object_id) do
1593 inner_join: delivery in assoc(u, :deliveries),
1594 where: delivery.object_id == ^object_id
1599 def change_email(user, email) do
1601 |> cast(%{email: email}, [:email])
1602 |> validate_required([:email])
1603 |> unique_constraint(:email)
1604 |> validate_format(:email, @email_regex)
1605 |> update_and_set_cache()
1609 Changes `user.info` and returns the user changeset.
1611 `fun` is called with the `user.info`.
1613 def change_info(user, fun) do
1614 changeset = change(user)
1615 info = get_field(changeset, :info) || %User.Info{}
1616 put_embed(changeset, :info, fun.(info))
1620 Updates `user.info` and sets cache.
1622 `fun` is called with the `user.info`.
1624 def update_info(user, fun) do
1627 |> update_and_set_cache()