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