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