Merge branch 'static-accept-missing' into 'develop'
[akkoma] / lib / pleroma / user.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.User do
6 use Ecto.Schema
7
8 import Ecto.Changeset
9 import Ecto.Query
10 import Ecto, only: [assoc: 2]
11
12 alias Comeonin.Pbkdf2
13 alias Ecto.Multi
14 alias Pleroma.Activity
15 alias Pleroma.Config
16 alias Pleroma.Conversation.Participation
17 alias Pleroma.Delivery
18 alias Pleroma.FollowingRelationship
19 alias Pleroma.Keys
20 alias Pleroma.Notification
21 alias Pleroma.Object
22 alias Pleroma.Registration
23 alias Pleroma.Repo
24 alias Pleroma.RepoStreamer
25 alias Pleroma.User
26 alias Pleroma.UserRelationship
27 alias Pleroma.Web
28 alias Pleroma.Web.ActivityPub.ActivityPub
29 alias Pleroma.Web.ActivityPub.Utils
30 alias Pleroma.Web.CommonAPI
31 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
32 alias Pleroma.Web.OAuth
33 alias Pleroma.Web.RelMe
34 alias Pleroma.Workers.BackgroundWorker
35
36 require Logger
37
38 @type t :: %__MODULE__{}
39 @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
40 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
41
42 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
43 @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
44
45 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
46 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
47
48 # AP ID user relationships (blocks, mutes etc.)
49 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
50 @user_relationships_config [
51 block: [
52 blocker_blocks: :blocked_users,
53 blockee_blocks: :blocker_users
54 ],
55 mute: [
56 muter_mutes: :muted_users,
57 mutee_mutes: :muter_users
58 ],
59 reblog_mute: [
60 reblog_muter_mutes: :reblog_muted_users,
61 reblog_mutee_mutes: :reblog_muter_users
62 ],
63 notification_mute: [
64 notification_muter_mutes: :notification_muted_users,
65 notification_mutee_mutes: :notification_muter_users
66 ],
67 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
68 inverse_subscription: [
69 subscribee_subscriptions: :subscriber_users,
70 subscriber_subscriptions: :subscribee_users
71 ]
72 ]
73
74 schema "users" do
75 field(:bio, :string)
76 field(:email, :string)
77 field(:name, :string)
78 field(:nickname, :string)
79 field(:password_hash, :string)
80 field(:password, :string, virtual: true)
81 field(:password_confirmation, :string, virtual: true)
82 field(:keys, :string)
83 field(:ap_id, :string)
84 field(:avatar, :map)
85 field(:local, :boolean, default: true)
86 field(:follower_address, :string)
87 field(:following_address, :string)
88 field(:search_rank, :float, virtual: true)
89 field(:search_type, :integer, virtual: true)
90 field(:tags, {:array, :string}, default: [])
91 field(:last_refreshed_at, :naive_datetime_usec)
92 field(:last_digest_emailed_at, :naive_datetime)
93 field(:banner, :map, default: %{})
94 field(:background, :map, default: %{})
95 field(:source_data, :map, default: %{})
96 field(:note_count, :integer, default: 0)
97 field(:follower_count, :integer, default: 0)
98 field(:following_count, :integer, default: 0)
99 field(:locked, :boolean, default: false)
100 field(:confirmation_pending, :boolean, default: false)
101 field(:password_reset_pending, :boolean, default: false)
102 field(:confirmation_token, :string, default: nil)
103 field(:default_scope, :string, default: "public")
104 field(:domain_blocks, {:array, :string}, default: [])
105 field(:deactivated, :boolean, default: false)
106 field(:no_rich_text, :boolean, default: false)
107 field(:ap_enabled, :boolean, default: false)
108 field(:is_moderator, :boolean, default: false)
109 field(:is_admin, :boolean, default: false)
110 field(:show_role, :boolean, default: true)
111 field(:settings, :map, default: nil)
112 field(:magic_key, :string, default: nil)
113 field(:uri, :string, default: nil)
114 field(:hide_followers_count, :boolean, default: false)
115 field(:hide_follows_count, :boolean, default: false)
116 field(:hide_followers, :boolean, default: false)
117 field(:hide_follows, :boolean, default: false)
118 field(:hide_favorites, :boolean, default: true)
119 field(:unread_conversation_count, :integer, default: 0)
120 field(:pinned_activities, {:array, :string}, default: [])
121 field(:email_notifications, :map, default: %{"digest" => false})
122 field(:mascot, :map, default: nil)
123 field(:emoji, {:array, :map}, default: [])
124 field(:pleroma_settings_store, :map, default: %{})
125 field(:fields, {:array, :map}, default: [])
126 field(:raw_fields, {:array, :map}, default: [])
127 field(:discoverable, :boolean, default: false)
128 field(:invisible, :boolean, default: false)
129 field(:allow_following_move, :boolean, default: true)
130 field(:skip_thread_containment, :boolean, default: false)
131 field(:actor_type, :string, default: "Person")
132 field(:also_known_as, {:array, :string}, default: [])
133
134 embeds_one(
135 :notification_settings,
136 Pleroma.User.NotificationSetting,
137 on_replace: :update
138 )
139
140 has_many(:notifications, Notification)
141 has_many(:registrations, Registration)
142 has_many(:deliveries, Delivery)
143
144 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
145 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
146
147 for {relationship_type,
148 [
149 {outgoing_relation, outgoing_relation_target},
150 {incoming_relation, incoming_relation_source}
151 ]} <- @user_relationships_config do
152 # Definitions of `has_many :blocker_blocks`, `has_many :muter_mutes` etc.
153 has_many(outgoing_relation, UserRelationship,
154 foreign_key: :source_id,
155 where: [relationship_type: relationship_type]
156 )
157
158 # Definitions of `has_many :blockee_blocks`, `has_many :mutee_mutes` etc.
159 has_many(incoming_relation, UserRelationship,
160 foreign_key: :target_id,
161 where: [relationship_type: relationship_type]
162 )
163
164 # Definitions of `has_many :blocked_users`, `has_many :muted_users` etc.
165 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
166
167 # Definitions of `has_many :blocker_users`, `has_many :muter_users` etc.
168 has_many(incoming_relation_source, through: [incoming_relation, :source])
169 end
170
171 # `:blocks` is deprecated (replaced with `blocked_users` relation)
172 field(:blocks, {:array, :string}, default: [])
173 # `:mutes` is deprecated (replaced with `muted_users` relation)
174 field(:mutes, {:array, :string}, default: [])
175 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
176 field(:muted_reblogs, {:array, :string}, default: [])
177 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
178 field(:muted_notifications, {:array, :string}, default: [])
179 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
180 field(:subscribers, {:array, :string}, default: [])
181
182 timestamps()
183 end
184
185 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
186 @user_relationships_config do
187 # Definitions of `blocked_users_relation/1`, `muted_users_relation/1`, etc.
188 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
189 target_users_query = assoc(user, unquote(outgoing_relation_target))
190
191 if restrict_deactivated? do
192 restrict_deactivated(target_users_query)
193 else
194 target_users_query
195 end
196 end
197
198 # Definitions of `blocked_users/1`, `muted_users/1`, etc.
199 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
200 __MODULE__
201 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
202 user,
203 restrict_deactivated?
204 ])
205 |> Repo.all()
206 end
207
208 # Definitions of `blocked_users_ap_ids/1`, `muted_users_ap_ids/1`, etc.
209 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
210 __MODULE__
211 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
212 user,
213 restrict_deactivated?
214 ])
215 |> select([u], u.ap_id)
216 |> Repo.all()
217 end
218 end
219
220 @doc "Returns status account"
221 @spec account_status(User.t()) :: account_status()
222 def account_status(%User{deactivated: true}), do: :deactivated
223 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
224
225 def account_status(%User{confirmation_pending: true}) do
226 case Config.get([:instance, :account_activation_required]) do
227 true -> :confirmation_pending
228 _ -> :active
229 end
230 end
231
232 def account_status(%User{}), do: :active
233
234 @spec visible_for?(User.t(), User.t() | nil) :: boolean()
235 def visible_for?(user, for_user \\ nil)
236
237 def visible_for?(%User{invisible: true}, _), do: false
238
239 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
240
241 def visible_for?(%User{} = user, for_user) do
242 account_status(user) == :active || superuser?(for_user)
243 end
244
245 def visible_for?(_, _), do: false
246
247 @spec superuser?(User.t()) :: boolean()
248 def superuser?(%User{local: true, is_admin: true}), do: true
249 def superuser?(%User{local: true, is_moderator: true}), do: true
250 def superuser?(_), do: false
251
252 @spec invisible?(User.t()) :: boolean()
253 def invisible?(%User{invisible: true}), do: true
254 def invisible?(_), do: false
255
256 def avatar_url(user, options \\ []) do
257 case user.avatar do
258 %{"url" => [%{"href" => href} | _]} -> href
259 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
260 end
261 end
262
263 def banner_url(user, options \\ []) do
264 case user.banner do
265 %{"url" => [%{"href" => href} | _]} -> href
266 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
267 end
268 end
269
270 def profile_url(%User{source_data: %{"url" => url}}), do: url
271 def profile_url(%User{ap_id: ap_id}), do: ap_id
272 def profile_url(_), do: nil
273
274 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
275
276 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
277 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
278
279 @spec ap_following(User.t()) :: Sring.t()
280 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
281 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
282
283 def follow_state(%User{} = user, %User{} = target) do
284 case Utils.fetch_latest_follow(user, target) do
285 %{data: %{"state" => state}} -> state
286 # Ideally this would be nil, but then Cachex does not commit the value
287 _ -> false
288 end
289 end
290
291 def get_cached_follow_state(user, target) do
292 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
293 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
294 end
295
296 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
297 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
298 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
299 end
300
301 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
302 def restrict_deactivated(query) do
303 from(u in query, where: u.deactivated != ^true)
304 end
305
306 defdelegate following_count(user), to: FollowingRelationship
307
308 defp truncate_fields_param(params) do
309 if Map.has_key?(params, :fields) do
310 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
311 else
312 params
313 end
314 end
315
316 defp truncate_if_exists(params, key, max_length) do
317 if Map.has_key?(params, key) and is_binary(params[key]) do
318 {value, _chopped} = String.split_at(params[key], max_length)
319 Map.put(params, key, value)
320 else
321 params
322 end
323 end
324
325 def remote_user_creation(params) do
326 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
327 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
328
329 params =
330 params
331 |> truncate_if_exists(:name, name_limit)
332 |> truncate_if_exists(:bio, bio_limit)
333 |> truncate_fields_param()
334
335 changeset =
336 %User{local: false}
337 |> cast(
338 params,
339 [
340 :bio,
341 :name,
342 :ap_id,
343 :nickname,
344 :avatar,
345 :ap_enabled,
346 :source_data,
347 :banner,
348 :locked,
349 :magic_key,
350 :uri,
351 :hide_followers,
352 :hide_follows,
353 :hide_followers_count,
354 :hide_follows_count,
355 :follower_count,
356 :fields,
357 :following_count,
358 :discoverable,
359 :invisible,
360 :actor_type,
361 :also_known_as
362 ]
363 )
364 |> validate_required([:name, :ap_id])
365 |> unique_constraint(:nickname)
366 |> validate_format(:nickname, @email_regex)
367 |> validate_length(:bio, max: bio_limit)
368 |> validate_length(:name, max: name_limit)
369 |> validate_fields(true)
370
371 case params[:source_data] do
372 %{"followers" => followers, "following" => following} ->
373 changeset
374 |> put_change(:follower_address, followers)
375 |> put_change(:following_address, following)
376
377 _ ->
378 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
379 put_change(changeset, :follower_address, followers)
380 end
381 end
382
383 def update_changeset(struct, params \\ %{}) do
384 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
385 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
386
387 struct
388 |> cast(
389 params,
390 [
391 :bio,
392 :name,
393 :avatar,
394 :locked,
395 :no_rich_text,
396 :default_scope,
397 :banner,
398 :hide_follows,
399 :hide_followers,
400 :hide_followers_count,
401 :hide_follows_count,
402 :hide_favorites,
403 :allow_following_move,
404 :background,
405 :show_role,
406 :skip_thread_containment,
407 :fields,
408 :raw_fields,
409 :pleroma_settings_store,
410 :discoverable,
411 :actor_type,
412 :also_known_as
413 ]
414 )
415 |> unique_constraint(:nickname)
416 |> validate_format(:nickname, local_nickname_regex())
417 |> validate_length(:bio, max: bio_limit)
418 |> validate_length(:name, min: 1, max: name_limit)
419 |> validate_fields(false)
420 end
421
422 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
423 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
424 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
425
426 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
427
428 params = if remote?, do: truncate_fields_param(params), else: params
429
430 struct
431 |> cast(
432 params,
433 [
434 :bio,
435 :name,
436 :follower_address,
437 :following_address,
438 :avatar,
439 :last_refreshed_at,
440 :ap_enabled,
441 :source_data,
442 :banner,
443 :locked,
444 :magic_key,
445 :follower_count,
446 :following_count,
447 :hide_follows,
448 :fields,
449 :hide_followers,
450 :allow_following_move,
451 :discoverable,
452 :hide_followers_count,
453 :hide_follows_count,
454 :actor_type,
455 :also_known_as
456 ]
457 )
458 |> unique_constraint(:nickname)
459 |> validate_format(:nickname, local_nickname_regex())
460 |> validate_length(:bio, max: bio_limit)
461 |> validate_length(:name, max: name_limit)
462 |> validate_fields(remote?)
463 end
464
465 def password_update_changeset(struct, params) do
466 struct
467 |> cast(params, [:password, :password_confirmation])
468 |> validate_required([:password, :password_confirmation])
469 |> validate_confirmation(:password)
470 |> put_password_hash()
471 |> put_change(:password_reset_pending, false)
472 end
473
474 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
475 def reset_password(%User{id: user_id} = user, data) do
476 multi =
477 Multi.new()
478 |> Multi.update(:user, password_update_changeset(user, data))
479 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
480 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
481
482 case Repo.transaction(multi) do
483 {:ok, %{user: user} = _} -> set_cache(user)
484 {:error, _, changeset, _} -> {:error, changeset}
485 end
486 end
487
488 def update_password_reset_pending(user, value) do
489 user
490 |> change()
491 |> put_change(:password_reset_pending, value)
492 |> update_and_set_cache()
493 end
494
495 def force_password_reset_async(user) do
496 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
497 end
498
499 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
500 def force_password_reset(user), do: update_password_reset_pending(user, true)
501
502 def register_changeset(struct, params \\ %{}, opts \\ []) do
503 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
504 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
505
506 need_confirmation? =
507 if is_nil(opts[:need_confirmation]) do
508 Pleroma.Config.get([:instance, :account_activation_required])
509 else
510 opts[:need_confirmation]
511 end
512
513 struct
514 |> confirmation_changeset(need_confirmation: need_confirmation?)
515 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
516 |> validate_required([:name, :nickname, :password, :password_confirmation])
517 |> validate_confirmation(:password)
518 |> unique_constraint(:email)
519 |> unique_constraint(:nickname)
520 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
521 |> validate_format(:nickname, local_nickname_regex())
522 |> validate_format(:email, @email_regex)
523 |> validate_length(:bio, max: bio_limit)
524 |> validate_length(:name, min: 1, max: name_limit)
525 |> maybe_validate_required_email(opts[:external])
526 |> put_password_hash
527 |> put_ap_id()
528 |> unique_constraint(:ap_id)
529 |> put_following_and_follower_address()
530 end
531
532 def maybe_validate_required_email(changeset, true), do: changeset
533
534 def maybe_validate_required_email(changeset, _) do
535 if Pleroma.Config.get([:instance, :account_activation_required]) do
536 validate_required(changeset, [:email])
537 else
538 changeset
539 end
540 end
541
542 defp put_ap_id(changeset) do
543 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
544 put_change(changeset, :ap_id, ap_id)
545 end
546
547 defp put_following_and_follower_address(changeset) do
548 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
549
550 changeset
551 |> put_change(:follower_address, followers)
552 end
553
554 defp autofollow_users(user) do
555 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
556
557 autofollowed_users =
558 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
559 |> Repo.all()
560
561 follow_all(user, autofollowed_users)
562 end
563
564 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
565 def register(%Ecto.Changeset{} = changeset) do
566 with {:ok, user} <- Repo.insert(changeset) do
567 post_register_action(user)
568 end
569 end
570
571 def post_register_action(%User{} = user) do
572 with {:ok, user} <- autofollow_users(user),
573 {:ok, user} <- set_cache(user),
574 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
575 {:ok, _} <- try_send_confirmation_email(user) do
576 {:ok, user}
577 end
578 end
579
580 def try_send_confirmation_email(%User{} = user) do
581 if user.confirmation_pending &&
582 Pleroma.Config.get([:instance, :account_activation_required]) do
583 user
584 |> Pleroma.Emails.UserEmail.account_confirmation_email()
585 |> Pleroma.Emails.Mailer.deliver_async()
586
587 {:ok, :enqueued}
588 else
589 {:ok, :noop}
590 end
591 end
592
593 def try_send_confirmation_email(users) do
594 Enum.each(users, &try_send_confirmation_email/1)
595 end
596
597 def needs_update?(%User{local: true}), do: false
598
599 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
600
601 def needs_update?(%User{local: false} = user) do
602 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
603 end
604
605 def needs_update?(_), do: true
606
607 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
608 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
609 follow(follower, followed, "pending")
610 end
611
612 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
613 follow(follower, followed)
614 end
615
616 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
617 if not ap_enabled?(followed) do
618 follow(follower, followed)
619 else
620 {:ok, follower}
621 end
622 end
623
624 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
625 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
626 def follow_all(follower, followeds) do
627 followeds
628 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
629 |> Enum.each(&follow(follower, &1, "accept"))
630
631 set_cache(follower)
632 end
633
634 defdelegate following(user), to: FollowingRelationship
635
636 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
637 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
638
639 cond do
640 followed.deactivated ->
641 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
642
643 deny_follow_blocked and blocks?(followed, follower) ->
644 {:error, "Could not follow user: #{followed.nickname} blocked you."}
645
646 true ->
647 FollowingRelationship.follow(follower, followed, state)
648
649 {:ok, _} = update_follower_count(followed)
650
651 follower
652 |> update_following_count()
653 |> set_cache()
654 end
655 end
656
657 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
658 {:error, "Not subscribed!"}
659 end
660
661 def unfollow(%User{} = follower, %User{} = followed) do
662 case get_follow_state(follower, followed) do
663 state when state in ["accept", "pending"] ->
664 FollowingRelationship.unfollow(follower, followed)
665 {:ok, followed} = update_follower_count(followed)
666
667 {:ok, follower} =
668 follower
669 |> update_following_count()
670 |> set_cache()
671
672 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
673
674 nil ->
675 {:error, "Not subscribed!"}
676 end
677 end
678
679 defdelegate following?(follower, followed), to: FollowingRelationship
680
681 def get_follow_state(%User{} = follower, %User{} = following) do
682 following_relationship = FollowingRelationship.get(follower, following)
683
684 case {following_relationship, following.local} do
685 {nil, false} ->
686 case Utils.fetch_latest_follow(follower, following) do
687 %{data: %{"state" => state}} when state in ["pending", "accept"] -> state
688 _ -> nil
689 end
690
691 {%{state: state}, _} ->
692 state
693
694 {nil, _} ->
695 nil
696 end
697 end
698
699 def locked?(%User{} = user) do
700 user.locked || false
701 end
702
703 def get_by_id(id) do
704 Repo.get_by(User, id: id)
705 end
706
707 def get_by_ap_id(ap_id) do
708 Repo.get_by(User, ap_id: ap_id)
709 end
710
711 def get_all_by_ap_id(ap_ids) do
712 from(u in __MODULE__,
713 where: u.ap_id in ^ap_ids
714 )
715 |> Repo.all()
716 end
717
718 def get_all_by_ids(ids) do
719 from(u in __MODULE__, where: u.id in ^ids)
720 |> Repo.all()
721 end
722
723 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
724 # of the ap_id and the domain and tries to get that user
725 def get_by_guessed_nickname(ap_id) do
726 domain = URI.parse(ap_id).host
727 name = List.last(String.split(ap_id, "/"))
728 nickname = "#{name}@#{domain}"
729
730 get_cached_by_nickname(nickname)
731 end
732
733 def set_cache({:ok, user}), do: set_cache(user)
734 def set_cache({:error, err}), do: {:error, err}
735
736 def set_cache(%User{} = user) do
737 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
738 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
739 {:ok, user}
740 end
741
742 def update_and_set_cache(struct, params) do
743 struct
744 |> update_changeset(params)
745 |> update_and_set_cache()
746 end
747
748 def update_and_set_cache(changeset) do
749 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
750 set_cache(user)
751 end
752 end
753
754 def invalidate_cache(user) do
755 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
756 Cachex.del(:user_cache, "nickname:#{user.nickname}")
757 end
758
759 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
760 def get_cached_by_ap_id(ap_id) do
761 key = "ap_id:#{ap_id}"
762
763 with {:ok, nil} <- Cachex.get(:user_cache, key),
764 user when not is_nil(user) <- get_by_ap_id(ap_id),
765 {:ok, true} <- Cachex.put(:user_cache, key, user) do
766 user
767 else
768 {:ok, user} -> user
769 nil -> nil
770 end
771 end
772
773 def get_cached_by_id(id) do
774 key = "id:#{id}"
775
776 ap_id =
777 Cachex.fetch!(:user_cache, key, fn _ ->
778 user = get_by_id(id)
779
780 if user do
781 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
782 {:commit, user.ap_id}
783 else
784 {:ignore, ""}
785 end
786 end)
787
788 get_cached_by_ap_id(ap_id)
789 end
790
791 def get_cached_by_nickname(nickname) do
792 key = "nickname:#{nickname}"
793
794 Cachex.fetch!(:user_cache, key, fn ->
795 case get_or_fetch_by_nickname(nickname) do
796 {:ok, user} -> {:commit, user}
797 {:error, _error} -> {:ignore, nil}
798 end
799 end)
800 end
801
802 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
803 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
804
805 cond do
806 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
807 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
808
809 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
810 get_cached_by_nickname(nickname_or_id)
811
812 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
813 get_cached_by_nickname(nickname_or_id)
814
815 true ->
816 nil
817 end
818 end
819
820 def get_by_nickname(nickname) do
821 Repo.get_by(User, nickname: nickname) ||
822 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
823 Repo.get_by(User, nickname: local_nickname(nickname))
824 end
825 end
826
827 def get_by_email(email), do: Repo.get_by(User, email: email)
828
829 def get_by_nickname_or_email(nickname_or_email) do
830 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
831 end
832
833 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
834
835 def get_or_fetch_by_nickname(nickname) do
836 with %User{} = user <- get_by_nickname(nickname) do
837 {:ok, user}
838 else
839 _e ->
840 with [_nick, _domain] <- String.split(nickname, "@"),
841 {:ok, user} <- fetch_by_nickname(nickname) do
842 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
843 fetch_initial_posts(user)
844 end
845
846 {:ok, user}
847 else
848 _e -> {:error, "not found " <> nickname}
849 end
850 end
851 end
852
853 @doc "Fetch some posts when the user has just been federated with"
854 def fetch_initial_posts(user) do
855 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
856 end
857
858 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
859 def get_followers_query(%User{} = user, nil) do
860 User.Query.build(%{followers: user, deactivated: false})
861 end
862
863 def get_followers_query(user, page) do
864 user
865 |> get_followers_query(nil)
866 |> User.Query.paginate(page, 20)
867 end
868
869 @spec get_followers_query(User.t()) :: Ecto.Query.t()
870 def get_followers_query(user), do: get_followers_query(user, nil)
871
872 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
873 def get_followers(user, page \\ nil) do
874 user
875 |> get_followers_query(page)
876 |> Repo.all()
877 end
878
879 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
880 def get_external_followers(user, page \\ nil) do
881 user
882 |> get_followers_query(page)
883 |> User.Query.build(%{external: true})
884 |> Repo.all()
885 end
886
887 def get_followers_ids(user, page \\ nil) do
888 user
889 |> get_followers_query(page)
890 |> select([u], u.id)
891 |> Repo.all()
892 end
893
894 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
895 def get_friends_query(%User{} = user, nil) do
896 User.Query.build(%{friends: user, deactivated: false})
897 end
898
899 def get_friends_query(user, page) do
900 user
901 |> get_friends_query(nil)
902 |> User.Query.paginate(page, 20)
903 end
904
905 @spec get_friends_query(User.t()) :: Ecto.Query.t()
906 def get_friends_query(user), do: get_friends_query(user, nil)
907
908 def get_friends(user, page \\ nil) do
909 user
910 |> get_friends_query(page)
911 |> Repo.all()
912 end
913
914 def get_friends_ap_ids(user) do
915 user
916 |> get_friends_query(nil)
917 |> select([u], u.ap_id)
918 |> Repo.all()
919 end
920
921 def get_friends_ids(user, page \\ nil) do
922 user
923 |> get_friends_query(page)
924 |> select([u], u.id)
925 |> Repo.all()
926 end
927
928 defdelegate get_follow_requests(user), to: FollowingRelationship
929
930 def increase_note_count(%User{} = user) do
931 User
932 |> where(id: ^user.id)
933 |> update([u], inc: [note_count: 1])
934 |> select([u], u)
935 |> Repo.update_all([])
936 |> case do
937 {1, [user]} -> set_cache(user)
938 _ -> {:error, user}
939 end
940 end
941
942 def decrease_note_count(%User{} = user) do
943 User
944 |> where(id: ^user.id)
945 |> update([u],
946 set: [
947 note_count: fragment("greatest(0, note_count - 1)")
948 ]
949 )
950 |> select([u], u)
951 |> Repo.update_all([])
952 |> case do
953 {1, [user]} -> set_cache(user)
954 _ -> {:error, user}
955 end
956 end
957
958 def update_note_count(%User{} = user, note_count \\ nil) do
959 note_count =
960 note_count ||
961 from(
962 a in Object,
963 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
964 select: count(a.id)
965 )
966 |> Repo.one()
967
968 user
969 |> cast(%{note_count: note_count}, [:note_count])
970 |> update_and_set_cache()
971 end
972
973 @spec maybe_fetch_follow_information(User.t()) :: User.t()
974 def maybe_fetch_follow_information(user) do
975 with {:ok, user} <- fetch_follow_information(user) do
976 user
977 else
978 e ->
979 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
980
981 user
982 end
983 end
984
985 def fetch_follow_information(user) do
986 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
987 user
988 |> follow_information_changeset(info)
989 |> update_and_set_cache()
990 end
991 end
992
993 defp follow_information_changeset(user, params) do
994 user
995 |> cast(params, [
996 :hide_followers,
997 :hide_follows,
998 :follower_count,
999 :following_count,
1000 :hide_followers_count,
1001 :hide_follows_count
1002 ])
1003 end
1004
1005 def update_follower_count(%User{} = user) do
1006 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
1007 follower_count_query =
1008 User.Query.build(%{followers: user, deactivated: false})
1009 |> select([u], %{count: count(u.id)})
1010
1011 User
1012 |> where(id: ^user.id)
1013 |> join(:inner, [u], s in subquery(follower_count_query))
1014 |> update([u, s],
1015 set: [follower_count: s.count]
1016 )
1017 |> select([u], u)
1018 |> Repo.update_all([])
1019 |> case do
1020 {1, [user]} -> set_cache(user)
1021 _ -> {:error, user}
1022 end
1023 else
1024 {:ok, maybe_fetch_follow_information(user)}
1025 end
1026 end
1027
1028 @spec update_following_count(User.t()) :: User.t()
1029 def update_following_count(%User{local: false} = user) do
1030 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1031 maybe_fetch_follow_information(user)
1032 else
1033 user
1034 end
1035 end
1036
1037 def update_following_count(%User{local: true} = user) do
1038 following_count = FollowingRelationship.following_count(user)
1039
1040 user
1041 |> follow_information_changeset(%{following_count: following_count})
1042 |> Repo.update!()
1043 end
1044
1045 def set_unread_conversation_count(%User{local: true} = user) do
1046 unread_query = Participation.unread_conversation_count_for_user(user)
1047
1048 User
1049 |> join(:inner, [u], p in subquery(unread_query))
1050 |> update([u, p],
1051 set: [unread_conversation_count: p.count]
1052 )
1053 |> where([u], u.id == ^user.id)
1054 |> select([u], u)
1055 |> Repo.update_all([])
1056 |> case do
1057 {1, [user]} -> set_cache(user)
1058 _ -> {:error, user}
1059 end
1060 end
1061
1062 def set_unread_conversation_count(user), do: {:ok, user}
1063
1064 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1065 unread_query =
1066 Participation.unread_conversation_count_for_user(user)
1067 |> where([p], p.conversation_id == ^conversation.id)
1068
1069 User
1070 |> join(:inner, [u], p in subquery(unread_query))
1071 |> update([u, p],
1072 inc: [unread_conversation_count: 1]
1073 )
1074 |> where([u], u.id == ^user.id)
1075 |> where([u, p], p.count == 0)
1076 |> select([u], u)
1077 |> Repo.update_all([])
1078 |> case do
1079 {1, [user]} -> set_cache(user)
1080 _ -> {:error, user}
1081 end
1082 end
1083
1084 def increment_unread_conversation_count(_, user), do: {:ok, user}
1085
1086 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1087 def get_users_from_set(ap_ids, local_only \\ true) do
1088 criteria = %{ap_id: ap_ids, deactivated: false}
1089 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1090
1091 User.Query.build(criteria)
1092 |> Repo.all()
1093 end
1094
1095 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1096 def get_recipients_from_activity(%Activity{recipients: to}) do
1097 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1098 |> Repo.all()
1099 end
1100
1101 @spec mute(User.t(), User.t(), boolean()) ::
1102 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1103 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1104 add_to_mutes(muter, mutee, notifications?)
1105 end
1106
1107 def unmute(%User{} = muter, %User{} = mutee) do
1108 remove_from_mutes(muter, mutee)
1109 end
1110
1111 def subscribe(%User{} = subscriber, %User{} = target) do
1112 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1113
1114 if blocks?(target, subscriber) and deny_follow_blocked do
1115 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1116 else
1117 # Note: the relationship is inverse: subscriber acts as relationship target
1118 UserRelationship.create_inverse_subscription(target, subscriber)
1119 end
1120 end
1121
1122 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1123 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1124 subscribe(subscriber, subscribee)
1125 end
1126 end
1127
1128 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1129 # Note: the relationship is inverse: subscriber acts as relationship target
1130 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1131 end
1132
1133 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1134 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1135 unsubscribe(unsubscriber, user)
1136 end
1137 end
1138
1139 def block(%User{} = blocker, %User{} = blocked) do
1140 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1141 blocker =
1142 if following?(blocker, blocked) do
1143 {:ok, blocker, _} = unfollow(blocker, blocked)
1144 blocker
1145 else
1146 blocker
1147 end
1148
1149 # clear any requested follows as well
1150 blocked =
1151 case CommonAPI.reject_follow_request(blocked, blocker) do
1152 {:ok, %User{} = updated_blocked} -> updated_blocked
1153 nil -> blocked
1154 end
1155
1156 unsubscribe(blocked, blocker)
1157
1158 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1159
1160 {:ok, blocker} = update_follower_count(blocker)
1161 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1162 add_to_block(blocker, blocked)
1163 end
1164
1165 # helper to handle the block given only an actor's AP id
1166 def block(%User{} = blocker, %{ap_id: ap_id}) do
1167 block(blocker, get_cached_by_ap_id(ap_id))
1168 end
1169
1170 def unblock(%User{} = blocker, %User{} = blocked) do
1171 remove_from_block(blocker, blocked)
1172 end
1173
1174 # helper to handle the block given only an actor's AP id
1175 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1176 unblock(blocker, get_cached_by_ap_id(ap_id))
1177 end
1178
1179 def mutes?(nil, _), do: false
1180 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1181
1182 def mutes_user?(%User{} = user, %User{} = target) do
1183 UserRelationship.mute_exists?(user, target)
1184 end
1185
1186 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1187 def muted_notifications?(nil, _), do: false
1188
1189 def muted_notifications?(%User{} = user, %User{} = target),
1190 do: UserRelationship.notification_mute_exists?(user, target)
1191
1192 def blocks?(nil, _), do: false
1193
1194 def blocks?(%User{} = user, %User{} = target) do
1195 blocks_user?(user, target) ||
1196 (!User.following?(user, target) && blocks_domain?(user, target))
1197 end
1198
1199 def blocks_user?(%User{} = user, %User{} = target) do
1200 UserRelationship.block_exists?(user, target)
1201 end
1202
1203 def blocks_user?(_, _), do: false
1204
1205 def blocks_domain?(%User{} = user, %User{} = target) do
1206 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1207 %{host: host} = URI.parse(target.ap_id)
1208 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1209 end
1210
1211 def blocks_domain?(_, _), do: false
1212
1213 def subscribed_to?(%User{} = user, %User{} = target) do
1214 # Note: the relationship is inverse: subscriber acts as relationship target
1215 UserRelationship.inverse_subscription_exists?(target, user)
1216 end
1217
1218 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1219 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1220 subscribed_to?(user, target)
1221 end
1222 end
1223
1224 @doc """
1225 Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
1226 E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1227 """
1228 @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1229 def outgoing_relations_ap_ids(_, []), do: %{}
1230
1231 def outgoing_relations_ap_ids(%User{} = user, relationship_types)
1232 when is_list(relationship_types) do
1233 db_result =
1234 user
1235 |> assoc(:outgoing_relationships)
1236 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1237 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1238 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1239 |> group_by([user_rel, u], user_rel.relationship_type)
1240 |> Repo.all()
1241 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1242
1243 Enum.into(
1244 relationship_types,
1245 %{},
1246 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1247 )
1248 end
1249
1250 def deactivate_async(user, status \\ true) do
1251 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1252 end
1253
1254 def deactivate(user, status \\ true)
1255
1256 def deactivate(users, status) when is_list(users) do
1257 Repo.transaction(fn ->
1258 for user <- users, do: deactivate(user, status)
1259 end)
1260 end
1261
1262 def deactivate(%User{} = user, status) do
1263 with {:ok, user} <- set_activation_status(user, status) do
1264 user
1265 |> get_followers()
1266 |> Enum.filter(& &1.local)
1267 |> Enum.each(fn follower ->
1268 follower |> update_following_count() |> set_cache()
1269 end)
1270
1271 # Only update local user counts, remote will be update during the next pull.
1272 user
1273 |> get_friends()
1274 |> Enum.filter(& &1.local)
1275 |> Enum.each(&update_follower_count/1)
1276
1277 {:ok, user}
1278 end
1279 end
1280
1281 def update_notification_settings(%User{} = user, settings) do
1282 user
1283 |> cast(%{notification_settings: settings}, [])
1284 |> cast_embed(:notification_settings)
1285 |> validate_required([:notification_settings])
1286 |> update_and_set_cache()
1287 end
1288
1289 def delete(users) when is_list(users) do
1290 for user <- users, do: delete(user)
1291 end
1292
1293 def delete(%User{} = user) do
1294 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1295 end
1296
1297 def perform(:force_password_reset, user), do: force_password_reset(user)
1298
1299 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1300 def perform(:delete, %User{} = user) do
1301 {:ok, _user} = ActivityPub.delete(user)
1302
1303 # Remove all relationships
1304 user
1305 |> get_followers()
1306 |> Enum.each(fn follower ->
1307 ActivityPub.unfollow(follower, user)
1308 unfollow(follower, user)
1309 end)
1310
1311 user
1312 |> get_friends()
1313 |> Enum.each(fn followed ->
1314 ActivityPub.unfollow(user, followed)
1315 unfollow(user, followed)
1316 end)
1317
1318 delete_user_activities(user)
1319 invalidate_cache(user)
1320 Repo.delete(user)
1321 end
1322
1323 def perform(:fetch_initial_posts, %User{} = user) do
1324 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1325
1326 # Insert all the posts in reverse order, so they're in the right order on the timeline
1327 user.source_data["outbox"]
1328 |> Utils.fetch_ordered_collection(pages)
1329 |> Enum.reverse()
1330 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1331 end
1332
1333 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1334
1335 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1336 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1337 when is_list(blocked_identifiers) do
1338 Enum.map(
1339 blocked_identifiers,
1340 fn blocked_identifier ->
1341 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1342 {:ok, _user_block} <- block(blocker, blocked),
1343 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1344 blocked
1345 else
1346 err ->
1347 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1348 err
1349 end
1350 end
1351 )
1352 end
1353
1354 def perform(:follow_import, %User{} = follower, followed_identifiers)
1355 when is_list(followed_identifiers) do
1356 Enum.map(
1357 followed_identifiers,
1358 fn followed_identifier ->
1359 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1360 {:ok, follower} <- maybe_direct_follow(follower, followed),
1361 {:ok, _} <- ActivityPub.follow(follower, followed) do
1362 followed
1363 else
1364 err ->
1365 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1366 err
1367 end
1368 end
1369 )
1370 end
1371
1372 @spec external_users_query() :: Ecto.Query.t()
1373 def external_users_query do
1374 User.Query.build(%{
1375 external: true,
1376 active: true,
1377 order_by: :id
1378 })
1379 end
1380
1381 @spec external_users(keyword()) :: [User.t()]
1382 def external_users(opts \\ []) do
1383 query =
1384 external_users_query()
1385 |> select([u], struct(u, [:id, :ap_id]))
1386
1387 query =
1388 if opts[:max_id],
1389 do: where(query, [u], u.id > ^opts[:max_id]),
1390 else: query
1391
1392 query =
1393 if opts[:limit],
1394 do: limit(query, ^opts[:limit]),
1395 else: query
1396
1397 Repo.all(query)
1398 end
1399
1400 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1401 BackgroundWorker.enqueue("blocks_import", %{
1402 "blocker_id" => blocker.id,
1403 "blocked_identifiers" => blocked_identifiers
1404 })
1405 end
1406
1407 def follow_import(%User{} = follower, followed_identifiers)
1408 when is_list(followed_identifiers) do
1409 BackgroundWorker.enqueue("follow_import", %{
1410 "follower_id" => follower.id,
1411 "followed_identifiers" => followed_identifiers
1412 })
1413 end
1414
1415 def delete_user_activities(%User{ap_id: ap_id}) do
1416 ap_id
1417 |> Activity.Queries.by_actor()
1418 |> RepoStreamer.chunk_stream(50)
1419 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1420 |> Stream.run()
1421 end
1422
1423 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1424 activity
1425 |> Object.normalize()
1426 |> ActivityPub.delete()
1427 end
1428
1429 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1430 object = Object.normalize(activity)
1431
1432 activity.actor
1433 |> get_cached_by_ap_id()
1434 |> ActivityPub.unlike(object)
1435 end
1436
1437 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1438 object = Object.normalize(activity)
1439
1440 activity.actor
1441 |> get_cached_by_ap_id()
1442 |> ActivityPub.unannounce(object)
1443 end
1444
1445 defp delete_activity(_activity), do: "Doing nothing"
1446
1447 def html_filter_policy(%User{no_rich_text: true}) do
1448 Pleroma.HTML.Scrubber.TwitterText
1449 end
1450
1451 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1452
1453 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1454
1455 def get_or_fetch_by_ap_id(ap_id) do
1456 user = get_cached_by_ap_id(ap_id)
1457
1458 if !is_nil(user) and !needs_update?(user) do
1459 {:ok, user}
1460 else
1461 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1462 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1463
1464 resp = fetch_by_ap_id(ap_id)
1465
1466 if should_fetch_initial do
1467 with {:ok, %User{} = user} <- resp do
1468 fetch_initial_posts(user)
1469 end
1470 end
1471
1472 resp
1473 end
1474 end
1475
1476 @doc """
1477 Creates an internal service actor by URI if missing.
1478 Optionally takes nickname for addressing.
1479 """
1480 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1481 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1482 {_, user} =
1483 case get_cached_by_ap_id(uri) do
1484 nil ->
1485 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1486 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1487 {:error, nil}
1488 end
1489
1490 %User{invisible: false} = user ->
1491 set_invisible(user)
1492
1493 user ->
1494 {:ok, user}
1495 end
1496
1497 user
1498 end
1499
1500 @spec set_invisible(User.t()) :: {:ok, User.t()}
1501 defp set_invisible(user) do
1502 user
1503 |> change(%{invisible: true})
1504 |> update_and_set_cache()
1505 end
1506
1507 @spec create_service_actor(String.t(), String.t()) ::
1508 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1509 defp create_service_actor(uri, nickname) do
1510 %User{
1511 invisible: true,
1512 local: true,
1513 ap_id: uri,
1514 nickname: nickname,
1515 follower_address: uri <> "/followers"
1516 }
1517 |> change
1518 |> unique_constraint(:nickname)
1519 |> Repo.insert()
1520 |> set_cache()
1521 end
1522
1523 # AP style
1524 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1525 key =
1526 public_key_pem
1527 |> :public_key.pem_decode()
1528 |> hd()
1529 |> :public_key.pem_entry_decode()
1530
1531 {:ok, key}
1532 end
1533
1534 def public_key(_), do: {:error, "not found key"}
1535
1536 def get_public_key_for_ap_id(ap_id) do
1537 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1538 {:ok, public_key} <- public_key(user) do
1539 {:ok, public_key}
1540 else
1541 _ -> :error
1542 end
1543 end
1544
1545 defp blank?(""), do: nil
1546 defp blank?(n), do: n
1547
1548 def insert_or_update_user(data) do
1549 data
1550 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1551 |> remote_user_creation()
1552 |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
1553 |> set_cache()
1554 end
1555
1556 def ap_enabled?(%User{local: true}), do: true
1557 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1558 def ap_enabled?(_), do: false
1559
1560 @doc "Gets or fetch a user by uri or nickname."
1561 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1562 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1563 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1564
1565 # wait a period of time and return newest version of the User structs
1566 # this is because we have synchronous follow APIs and need to simulate them
1567 # with an async handshake
1568 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1569 with %User{} = a <- get_cached_by_id(a.id),
1570 %User{} = b <- get_cached_by_id(b.id) do
1571 {:ok, a, b}
1572 else
1573 nil -> :error
1574 end
1575 end
1576
1577 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1578 with :ok <- :timer.sleep(timeout),
1579 %User{} = a <- get_cached_by_id(a.id),
1580 %User{} = b <- get_cached_by_id(b.id) do
1581 {:ok, a, b}
1582 else
1583 nil -> :error
1584 end
1585 end
1586
1587 def parse_bio(bio) when is_binary(bio) and bio != "" do
1588 bio
1589 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1590 |> elem(0)
1591 end
1592
1593 def parse_bio(_), do: ""
1594
1595 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1596 # TODO: get profile URLs other than user.ap_id
1597 profile_urls = [user.ap_id]
1598
1599 bio
1600 |> CommonUtils.format_input("text/plain",
1601 mentions_format: :full,
1602 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1603 )
1604 |> elem(0)
1605 end
1606
1607 def parse_bio(_, _), do: ""
1608
1609 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1610 Repo.transaction(fn ->
1611 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1612 end)
1613 end
1614
1615 def tag(nickname, tags) when is_binary(nickname),
1616 do: tag(get_by_nickname(nickname), tags)
1617
1618 def tag(%User{} = user, tags),
1619 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1620
1621 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1622 Repo.transaction(fn ->
1623 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1624 end)
1625 end
1626
1627 def untag(nickname, tags) when is_binary(nickname),
1628 do: untag(get_by_nickname(nickname), tags)
1629
1630 def untag(%User{} = user, tags),
1631 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1632
1633 defp update_tags(%User{} = user, new_tags) do
1634 {:ok, updated_user} =
1635 user
1636 |> change(%{tags: new_tags})
1637 |> update_and_set_cache()
1638
1639 updated_user
1640 end
1641
1642 defp normalize_tags(tags) do
1643 [tags]
1644 |> List.flatten()
1645 |> Enum.map(&String.downcase/1)
1646 end
1647
1648 defp local_nickname_regex do
1649 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1650 @extended_local_nickname_regex
1651 else
1652 @strict_local_nickname_regex
1653 end
1654 end
1655
1656 def local_nickname(nickname_or_mention) do
1657 nickname_or_mention
1658 |> full_nickname()
1659 |> String.split("@")
1660 |> hd()
1661 end
1662
1663 def full_nickname(nickname_or_mention),
1664 do: String.trim_leading(nickname_or_mention, "@")
1665
1666 def error_user(ap_id) do
1667 %User{
1668 name: ap_id,
1669 ap_id: ap_id,
1670 nickname: "erroruser@example.com",
1671 inserted_at: NaiveDateTime.utc_now()
1672 }
1673 end
1674
1675 @spec all_superusers() :: [User.t()]
1676 def all_superusers do
1677 User.Query.build(%{super_users: true, local: true, deactivated: false})
1678 |> Repo.all()
1679 end
1680
1681 def showing_reblogs?(%User{} = user, %User{} = target) do
1682 not UserRelationship.reblog_mute_exists?(user, target)
1683 end
1684
1685 @doc """
1686 The function returns a query to get users with no activity for given interval of days.
1687 Inactive users are those who didn't read any notification, or had any activity where
1688 the user is the activity's actor, during `inactivity_threshold` days.
1689 Deactivated users will not appear in this list.
1690
1691 ## Examples
1692
1693 iex> Pleroma.User.list_inactive_users()
1694 %Ecto.Query{}
1695 """
1696 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1697 def list_inactive_users_query(inactivity_threshold \\ 7) do
1698 negative_inactivity_threshold = -inactivity_threshold
1699 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1700 # Subqueries are not supported in `where` clauses, join gets too complicated.
1701 has_read_notifications =
1702 from(n in Pleroma.Notification,
1703 where: n.seen == true,
1704 group_by: n.id,
1705 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1706 select: n.user_id
1707 )
1708 |> Pleroma.Repo.all()
1709
1710 from(u in Pleroma.User,
1711 left_join: a in Pleroma.Activity,
1712 on: u.ap_id == a.actor,
1713 where: not is_nil(u.nickname),
1714 where: u.deactivated != ^true,
1715 where: u.id not in ^has_read_notifications,
1716 group_by: u.id,
1717 having:
1718 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1719 is_nil(max(a.inserted_at))
1720 )
1721 end
1722
1723 @doc """
1724 Enable or disable email notifications for user
1725
1726 ## Examples
1727
1728 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1729 Pleroma.User{email_notifications: %{"digest" => true}}
1730
1731 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1732 Pleroma.User{email_notifications: %{"digest" => false}}
1733 """
1734 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1735 {:ok, t()} | {:error, Ecto.Changeset.t()}
1736 def switch_email_notifications(user, type, status) do
1737 User.update_email_notifications(user, %{type => status})
1738 end
1739
1740 @doc """
1741 Set `last_digest_emailed_at` value for the user to current time
1742 """
1743 @spec touch_last_digest_emailed_at(t()) :: t()
1744 def touch_last_digest_emailed_at(user) do
1745 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1746
1747 {:ok, updated_user} =
1748 user
1749 |> change(%{last_digest_emailed_at: now})
1750 |> update_and_set_cache()
1751
1752 updated_user
1753 end
1754
1755 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1756 def toggle_confirmation(%User{} = user) do
1757 user
1758 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1759 |> update_and_set_cache()
1760 end
1761
1762 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1763 def toggle_confirmation(users) do
1764 Enum.map(users, &toggle_confirmation/1)
1765 end
1766
1767 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1768 mascot
1769 end
1770
1771 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1772 # use instance-default
1773 config = Pleroma.Config.get([:assets, :mascots])
1774 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1775 mascot = Keyword.get(config, default_mascot)
1776
1777 %{
1778 "id" => "default-mascot",
1779 "url" => mascot[:url],
1780 "preview_url" => mascot[:url],
1781 "pleroma" => %{
1782 "mime_type" => mascot[:mime_type]
1783 }
1784 }
1785 end
1786
1787 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1788
1789 def ensure_keys_present(%User{} = user) do
1790 with {:ok, pem} <- Keys.generate_rsa_pem() do
1791 user
1792 |> cast(%{keys: pem}, [:keys])
1793 |> validate_required([:keys])
1794 |> update_and_set_cache()
1795 end
1796 end
1797
1798 def get_ap_ids_by_nicknames(nicknames) do
1799 from(u in User,
1800 where: u.nickname in ^nicknames,
1801 select: u.ap_id
1802 )
1803 |> Repo.all()
1804 end
1805
1806 defdelegate search(query, opts \\ []), to: User.Search
1807
1808 defp put_password_hash(
1809 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1810 ) do
1811 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1812 end
1813
1814 defp put_password_hash(changeset), do: changeset
1815
1816 def is_internal_user?(%User{nickname: nil}), do: true
1817 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1818 def is_internal_user?(_), do: false
1819
1820 # A hack because user delete activities have a fake id for whatever reason
1821 # TODO: Get rid of this
1822 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1823
1824 def get_delivered_users_by_object_id(object_id) do
1825 from(u in User,
1826 inner_join: delivery in assoc(u, :deliveries),
1827 where: delivery.object_id == ^object_id
1828 )
1829 |> Repo.all()
1830 end
1831
1832 def change_email(user, email) do
1833 user
1834 |> cast(%{email: email}, [:email])
1835 |> validate_required([:email])
1836 |> unique_constraint(:email)
1837 |> validate_format(:email, @email_regex)
1838 |> update_and_set_cache()
1839 end
1840
1841 # Internal function; public one is `deactivate/2`
1842 defp set_activation_status(user, deactivated) do
1843 user
1844 |> cast(%{deactivated: deactivated}, [:deactivated])
1845 |> update_and_set_cache()
1846 end
1847
1848 def update_banner(user, banner) do
1849 user
1850 |> cast(%{banner: banner}, [:banner])
1851 |> update_and_set_cache()
1852 end
1853
1854 def update_background(user, background) do
1855 user
1856 |> cast(%{background: background}, [:background])
1857 |> update_and_set_cache()
1858 end
1859
1860 def update_source_data(user, source_data) do
1861 user
1862 |> cast(%{source_data: source_data}, [:source_data])
1863 |> update_and_set_cache()
1864 end
1865
1866 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1867 %{
1868 admin: is_admin,
1869 moderator: is_moderator
1870 }
1871 end
1872
1873 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1874 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1875 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1876 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1877
1878 attachment
1879 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1880 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1881 |> Enum.take(limit)
1882 end
1883
1884 def fields(%{fields: nil}), do: []
1885
1886 def fields(%{fields: fields}), do: fields
1887
1888 def validate_fields(changeset, remote? \\ false) do
1889 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1890 limit = Pleroma.Config.get([:instance, limit_name], 0)
1891
1892 changeset
1893 |> validate_length(:fields, max: limit)
1894 |> validate_change(:fields, fn :fields, fields ->
1895 if Enum.all?(fields, &valid_field?/1) do
1896 []
1897 else
1898 [fields: "invalid"]
1899 end
1900 end)
1901 end
1902
1903 defp valid_field?(%{"name" => name, "value" => value}) do
1904 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1905 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1906
1907 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1908 String.length(value) <= value_limit
1909 end
1910
1911 defp valid_field?(_), do: false
1912
1913 defp truncate_field(%{"name" => name, "value" => value}) do
1914 {name, _chopped} =
1915 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1916
1917 {value, _chopped} =
1918 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1919
1920 %{"name" => name, "value" => value}
1921 end
1922
1923 def admin_api_update(user, params) do
1924 user
1925 |> cast(params, [
1926 :is_moderator,
1927 :is_admin,
1928 :show_role
1929 ])
1930 |> update_and_set_cache()
1931 end
1932
1933 @doc "Signs user out of all applications"
1934 def global_sign_out(user) do
1935 OAuth.Authorization.delete_user_authorizations(user)
1936 OAuth.Token.delete_user_tokens(user)
1937 end
1938
1939 def mascot_update(user, url) do
1940 user
1941 |> cast(%{mascot: url}, [:mascot])
1942 |> validate_required([:mascot])
1943 |> update_and_set_cache()
1944 end
1945
1946 def mastodon_settings_update(user, settings) do
1947 user
1948 |> cast(%{settings: settings}, [:settings])
1949 |> validate_required([:settings])
1950 |> update_and_set_cache()
1951 end
1952
1953 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1954 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1955 params =
1956 if need_confirmation? do
1957 %{
1958 confirmation_pending: true,
1959 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1960 }
1961 else
1962 %{
1963 confirmation_pending: false,
1964 confirmation_token: nil
1965 }
1966 end
1967
1968 cast(user, params, [:confirmation_pending, :confirmation_token])
1969 end
1970
1971 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1972 if id not in user.pinned_activities do
1973 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1974 params = %{pinned_activities: user.pinned_activities ++ [id]}
1975
1976 user
1977 |> cast(params, [:pinned_activities])
1978 |> validate_length(:pinned_activities,
1979 max: max_pinned_statuses,
1980 message: "You have already pinned the maximum number of statuses"
1981 )
1982 else
1983 change(user)
1984 end
1985 |> update_and_set_cache()
1986 end
1987
1988 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1989 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1990
1991 user
1992 |> cast(params, [:pinned_activities])
1993 |> update_and_set_cache()
1994 end
1995
1996 def update_email_notifications(user, settings) do
1997 email_notifications =
1998 user.email_notifications
1999 |> Map.merge(settings)
2000 |> Map.take(["digest"])
2001
2002 params = %{email_notifications: email_notifications}
2003 fields = [:email_notifications]
2004
2005 user
2006 |> cast(params, fields)
2007 |> validate_required(fields)
2008 |> update_and_set_cache()
2009 end
2010
2011 defp set_domain_blocks(user, domain_blocks) do
2012 params = %{domain_blocks: domain_blocks}
2013
2014 user
2015 |> cast(params, [:domain_blocks])
2016 |> validate_required([:domain_blocks])
2017 |> update_and_set_cache()
2018 end
2019
2020 def block_domain(user, domain_blocked) do
2021 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2022 end
2023
2024 def unblock_domain(user, domain_blocked) do
2025 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2026 end
2027
2028 @spec add_to_block(User.t(), User.t()) ::
2029 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2030 defp add_to_block(%User{} = user, %User{} = blocked) do
2031 UserRelationship.create_block(user, blocked)
2032 end
2033
2034 @spec add_to_block(User.t(), User.t()) ::
2035 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2036 defp remove_from_block(%User{} = user, %User{} = blocked) do
2037 UserRelationship.delete_block(user, blocked)
2038 end
2039
2040 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2041 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2042 {:ok, user_notification_mute} <-
2043 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2044 {:ok, nil} do
2045 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2046 end
2047 end
2048
2049 defp remove_from_mutes(user, %User{} = muted_user) do
2050 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2051 {:ok, user_notification_mute} <-
2052 UserRelationship.delete_notification_mute(user, muted_user) do
2053 {:ok, [user_mute, user_notification_mute]}
2054 end
2055 end
2056
2057 def set_invisible(user, invisible) do
2058 params = %{invisible: invisible}
2059
2060 user
2061 |> cast(params, [:invisible])
2062 |> validate_required([:invisible])
2063 |> update_and_set_cache()
2064 end
2065 end