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