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