rip out fetch_initial_posts
[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 {:ok, user}
843 else
844 _e -> {:error, "not found " <> nickname}
845 end
846 end
847 end
848
849 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
850 def get_followers_query(%User{} = user, nil) do
851 User.Query.build(%{followers: user, deactivated: false})
852 end
853
854 def get_followers_query(user, page) do
855 user
856 |> get_followers_query(nil)
857 |> User.Query.paginate(page, 20)
858 end
859
860 @spec get_followers_query(User.t()) :: Ecto.Query.t()
861 def get_followers_query(user), do: get_followers_query(user, nil)
862
863 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
864 def get_followers(user, page \\ nil) do
865 user
866 |> get_followers_query(page)
867 |> Repo.all()
868 end
869
870 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
871 def get_external_followers(user, page \\ nil) do
872 user
873 |> get_followers_query(page)
874 |> User.Query.build(%{external: true})
875 |> Repo.all()
876 end
877
878 def get_followers_ids(user, page \\ nil) do
879 user
880 |> get_followers_query(page)
881 |> select([u], u.id)
882 |> Repo.all()
883 end
884
885 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
886 def get_friends_query(%User{} = user, nil) do
887 User.Query.build(%{friends: user, deactivated: false})
888 end
889
890 def get_friends_query(user, page) do
891 user
892 |> get_friends_query(nil)
893 |> User.Query.paginate(page, 20)
894 end
895
896 @spec get_friends_query(User.t()) :: Ecto.Query.t()
897 def get_friends_query(user), do: get_friends_query(user, nil)
898
899 def get_friends(user, page \\ nil) do
900 user
901 |> get_friends_query(page)
902 |> Repo.all()
903 end
904
905 def get_friends_ap_ids(user) do
906 user
907 |> get_friends_query(nil)
908 |> select([u], u.ap_id)
909 |> Repo.all()
910 end
911
912 def get_friends_ids(user, page \\ nil) do
913 user
914 |> get_friends_query(page)
915 |> select([u], u.id)
916 |> Repo.all()
917 end
918
919 defdelegate get_follow_requests(user), to: FollowingRelationship
920
921 def increase_note_count(%User{} = user) do
922 User
923 |> where(id: ^user.id)
924 |> update([u], inc: [note_count: 1])
925 |> select([u], u)
926 |> Repo.update_all([])
927 |> case do
928 {1, [user]} -> set_cache(user)
929 _ -> {:error, user}
930 end
931 end
932
933 def decrease_note_count(%User{} = user) do
934 User
935 |> where(id: ^user.id)
936 |> update([u],
937 set: [
938 note_count: fragment("greatest(0, note_count - 1)")
939 ]
940 )
941 |> select([u], u)
942 |> Repo.update_all([])
943 |> case do
944 {1, [user]} -> set_cache(user)
945 _ -> {:error, user}
946 end
947 end
948
949 def update_note_count(%User{} = user, note_count \\ nil) do
950 note_count =
951 note_count ||
952 from(
953 a in Object,
954 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
955 select: count(a.id)
956 )
957 |> Repo.one()
958
959 user
960 |> cast(%{note_count: note_count}, [:note_count])
961 |> update_and_set_cache()
962 end
963
964 @spec maybe_fetch_follow_information(User.t()) :: User.t()
965 def maybe_fetch_follow_information(user) do
966 with {:ok, user} <- fetch_follow_information(user) do
967 user
968 else
969 e ->
970 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
971
972 user
973 end
974 end
975
976 def fetch_follow_information(user) do
977 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
978 user
979 |> follow_information_changeset(info)
980 |> update_and_set_cache()
981 end
982 end
983
984 defp follow_information_changeset(user, params) do
985 user
986 |> cast(params, [
987 :hide_followers,
988 :hide_follows,
989 :follower_count,
990 :following_count,
991 :hide_followers_count,
992 :hide_follows_count
993 ])
994 end
995
996 def update_follower_count(%User{} = user) do
997 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
998 follower_count_query =
999 User.Query.build(%{followers: user, deactivated: false})
1000 |> select([u], %{count: count(u.id)})
1001
1002 User
1003 |> where(id: ^user.id)
1004 |> join(:inner, [u], s in subquery(follower_count_query))
1005 |> update([u, s],
1006 set: [follower_count: s.count]
1007 )
1008 |> select([u], u)
1009 |> Repo.update_all([])
1010 |> case do
1011 {1, [user]} -> set_cache(user)
1012 _ -> {:error, user}
1013 end
1014 else
1015 {:ok, maybe_fetch_follow_information(user)}
1016 end
1017 end
1018
1019 @spec update_following_count(User.t()) :: User.t()
1020 def update_following_count(%User{local: false} = user) do
1021 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1022 maybe_fetch_follow_information(user)
1023 else
1024 user
1025 end
1026 end
1027
1028 def update_following_count(%User{local: true} = user) do
1029 following_count = FollowingRelationship.following_count(user)
1030
1031 user
1032 |> follow_information_changeset(%{following_count: following_count})
1033 |> Repo.update!()
1034 end
1035
1036 def set_unread_conversation_count(%User{local: true} = user) do
1037 unread_query = Participation.unread_conversation_count_for_user(user)
1038
1039 User
1040 |> join(:inner, [u], p in subquery(unread_query))
1041 |> update([u, p],
1042 set: [unread_conversation_count: p.count]
1043 )
1044 |> where([u], u.id == ^user.id)
1045 |> select([u], u)
1046 |> Repo.update_all([])
1047 |> case do
1048 {1, [user]} -> set_cache(user)
1049 _ -> {:error, user}
1050 end
1051 end
1052
1053 def set_unread_conversation_count(user), do: {:ok, user}
1054
1055 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1056 unread_query =
1057 Participation.unread_conversation_count_for_user(user)
1058 |> where([p], p.conversation_id == ^conversation.id)
1059
1060 User
1061 |> join(:inner, [u], p in subquery(unread_query))
1062 |> update([u, p],
1063 inc: [unread_conversation_count: 1]
1064 )
1065 |> where([u], u.id == ^user.id)
1066 |> where([u, p], p.count == 0)
1067 |> select([u], u)
1068 |> Repo.update_all([])
1069 |> case do
1070 {1, [user]} -> set_cache(user)
1071 _ -> {:error, user}
1072 end
1073 end
1074
1075 def increment_unread_conversation_count(_, user), do: {:ok, user}
1076
1077 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1078 def get_users_from_set(ap_ids, local_only \\ true) do
1079 criteria = %{ap_id: ap_ids, deactivated: false}
1080 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1081
1082 User.Query.build(criteria)
1083 |> Repo.all()
1084 end
1085
1086 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1087 def get_recipients_from_activity(%Activity{recipients: to}) do
1088 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1089 |> Repo.all()
1090 end
1091
1092 @spec mute(User.t(), User.t(), boolean()) ::
1093 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1094 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1095 add_to_mutes(muter, mutee, notifications?)
1096 end
1097
1098 def unmute(%User{} = muter, %User{} = mutee) do
1099 remove_from_mutes(muter, mutee)
1100 end
1101
1102 def subscribe(%User{} = subscriber, %User{} = target) do
1103 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1104
1105 if blocks?(target, subscriber) and deny_follow_blocked do
1106 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1107 else
1108 # Note: the relationship is inverse: subscriber acts as relationship target
1109 UserRelationship.create_inverse_subscription(target, subscriber)
1110 end
1111 end
1112
1113 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1114 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1115 subscribe(subscriber, subscribee)
1116 end
1117 end
1118
1119 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1120 # Note: the relationship is inverse: subscriber acts as relationship target
1121 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1122 end
1123
1124 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1125 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1126 unsubscribe(unsubscriber, user)
1127 end
1128 end
1129
1130 def block(%User{} = blocker, %User{} = blocked) do
1131 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1132 blocker =
1133 if following?(blocker, blocked) do
1134 {:ok, blocker, _} = unfollow(blocker, blocked)
1135 blocker
1136 else
1137 blocker
1138 end
1139
1140 # clear any requested follows as well
1141 blocked =
1142 case CommonAPI.reject_follow_request(blocked, blocker) do
1143 {:ok, %User{} = updated_blocked} -> updated_blocked
1144 nil -> blocked
1145 end
1146
1147 unsubscribe(blocked, blocker)
1148
1149 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1150
1151 {:ok, blocker} = update_follower_count(blocker)
1152 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1153 add_to_block(blocker, blocked)
1154 end
1155
1156 # helper to handle the block given only an actor's AP id
1157 def block(%User{} = blocker, %{ap_id: ap_id}) do
1158 block(blocker, get_cached_by_ap_id(ap_id))
1159 end
1160
1161 def unblock(%User{} = blocker, %User{} = blocked) do
1162 remove_from_block(blocker, blocked)
1163 end
1164
1165 # helper to handle the block given only an actor's AP id
1166 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1167 unblock(blocker, get_cached_by_ap_id(ap_id))
1168 end
1169
1170 def mutes?(nil, _), do: false
1171 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1172
1173 def mutes_user?(%User{} = user, %User{} = target) do
1174 UserRelationship.mute_exists?(user, target)
1175 end
1176
1177 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1178 def muted_notifications?(nil, _), do: false
1179
1180 def muted_notifications?(%User{} = user, %User{} = target),
1181 do: UserRelationship.notification_mute_exists?(user, target)
1182
1183 def blocks?(nil, _), do: false
1184
1185 def blocks?(%User{} = user, %User{} = target) do
1186 blocks_user?(user, target) ||
1187 (!User.following?(user, target) && blocks_domain?(user, target))
1188 end
1189
1190 def blocks_user?(%User{} = user, %User{} = target) do
1191 UserRelationship.block_exists?(user, target)
1192 end
1193
1194 def blocks_user?(_, _), do: false
1195
1196 def blocks_domain?(%User{} = user, %User{} = target) do
1197 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1198 %{host: host} = URI.parse(target.ap_id)
1199 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1200 end
1201
1202 def blocks_domain?(_, _), do: false
1203
1204 def subscribed_to?(%User{} = user, %User{} = target) do
1205 # Note: the relationship is inverse: subscriber acts as relationship target
1206 UserRelationship.inverse_subscription_exists?(target, user)
1207 end
1208
1209 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1210 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1211 subscribed_to?(user, target)
1212 end
1213 end
1214
1215 @doc """
1216 Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
1217 E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1218 """
1219 @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1220 def outgoing_relations_ap_ids(_, []), do: %{}
1221
1222 def outgoing_relations_ap_ids(%User{} = user, relationship_types)
1223 when is_list(relationship_types) do
1224 db_result =
1225 user
1226 |> assoc(:outgoing_relationships)
1227 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1228 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1229 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1230 |> group_by([user_rel, u], user_rel.relationship_type)
1231 |> Repo.all()
1232 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1233
1234 Enum.into(
1235 relationship_types,
1236 %{},
1237 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1238 )
1239 end
1240
1241 def deactivate_async(user, status \\ true) do
1242 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1243 end
1244
1245 def deactivate(user, status \\ true)
1246
1247 def deactivate(users, status) when is_list(users) do
1248 Repo.transaction(fn ->
1249 for user <- users, do: deactivate(user, status)
1250 end)
1251 end
1252
1253 def deactivate(%User{} = user, status) do
1254 with {:ok, user} <- set_activation_status(user, status) do
1255 user
1256 |> get_followers()
1257 |> Enum.filter(& &1.local)
1258 |> Enum.each(fn follower ->
1259 follower |> update_following_count() |> set_cache()
1260 end)
1261
1262 # Only update local user counts, remote will be update during the next pull.
1263 user
1264 |> get_friends()
1265 |> Enum.filter(& &1.local)
1266 |> Enum.each(&update_follower_count/1)
1267
1268 {:ok, user}
1269 end
1270 end
1271
1272 def update_notification_settings(%User{} = user, settings) do
1273 user
1274 |> cast(%{notification_settings: settings}, [])
1275 |> cast_embed(:notification_settings)
1276 |> validate_required([:notification_settings])
1277 |> update_and_set_cache()
1278 end
1279
1280 def delete(users) when is_list(users) do
1281 for user <- users, do: delete(user)
1282 end
1283
1284 def delete(%User{} = user) do
1285 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1286 end
1287
1288 def perform(:force_password_reset, user), do: force_password_reset(user)
1289
1290 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1291 def perform(:delete, %User{} = user) do
1292 {:ok, _user} = ActivityPub.delete(user)
1293
1294 # Remove all relationships
1295 user
1296 |> get_followers()
1297 |> Enum.each(fn follower ->
1298 ActivityPub.unfollow(follower, user)
1299 unfollow(follower, user)
1300 end)
1301
1302 user
1303 |> get_friends()
1304 |> Enum.each(fn followed ->
1305 ActivityPub.unfollow(user, followed)
1306 unfollow(user, followed)
1307 end)
1308
1309 delete_user_activities(user)
1310 invalidate_cache(user)
1311 Repo.delete(user)
1312 end
1313
1314 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1315
1316 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1317 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1318 when is_list(blocked_identifiers) do
1319 Enum.map(
1320 blocked_identifiers,
1321 fn blocked_identifier ->
1322 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1323 {:ok, _user_block} <- block(blocker, blocked),
1324 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1325 blocked
1326 else
1327 err ->
1328 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1329 err
1330 end
1331 end
1332 )
1333 end
1334
1335 def perform(:follow_import, %User{} = follower, followed_identifiers)
1336 when is_list(followed_identifiers) do
1337 Enum.map(
1338 followed_identifiers,
1339 fn followed_identifier ->
1340 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1341 {:ok, follower} <- maybe_direct_follow(follower, followed),
1342 {:ok, _} <- ActivityPub.follow(follower, followed) do
1343 followed
1344 else
1345 err ->
1346 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1347 err
1348 end
1349 end
1350 )
1351 end
1352
1353 @spec external_users_query() :: Ecto.Query.t()
1354 def external_users_query do
1355 User.Query.build(%{
1356 external: true,
1357 active: true,
1358 order_by: :id
1359 })
1360 end
1361
1362 @spec external_users(keyword()) :: [User.t()]
1363 def external_users(opts \\ []) do
1364 query =
1365 external_users_query()
1366 |> select([u], struct(u, [:id, :ap_id]))
1367
1368 query =
1369 if opts[:max_id],
1370 do: where(query, [u], u.id > ^opts[:max_id]),
1371 else: query
1372
1373 query =
1374 if opts[:limit],
1375 do: limit(query, ^opts[:limit]),
1376 else: query
1377
1378 Repo.all(query)
1379 end
1380
1381 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1382 BackgroundWorker.enqueue("blocks_import", %{
1383 "blocker_id" => blocker.id,
1384 "blocked_identifiers" => blocked_identifiers
1385 })
1386 end
1387
1388 def follow_import(%User{} = follower, followed_identifiers)
1389 when is_list(followed_identifiers) do
1390 BackgroundWorker.enqueue("follow_import", %{
1391 "follower_id" => follower.id,
1392 "followed_identifiers" => followed_identifiers
1393 })
1394 end
1395
1396 def delete_user_activities(%User{ap_id: ap_id}) do
1397 ap_id
1398 |> Activity.Queries.by_actor()
1399 |> RepoStreamer.chunk_stream(50)
1400 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1401 |> Stream.run()
1402 end
1403
1404 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1405 activity
1406 |> Object.normalize()
1407 |> ActivityPub.delete()
1408 end
1409
1410 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1411 object = Object.normalize(activity)
1412
1413 activity.actor
1414 |> get_cached_by_ap_id()
1415 |> ActivityPub.unlike(object)
1416 end
1417
1418 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1419 object = Object.normalize(activity)
1420
1421 activity.actor
1422 |> get_cached_by_ap_id()
1423 |> ActivityPub.unannounce(object)
1424 end
1425
1426 defp delete_activity(_activity), do: "Doing nothing"
1427
1428 def html_filter_policy(%User{no_rich_text: true}) do
1429 Pleroma.HTML.Scrubber.TwitterText
1430 end
1431
1432 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1433
1434 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1435
1436 def get_or_fetch_by_ap_id(ap_id) do
1437 user = get_cached_by_ap_id(ap_id)
1438
1439 if !is_nil(user) and !needs_update?(user) do
1440 {:ok, user}
1441 else
1442 fetch_by_ap_id(ap_id)
1443 end
1444 end
1445
1446 @doc """
1447 Creates an internal service actor by URI if missing.
1448 Optionally takes nickname for addressing.
1449 """
1450 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1451 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1452 {_, user} =
1453 case get_cached_by_ap_id(uri) do
1454 nil ->
1455 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1456 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1457 {:error, nil}
1458 end
1459
1460 %User{invisible: false} = user ->
1461 set_invisible(user)
1462
1463 user ->
1464 {:ok, user}
1465 end
1466
1467 user
1468 end
1469
1470 @spec set_invisible(User.t()) :: {:ok, User.t()}
1471 defp set_invisible(user) do
1472 user
1473 |> change(%{invisible: true})
1474 |> update_and_set_cache()
1475 end
1476
1477 @spec create_service_actor(String.t(), String.t()) ::
1478 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1479 defp create_service_actor(uri, nickname) do
1480 %User{
1481 invisible: true,
1482 local: true,
1483 ap_id: uri,
1484 nickname: nickname,
1485 follower_address: uri <> "/followers"
1486 }
1487 |> change
1488 |> unique_constraint(:nickname)
1489 |> Repo.insert()
1490 |> set_cache()
1491 end
1492
1493 # AP style
1494 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1495 key =
1496 public_key_pem
1497 |> :public_key.pem_decode()
1498 |> hd()
1499 |> :public_key.pem_entry_decode()
1500
1501 {:ok, key}
1502 end
1503
1504 def public_key(_), do: {:error, "not found key"}
1505
1506 def get_public_key_for_ap_id(ap_id) do
1507 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1508 {:ok, public_key} <- public_key(user) do
1509 {:ok, public_key}
1510 else
1511 _ -> :error
1512 end
1513 end
1514
1515 defp blank?(""), do: nil
1516 defp blank?(n), do: n
1517
1518 def insert_or_update_user(data) do
1519 data
1520 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1521 |> remote_user_creation()
1522 |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
1523 |> set_cache()
1524 end
1525
1526 def ap_enabled?(%User{local: true}), do: true
1527 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1528 def ap_enabled?(_), do: false
1529
1530 @doc "Gets or fetch a user by uri or nickname."
1531 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1532 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1533 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1534
1535 # wait a period of time and return newest version of the User structs
1536 # this is because we have synchronous follow APIs and need to simulate them
1537 # with an async handshake
1538 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1539 with %User{} = a <- get_cached_by_id(a.id),
1540 %User{} = b <- get_cached_by_id(b.id) do
1541 {:ok, a, b}
1542 else
1543 nil -> :error
1544 end
1545 end
1546
1547 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1548 with :ok <- :timer.sleep(timeout),
1549 %User{} = a <- get_cached_by_id(a.id),
1550 %User{} = b <- get_cached_by_id(b.id) do
1551 {:ok, a, b}
1552 else
1553 nil -> :error
1554 end
1555 end
1556
1557 def parse_bio(bio) when is_binary(bio) and bio != "" do
1558 bio
1559 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1560 |> elem(0)
1561 end
1562
1563 def parse_bio(_), do: ""
1564
1565 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1566 # TODO: get profile URLs other than user.ap_id
1567 profile_urls = [user.ap_id]
1568
1569 bio
1570 |> CommonUtils.format_input("text/plain",
1571 mentions_format: :full,
1572 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1573 )
1574 |> elem(0)
1575 end
1576
1577 def parse_bio(_, _), do: ""
1578
1579 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1580 Repo.transaction(fn ->
1581 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1582 end)
1583 end
1584
1585 def tag(nickname, tags) when is_binary(nickname),
1586 do: tag(get_by_nickname(nickname), tags)
1587
1588 def tag(%User{} = user, tags),
1589 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1590
1591 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1592 Repo.transaction(fn ->
1593 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1594 end)
1595 end
1596
1597 def untag(nickname, tags) when is_binary(nickname),
1598 do: untag(get_by_nickname(nickname), tags)
1599
1600 def untag(%User{} = user, tags),
1601 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1602
1603 defp update_tags(%User{} = user, new_tags) do
1604 {:ok, updated_user} =
1605 user
1606 |> change(%{tags: new_tags})
1607 |> update_and_set_cache()
1608
1609 updated_user
1610 end
1611
1612 defp normalize_tags(tags) do
1613 [tags]
1614 |> List.flatten()
1615 |> Enum.map(&String.downcase/1)
1616 end
1617
1618 defp local_nickname_regex do
1619 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1620 @extended_local_nickname_regex
1621 else
1622 @strict_local_nickname_regex
1623 end
1624 end
1625
1626 def local_nickname(nickname_or_mention) do
1627 nickname_or_mention
1628 |> full_nickname()
1629 |> String.split("@")
1630 |> hd()
1631 end
1632
1633 def full_nickname(nickname_or_mention),
1634 do: String.trim_leading(nickname_or_mention, "@")
1635
1636 def error_user(ap_id) do
1637 %User{
1638 name: ap_id,
1639 ap_id: ap_id,
1640 nickname: "erroruser@example.com",
1641 inserted_at: NaiveDateTime.utc_now()
1642 }
1643 end
1644
1645 @spec all_superusers() :: [User.t()]
1646 def all_superusers do
1647 User.Query.build(%{super_users: true, local: true, deactivated: false})
1648 |> Repo.all()
1649 end
1650
1651 def showing_reblogs?(%User{} = user, %User{} = target) do
1652 not UserRelationship.reblog_mute_exists?(user, target)
1653 end
1654
1655 @doc """
1656 The function returns a query to get users with no activity for given interval of days.
1657 Inactive users are those who didn't read any notification, or had any activity where
1658 the user is the activity's actor, during `inactivity_threshold` days.
1659 Deactivated users will not appear in this list.
1660
1661 ## Examples
1662
1663 iex> Pleroma.User.list_inactive_users()
1664 %Ecto.Query{}
1665 """
1666 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1667 def list_inactive_users_query(inactivity_threshold \\ 7) do
1668 negative_inactivity_threshold = -inactivity_threshold
1669 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1670 # Subqueries are not supported in `where` clauses, join gets too complicated.
1671 has_read_notifications =
1672 from(n in Pleroma.Notification,
1673 where: n.seen == true,
1674 group_by: n.id,
1675 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1676 select: n.user_id
1677 )
1678 |> Pleroma.Repo.all()
1679
1680 from(u in Pleroma.User,
1681 left_join: a in Pleroma.Activity,
1682 on: u.ap_id == a.actor,
1683 where: not is_nil(u.nickname),
1684 where: u.deactivated != ^true,
1685 where: u.id not in ^has_read_notifications,
1686 group_by: u.id,
1687 having:
1688 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1689 is_nil(max(a.inserted_at))
1690 )
1691 end
1692
1693 @doc """
1694 Enable or disable email notifications for user
1695
1696 ## Examples
1697
1698 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1699 Pleroma.User{email_notifications: %{"digest" => true}}
1700
1701 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1702 Pleroma.User{email_notifications: %{"digest" => false}}
1703 """
1704 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1705 {:ok, t()} | {:error, Ecto.Changeset.t()}
1706 def switch_email_notifications(user, type, status) do
1707 User.update_email_notifications(user, %{type => status})
1708 end
1709
1710 @doc """
1711 Set `last_digest_emailed_at` value for the user to current time
1712 """
1713 @spec touch_last_digest_emailed_at(t()) :: t()
1714 def touch_last_digest_emailed_at(user) do
1715 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1716
1717 {:ok, updated_user} =
1718 user
1719 |> change(%{last_digest_emailed_at: now})
1720 |> update_and_set_cache()
1721
1722 updated_user
1723 end
1724
1725 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1726 def toggle_confirmation(%User{} = user) do
1727 user
1728 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1729 |> update_and_set_cache()
1730 end
1731
1732 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1733 def toggle_confirmation(users) do
1734 Enum.map(users, &toggle_confirmation/1)
1735 end
1736
1737 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1738 mascot
1739 end
1740
1741 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1742 # use instance-default
1743 config = Pleroma.Config.get([:assets, :mascots])
1744 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1745 mascot = Keyword.get(config, default_mascot)
1746
1747 %{
1748 "id" => "default-mascot",
1749 "url" => mascot[:url],
1750 "preview_url" => mascot[:url],
1751 "pleroma" => %{
1752 "mime_type" => mascot[:mime_type]
1753 }
1754 }
1755 end
1756
1757 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1758
1759 def ensure_keys_present(%User{} = user) do
1760 with {:ok, pem} <- Keys.generate_rsa_pem() do
1761 user
1762 |> cast(%{keys: pem}, [:keys])
1763 |> validate_required([:keys])
1764 |> update_and_set_cache()
1765 end
1766 end
1767
1768 def get_ap_ids_by_nicknames(nicknames) do
1769 from(u in User,
1770 where: u.nickname in ^nicknames,
1771 select: u.ap_id
1772 )
1773 |> Repo.all()
1774 end
1775
1776 defdelegate search(query, opts \\ []), to: User.Search
1777
1778 defp put_password_hash(
1779 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1780 ) do
1781 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1782 end
1783
1784 defp put_password_hash(changeset), do: changeset
1785
1786 def is_internal_user?(%User{nickname: nil}), do: true
1787 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1788 def is_internal_user?(_), do: false
1789
1790 # A hack because user delete activities have a fake id for whatever reason
1791 # TODO: Get rid of this
1792 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1793
1794 def get_delivered_users_by_object_id(object_id) do
1795 from(u in User,
1796 inner_join: delivery in assoc(u, :deliveries),
1797 where: delivery.object_id == ^object_id
1798 )
1799 |> Repo.all()
1800 end
1801
1802 def change_email(user, email) do
1803 user
1804 |> cast(%{email: email}, [:email])
1805 |> validate_required([:email])
1806 |> unique_constraint(:email)
1807 |> validate_format(:email, @email_regex)
1808 |> update_and_set_cache()
1809 end
1810
1811 # Internal function; public one is `deactivate/2`
1812 defp set_activation_status(user, deactivated) do
1813 user
1814 |> cast(%{deactivated: deactivated}, [:deactivated])
1815 |> update_and_set_cache()
1816 end
1817
1818 def update_banner(user, banner) do
1819 user
1820 |> cast(%{banner: banner}, [:banner])
1821 |> update_and_set_cache()
1822 end
1823
1824 def update_background(user, background) do
1825 user
1826 |> cast(%{background: background}, [:background])
1827 |> update_and_set_cache()
1828 end
1829
1830 def update_source_data(user, source_data) do
1831 user
1832 |> cast(%{source_data: source_data}, [:source_data])
1833 |> update_and_set_cache()
1834 end
1835
1836 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1837 %{
1838 admin: is_admin,
1839 moderator: is_moderator
1840 }
1841 end
1842
1843 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1844 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1845 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1846 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1847
1848 attachment
1849 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1850 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1851 |> Enum.take(limit)
1852 end
1853
1854 def fields(%{fields: nil}), do: []
1855
1856 def fields(%{fields: fields}), do: fields
1857
1858 def validate_fields(changeset, remote? \\ false) do
1859 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1860 limit = Pleroma.Config.get([:instance, limit_name], 0)
1861
1862 changeset
1863 |> validate_length(:fields, max: limit)
1864 |> validate_change(:fields, fn :fields, fields ->
1865 if Enum.all?(fields, &valid_field?/1) do
1866 []
1867 else
1868 [fields: "invalid"]
1869 end
1870 end)
1871 end
1872
1873 defp valid_field?(%{"name" => name, "value" => value}) do
1874 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1875 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1876
1877 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1878 String.length(value) <= value_limit
1879 end
1880
1881 defp valid_field?(_), do: false
1882
1883 defp truncate_field(%{"name" => name, "value" => value}) do
1884 {name, _chopped} =
1885 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1886
1887 {value, _chopped} =
1888 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1889
1890 %{"name" => name, "value" => value}
1891 end
1892
1893 def admin_api_update(user, params) do
1894 user
1895 |> cast(params, [
1896 :is_moderator,
1897 :is_admin,
1898 :show_role
1899 ])
1900 |> update_and_set_cache()
1901 end
1902
1903 @doc "Signs user out of all applications"
1904 def global_sign_out(user) do
1905 OAuth.Authorization.delete_user_authorizations(user)
1906 OAuth.Token.delete_user_tokens(user)
1907 end
1908
1909 def mascot_update(user, url) do
1910 user
1911 |> cast(%{mascot: url}, [:mascot])
1912 |> validate_required([:mascot])
1913 |> update_and_set_cache()
1914 end
1915
1916 def mastodon_settings_update(user, settings) do
1917 user
1918 |> cast(%{settings: settings}, [:settings])
1919 |> validate_required([:settings])
1920 |> update_and_set_cache()
1921 end
1922
1923 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1924 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1925 params =
1926 if need_confirmation? do
1927 %{
1928 confirmation_pending: true,
1929 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1930 }
1931 else
1932 %{
1933 confirmation_pending: false,
1934 confirmation_token: nil
1935 }
1936 end
1937
1938 cast(user, params, [:confirmation_pending, :confirmation_token])
1939 end
1940
1941 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1942 if id not in user.pinned_activities do
1943 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1944 params = %{pinned_activities: user.pinned_activities ++ [id]}
1945
1946 user
1947 |> cast(params, [:pinned_activities])
1948 |> validate_length(:pinned_activities,
1949 max: max_pinned_statuses,
1950 message: "You have already pinned the maximum number of statuses"
1951 )
1952 else
1953 change(user)
1954 end
1955 |> update_and_set_cache()
1956 end
1957
1958 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1959 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1960
1961 user
1962 |> cast(params, [:pinned_activities])
1963 |> update_and_set_cache()
1964 end
1965
1966 def update_email_notifications(user, settings) do
1967 email_notifications =
1968 user.email_notifications
1969 |> Map.merge(settings)
1970 |> Map.take(["digest"])
1971
1972 params = %{email_notifications: email_notifications}
1973 fields = [:email_notifications]
1974
1975 user
1976 |> cast(params, fields)
1977 |> validate_required(fields)
1978 |> update_and_set_cache()
1979 end
1980
1981 defp set_domain_blocks(user, domain_blocks) do
1982 params = %{domain_blocks: domain_blocks}
1983
1984 user
1985 |> cast(params, [:domain_blocks])
1986 |> validate_required([:domain_blocks])
1987 |> update_and_set_cache()
1988 end
1989
1990 def block_domain(user, domain_blocked) do
1991 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1992 end
1993
1994 def unblock_domain(user, domain_blocked) do
1995 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1996 end
1997
1998 @spec add_to_block(User.t(), User.t()) ::
1999 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2000 defp add_to_block(%User{} = user, %User{} = blocked) do
2001 UserRelationship.create_block(user, blocked)
2002 end
2003
2004 @spec add_to_block(User.t(), User.t()) ::
2005 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2006 defp remove_from_block(%User{} = user, %User{} = blocked) do
2007 UserRelationship.delete_block(user, blocked)
2008 end
2009
2010 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2011 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2012 {:ok, user_notification_mute} <-
2013 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2014 {:ok, nil} do
2015 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2016 end
2017 end
2018
2019 defp remove_from_mutes(user, %User{} = muted_user) do
2020 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2021 {:ok, user_notification_mute} <-
2022 UserRelationship.delete_notification_mute(user, muted_user) do
2023 {:ok, [user_mute, user_notification_mute]}
2024 end
2025 end
2026
2027 def set_invisible(user, invisible) do
2028 params = %{invisible: invisible}
2029
2030 user
2031 |> cast(params, [:invisible])
2032 |> validate_required([:invisible])
2033 |> update_and_set_cache()
2034 end
2035 end