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