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