Swap map with each
[akkoma] / lib / pleroma / user.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.User do
6 use Ecto.Schema
7
8 import Ecto.Changeset
9 import Ecto.Query
10
11 alias Comeonin.Pbkdf2
12 alias Ecto.Multi
13 alias Pleroma.Activity
14 alias Pleroma.Conversation.Participation
15 alias Pleroma.Delivery
16 alias Pleroma.FollowingRelationship
17 alias Pleroma.Keys
18 alias Pleroma.Notification
19 alias Pleroma.Object
20 alias Pleroma.Registration
21 alias Pleroma.Repo
22 alias Pleroma.RepoStreamer
23 alias Pleroma.User
24 alias Pleroma.Web
25 alias Pleroma.Web.ActivityPub.ActivityPub
26 alias Pleroma.Web.ActivityPub.Utils
27 alias Pleroma.Web.CommonAPI
28 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
29 alias Pleroma.Web.OAuth
30 alias Pleroma.Web.RelMe
31 alias Pleroma.Workers.BackgroundWorker
32
33 require Logger
34
35 @type t :: %__MODULE__{}
36
37 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
38
39 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
40 @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
41
42 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
43 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
44
45 schema "users" do
46 field(:bio, :string)
47 field(:email, :string)
48 field(:name, :string)
49 field(:nickname, :string)
50 field(:password_hash, :string)
51 field(:password, :string, virtual: true)
52 field(:password_confirmation, :string, virtual: true)
53 field(:keys, :string)
54 field(:ap_id, :string)
55 field(:avatar, :map)
56 field(:local, :boolean, default: true)
57 field(:follower_address, :string)
58 field(:following_address, :string)
59 field(:search_rank, :float, virtual: true)
60 field(:search_type, :integer, virtual: true)
61 field(:tags, {:array, :string}, default: [])
62 field(:last_refreshed_at, :naive_datetime_usec)
63 field(:last_digest_emailed_at, :naive_datetime)
64
65 field(:banner, :map, default: %{})
66 field(:background, :map, default: %{})
67 field(:source_data, :map, default: %{})
68 field(:note_count, :integer, default: 0)
69 field(:follower_count, :integer, default: 0)
70 # Should be filled in only for remote users
71 field(:following_count, :integer, default: nil)
72 field(:locked, :boolean, default: false)
73 field(:confirmation_pending, :boolean, default: false)
74 field(:password_reset_pending, :boolean, default: false)
75 field(:confirmation_token, :string, default: nil)
76 field(:default_scope, :string, default: "public")
77 field(:blocks, {:array, :string}, default: [])
78 field(:domain_blocks, {:array, :string}, default: [])
79 field(:mutes, {:array, :string}, default: [])
80 field(:muted_reblogs, {:array, :string}, default: [])
81 field(:muted_notifications, {:array, :string}, default: [])
82 field(:subscribers, {:array, :string}, default: [])
83 field(:deactivated, :boolean, default: false)
84 field(:no_rich_text, :boolean, default: false)
85 field(:ap_enabled, :boolean, default: false)
86 field(:is_moderator, :boolean, default: false)
87 field(:is_admin, :boolean, default: false)
88 field(:show_role, :boolean, default: true)
89 field(:settings, :map, default: nil)
90 field(:magic_key, :string, default: nil)
91 field(:uri, :string, default: nil)
92 field(:hide_followers_count, :boolean, default: false)
93 field(:hide_follows_count, :boolean, default: false)
94 field(:hide_followers, :boolean, default: false)
95 field(:hide_follows, :boolean, default: false)
96 field(:hide_favorites, :boolean, default: true)
97 field(:unread_conversation_count, :integer, default: 0)
98 field(:pinned_activities, {:array, :string}, default: [])
99 field(:email_notifications, :map, default: %{"digest" => false})
100 field(:mascot, :map, default: nil)
101 field(:emoji, {:array, :map}, default: [])
102 field(:pleroma_settings_store, :map, default: %{})
103 field(:fields, {:array, :map}, default: [])
104 field(:raw_fields, {:array, :map}, default: [])
105 field(:discoverable, :boolean, default: false)
106 field(:invisible, :boolean, default: false)
107 field(:skip_thread_containment, :boolean, default: false)
108
109 field(:notification_settings, :map,
110 default: %{
111 "followers" => true,
112 "follows" => true,
113 "non_follows" => true,
114 "non_followers" => true
115 }
116 )
117
118 has_many(:notifications, Notification)
119 has_many(:registrations, Registration)
120 has_many(:deliveries, Delivery)
121
122 field(:info, :map, default: %{})
123
124 timestamps()
125 end
126
127 def auth_active?(%User{confirmation_pending: true}),
128 do: !Pleroma.Config.get([:instance, :account_activation_required])
129
130 def auth_active?(%User{}), do: true
131
132 def visible_for?(user, for_user \\ nil)
133
134 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
135
136 def visible_for?(%User{} = user, for_user) do
137 auth_active?(user) || superuser?(for_user)
138 end
139
140 def visible_for?(_, _), do: false
141
142 def superuser?(%User{local: true, is_admin: true}), do: true
143 def superuser?(%User{local: true, is_moderator: true}), do: true
144 def superuser?(_), do: false
145
146 def invisible?(%User{invisible: true}), do: true
147 def invisible?(_), do: false
148
149 def avatar_url(user, options \\ []) do
150 case user.avatar do
151 %{"url" => [%{"href" => href} | _]} -> href
152 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
153 end
154 end
155
156 def banner_url(user, options \\ []) do
157 case user.banner do
158 %{"url" => [%{"href" => href} | _]} -> href
159 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
160 end
161 end
162
163 def profile_url(%User{source_data: %{"url" => url}}), do: url
164 def profile_url(%User{ap_id: ap_id}), do: ap_id
165 def profile_url(_), do: nil
166
167 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
168
169 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
170 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
171
172 @spec ap_following(User.t()) :: Sring.t()
173 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
174 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
175
176 def user_info(%User{} = user, args \\ %{}) do
177 following_count =
178 Map.get(args, :following_count, user.following_count || following_count(user))
179
180 follower_count = Map.get(args, :follower_count, user.follower_count)
181
182 %{
183 note_count: user.note_count,
184 locked: user.locked,
185 confirmation_pending: user.confirmation_pending,
186 default_scope: user.default_scope
187 }
188 |> Map.put(:following_count, following_count)
189 |> Map.put(:follower_count, follower_count)
190 end
191
192 def follow_state(%User{} = user, %User{} = target) do
193 case Utils.fetch_latest_follow(user, target) do
194 %{data: %{"state" => state}} -> state
195 # Ideally this would be nil, but then Cachex does not commit the value
196 _ -> false
197 end
198 end
199
200 def get_cached_follow_state(user, target) do
201 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
202 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
203 end
204
205 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
206 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
207 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
208 end
209
210 def set_info_cache(user, args) do
211 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
212 end
213
214 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
215 def restrict_deactivated(query) do
216 from(u in query, where: u.deactivated != ^true)
217 end
218
219 defdelegate following_count(user), to: FollowingRelationship
220
221 defp truncate_fields_param(params) do
222 if Map.has_key?(params, :fields) do
223 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
224 else
225 params
226 end
227 end
228
229 defp truncate_if_exists(params, key, max_length) do
230 if Map.has_key?(params, key) and is_binary(params[key]) do
231 {value, _chopped} = String.split_at(params[key], max_length)
232 Map.put(params, key, value)
233 else
234 params
235 end
236 end
237
238 def remote_user_creation(params) do
239 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
240 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
241
242 params =
243 params
244 |> Map.put(:info, params[:info] || %{})
245 |> truncate_if_exists(:name, name_limit)
246 |> truncate_if_exists(:bio, bio_limit)
247 |> truncate_fields_param()
248
249 changeset =
250 %User{local: false}
251 |> cast(
252 params,
253 [
254 :bio,
255 :name,
256 :ap_id,
257 :nickname,
258 :avatar,
259 :ap_enabled,
260 :source_data,
261 :banner,
262 :locked,
263 :magic_key,
264 :uri,
265 :hide_followers,
266 :hide_follows,
267 :hide_followers_count,
268 :hide_follows_count,
269 :follower_count,
270 :fields,
271 :following_count,
272 :discoverable,
273 :invisible
274 ]
275 )
276 |> validate_required([:name, :ap_id])
277 |> unique_constraint(:nickname)
278 |> validate_format(:nickname, @email_regex)
279 |> validate_length(:bio, max: bio_limit)
280 |> validate_length(:name, max: name_limit)
281 |> validate_fields(true)
282
283 case params[:source_data] do
284 %{"followers" => followers, "following" => following} ->
285 changeset
286 |> put_change(:follower_address, followers)
287 |> put_change(:following_address, following)
288
289 _ ->
290 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
291 put_change(changeset, :follower_address, followers)
292 end
293 end
294
295 def update_changeset(struct, params \\ %{}) do
296 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
297 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
298
299 struct
300 |> cast(
301 params,
302 [
303 :bio,
304 :name,
305 :avatar,
306 :locked,
307 :no_rich_text,
308 :default_scope,
309 :banner,
310 :hide_follows,
311 :hide_followers,
312 :hide_followers_count,
313 :hide_follows_count,
314 :hide_favorites,
315 :background,
316 :show_role,
317 :skip_thread_containment,
318 :fields,
319 :raw_fields,
320 :pleroma_settings_store,
321 :discoverable
322 ]
323 )
324 |> unique_constraint(:nickname)
325 |> validate_format(:nickname, local_nickname_regex())
326 |> validate_length(:bio, max: bio_limit)
327 |> validate_length(:name, min: 1, max: name_limit)
328 |> validate_fields(false)
329 end
330
331 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
332 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
333 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
334
335 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
336
337 params = if remote?, do: truncate_fields_param(params), else: params
338
339 struct
340 |> cast(
341 params,
342 [
343 :bio,
344 :name,
345 :follower_address,
346 :following_address,
347 :avatar,
348 :last_refreshed_at,
349 :ap_enabled,
350 :source_data,
351 :banner,
352 :locked,
353 :magic_key,
354 :follower_count,
355 :following_count,
356 :hide_follows,
357 :fields,
358 :hide_followers,
359 :discoverable,
360 :hide_followers_count,
361 :hide_follows_count
362 ]
363 )
364 |> unique_constraint(:nickname)
365 |> validate_format(:nickname, local_nickname_regex())
366 |> validate_length(:bio, max: bio_limit)
367 |> validate_length(:name, max: name_limit)
368 |> validate_fields(remote?)
369 end
370
371 def password_update_changeset(struct, params) do
372 struct
373 |> cast(params, [:password, :password_confirmation])
374 |> validate_required([:password, :password_confirmation])
375 |> validate_confirmation(:password)
376 |> put_password_hash()
377 |> put_change(:password_reset_pending, false)
378 end
379
380 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
381 def reset_password(%User{id: user_id} = user, data) do
382 multi =
383 Multi.new()
384 |> Multi.update(:user, password_update_changeset(user, data))
385 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
386 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
387
388 case Repo.transaction(multi) do
389 {:ok, %{user: user} = _} -> set_cache(user)
390 {:error, _, changeset, _} -> {:error, changeset}
391 end
392 end
393
394 def update_password_reset_pending(user, value) do
395 user
396 |> change()
397 |> put_change(:password_reset_pending, value)
398 |> update_and_set_cache()
399 end
400
401 def force_password_reset_async(user) do
402 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
403 end
404
405 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
406 def force_password_reset(user), do: update_password_reset_pending(user, true)
407
408 def register_changeset(struct, params \\ %{}, opts \\ []) do
409 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
410 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
411
412 need_confirmation? =
413 if is_nil(opts[:need_confirmation]) do
414 Pleroma.Config.get([:instance, :account_activation_required])
415 else
416 opts[:need_confirmation]
417 end
418
419 struct
420 |> confirmation_changeset(need_confirmation: need_confirmation?)
421 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
422 |> validate_required([:name, :nickname, :password, :password_confirmation])
423 |> validate_confirmation(:password)
424 |> unique_constraint(:email)
425 |> unique_constraint(:nickname)
426 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
427 |> validate_format(:nickname, local_nickname_regex())
428 |> validate_format(:email, @email_regex)
429 |> validate_length(:bio, max: bio_limit)
430 |> validate_length(:name, min: 1, max: name_limit)
431 |> maybe_validate_required_email(opts[:external])
432 |> put_password_hash
433 |> put_ap_id()
434 |> unique_constraint(:ap_id)
435 |> put_following_and_follower_address()
436 end
437
438 def maybe_validate_required_email(changeset, true), do: changeset
439 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
440
441 defp put_ap_id(changeset) do
442 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
443 put_change(changeset, :ap_id, ap_id)
444 end
445
446 defp put_following_and_follower_address(changeset) do
447 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
448
449 changeset
450 |> put_change(:follower_address, followers)
451 end
452
453 defp autofollow_users(user) do
454 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
455
456 autofollowed_users =
457 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
458 |> Repo.all()
459
460 follow_all(user, autofollowed_users)
461 end
462
463 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
464 def register(%Ecto.Changeset{} = changeset) do
465 with {:ok, user} <- Repo.insert(changeset) do
466 post_register_action(user)
467 end
468 end
469
470 def post_register_action(%User{} = user) do
471 with {:ok, user} <- autofollow_users(user),
472 {:ok, user} <- set_cache(user),
473 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
474 {:ok, _} <- try_send_confirmation_email(user) do
475 {:ok, user}
476 end
477 end
478
479 def try_send_confirmation_email(%User{} = user) do
480 if user.confirmation_pending &&
481 Pleroma.Config.get([:instance, :account_activation_required]) do
482 user
483 |> Pleroma.Emails.UserEmail.account_confirmation_email()
484 |> Pleroma.Emails.Mailer.deliver_async()
485
486 {:ok, :enqueued}
487 else
488 {:ok, :noop}
489 end
490 end
491
492 def try_send_confirmation_email(users) do
493 Enum.each(users, &try_send_confirmation_email/1)
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
1103 # Only update local user counts, remote will be update during the next pull.
1104 user
1105 |> get_friends()
1106 |> Enum.filter(& &1.local)
1107 |> Enum.each(&update_follower_count/1)
1108
1109 {:ok, user}
1110 end
1111 end
1112
1113 def update_notification_settings(%User{} = user, settings) do
1114 settings =
1115 settings
1116 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1117 |> Map.new()
1118
1119 notification_settings =
1120 user.notification_settings
1121 |> Map.merge(settings)
1122 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1123
1124 params = %{notification_settings: notification_settings}
1125
1126 user
1127 |> cast(params, [:notification_settings])
1128 |> validate_required([:notification_settings])
1129 |> update_and_set_cache()
1130 end
1131
1132 def delete(users) when is_list(users) do
1133 for user <- users, do: delete(user)
1134 end
1135
1136 def delete(%User{} = user) do
1137 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1138 end
1139
1140 def perform(:force_password_reset, user), do: force_password_reset(user)
1141
1142 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1143 def perform(:delete, %User{} = user) do
1144 {:ok, _user} = ActivityPub.delete(user)
1145
1146 # Remove all relationships
1147 user
1148 |> get_followers()
1149 |> Enum.each(fn follower ->
1150 ActivityPub.unfollow(follower, user)
1151 unfollow(follower, user)
1152 end)
1153
1154 user
1155 |> get_friends()
1156 |> Enum.each(fn followed ->
1157 ActivityPub.unfollow(user, followed)
1158 unfollow(user, followed)
1159 end)
1160
1161 delete_user_activities(user)
1162 invalidate_cache(user)
1163 Repo.delete(user)
1164 end
1165
1166 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1167 def perform(:fetch_initial_posts, %User{} = user) do
1168 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1169
1170 # Insert all the posts in reverse order, so they're in the right order on the timeline
1171 user.source_data["outbox"]
1172 |> Utils.fetch_ordered_collection(pages)
1173 |> Enum.reverse()
1174 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1175 end
1176
1177 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1178
1179 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1180 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1181 when is_list(blocked_identifiers) do
1182 Enum.map(
1183 blocked_identifiers,
1184 fn blocked_identifier ->
1185 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1186 {:ok, blocker} <- block(blocker, blocked),
1187 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1188 blocked
1189 else
1190 err ->
1191 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1192 err
1193 end
1194 end
1195 )
1196 end
1197
1198 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1199 def perform(:follow_import, %User{} = follower, followed_identifiers)
1200 when is_list(followed_identifiers) do
1201 Enum.map(
1202 followed_identifiers,
1203 fn followed_identifier ->
1204 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1205 {:ok, follower} <- maybe_direct_follow(follower, followed),
1206 {:ok, _} <- ActivityPub.follow(follower, followed) do
1207 followed
1208 else
1209 err ->
1210 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1211 err
1212 end
1213 end
1214 )
1215 end
1216
1217 @spec external_users_query() :: Ecto.Query.t()
1218 def external_users_query do
1219 User.Query.build(%{
1220 external: true,
1221 active: true,
1222 order_by: :id
1223 })
1224 end
1225
1226 @spec external_users(keyword()) :: [User.t()]
1227 def external_users(opts \\ []) do
1228 query =
1229 external_users_query()
1230 |> select([u], struct(u, [:id, :ap_id, :info]))
1231
1232 query =
1233 if opts[:max_id],
1234 do: where(query, [u], u.id > ^opts[:max_id]),
1235 else: query
1236
1237 query =
1238 if opts[:limit],
1239 do: limit(query, ^opts[:limit]),
1240 else: query
1241
1242 Repo.all(query)
1243 end
1244
1245 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1246 BackgroundWorker.enqueue("blocks_import", %{
1247 "blocker_id" => blocker.id,
1248 "blocked_identifiers" => blocked_identifiers
1249 })
1250 end
1251
1252 def follow_import(%User{} = follower, followed_identifiers)
1253 when is_list(followed_identifiers) do
1254 BackgroundWorker.enqueue("follow_import", %{
1255 "follower_id" => follower.id,
1256 "followed_identifiers" => followed_identifiers
1257 })
1258 end
1259
1260 def delete_user_activities(%User{ap_id: ap_id}) do
1261 ap_id
1262 |> Activity.Queries.by_actor()
1263 |> RepoStreamer.chunk_stream(50)
1264 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1265 |> Stream.run()
1266 end
1267
1268 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1269 activity
1270 |> Object.normalize()
1271 |> ActivityPub.delete()
1272 end
1273
1274 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1275 object = Object.normalize(activity)
1276
1277 activity.actor
1278 |> get_cached_by_ap_id()
1279 |> ActivityPub.unlike(object)
1280 end
1281
1282 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1283 object = Object.normalize(activity)
1284
1285 activity.actor
1286 |> get_cached_by_ap_id()
1287 |> ActivityPub.unannounce(object)
1288 end
1289
1290 defp delete_activity(_activity), do: "Doing nothing"
1291
1292 def html_filter_policy(%User{no_rich_text: true}) do
1293 Pleroma.HTML.Scrubber.TwitterText
1294 end
1295
1296 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1297
1298 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1299
1300 def get_or_fetch_by_ap_id(ap_id) do
1301 user = get_cached_by_ap_id(ap_id)
1302
1303 if !is_nil(user) and !needs_update?(user) do
1304 {:ok, user}
1305 else
1306 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1307 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1308
1309 resp = fetch_by_ap_id(ap_id)
1310
1311 if should_fetch_initial do
1312 with {:ok, %User{} = user} <- resp do
1313 fetch_initial_posts(user)
1314 end
1315 end
1316
1317 resp
1318 end
1319 end
1320
1321 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1322 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1323 with %User{} = user <- get_cached_by_ap_id(uri) do
1324 user
1325 else
1326 _ ->
1327 {:ok, user} =
1328 %User{}
1329 |> cast(%{}, [:ap_id, :nickname, :local])
1330 |> put_change(:ap_id, uri)
1331 |> put_change(:nickname, nickname)
1332 |> put_change(:local, true)
1333 |> put_change(:follower_address, uri <> "/followers")
1334 |> Repo.insert()
1335
1336 user
1337 end
1338 end
1339
1340 # AP style
1341 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1342 key =
1343 public_key_pem
1344 |> :public_key.pem_decode()
1345 |> hd()
1346 |> :public_key.pem_entry_decode()
1347
1348 {:ok, key}
1349 end
1350
1351 def public_key(_), do: {:error, "not found key"}
1352
1353 def get_public_key_for_ap_id(ap_id) do
1354 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1355 {:ok, public_key} <- public_key(user) do
1356 {:ok, public_key}
1357 else
1358 _ -> :error
1359 end
1360 end
1361
1362 defp blank?(""), do: nil
1363 defp blank?(n), do: n
1364
1365 def insert_or_update_user(data) do
1366 data
1367 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1368 |> remote_user_creation()
1369 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1370 |> set_cache()
1371 end
1372
1373 def ap_enabled?(%User{local: true}), do: true
1374 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1375 def ap_enabled?(_), do: false
1376
1377 @doc "Gets or fetch a user by uri or nickname."
1378 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1379 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1380 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1381
1382 # wait a period of time and return newest version of the User structs
1383 # this is because we have synchronous follow APIs and need to simulate them
1384 # with an async handshake
1385 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1386 with %User{} = a <- get_cached_by_id(a.id),
1387 %User{} = b <- get_cached_by_id(b.id) do
1388 {:ok, a, b}
1389 else
1390 nil -> :error
1391 end
1392 end
1393
1394 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1395 with :ok <- :timer.sleep(timeout),
1396 %User{} = a <- get_cached_by_id(a.id),
1397 %User{} = b <- get_cached_by_id(b.id) do
1398 {:ok, a, b}
1399 else
1400 nil -> :error
1401 end
1402 end
1403
1404 def parse_bio(bio) when is_binary(bio) and bio != "" do
1405 bio
1406 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1407 |> elem(0)
1408 end
1409
1410 def parse_bio(_), do: ""
1411
1412 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1413 # TODO: get profile URLs other than user.ap_id
1414 profile_urls = [user.ap_id]
1415
1416 bio
1417 |> CommonUtils.format_input("text/plain",
1418 mentions_format: :full,
1419 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1420 )
1421 |> elem(0)
1422 end
1423
1424 def parse_bio(_, _), do: ""
1425
1426 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1427 Repo.transaction(fn ->
1428 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1429 end)
1430 end
1431
1432 def tag(nickname, tags) when is_binary(nickname),
1433 do: tag(get_by_nickname(nickname), tags)
1434
1435 def tag(%User{} = user, tags),
1436 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1437
1438 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1439 Repo.transaction(fn ->
1440 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1441 end)
1442 end
1443
1444 def untag(nickname, tags) when is_binary(nickname),
1445 do: untag(get_by_nickname(nickname), tags)
1446
1447 def untag(%User{} = user, tags),
1448 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1449
1450 defp update_tags(%User{} = user, new_tags) do
1451 {:ok, updated_user} =
1452 user
1453 |> change(%{tags: new_tags})
1454 |> update_and_set_cache()
1455
1456 updated_user
1457 end
1458
1459 defp normalize_tags(tags) do
1460 [tags]
1461 |> List.flatten()
1462 |> Enum.map(&String.downcase/1)
1463 end
1464
1465 defp local_nickname_regex do
1466 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1467 @extended_local_nickname_regex
1468 else
1469 @strict_local_nickname_regex
1470 end
1471 end
1472
1473 def local_nickname(nickname_or_mention) do
1474 nickname_or_mention
1475 |> full_nickname()
1476 |> String.split("@")
1477 |> hd()
1478 end
1479
1480 def full_nickname(nickname_or_mention),
1481 do: String.trim_leading(nickname_or_mention, "@")
1482
1483 def error_user(ap_id) do
1484 %User{
1485 name: ap_id,
1486 ap_id: ap_id,
1487 nickname: "erroruser@example.com",
1488 inserted_at: NaiveDateTime.utc_now()
1489 }
1490 end
1491
1492 @spec all_superusers() :: [User.t()]
1493 def all_superusers do
1494 User.Query.build(%{super_users: true, local: true, deactivated: false})
1495 |> Repo.all()
1496 end
1497
1498 def showing_reblogs?(%User{} = user, %User{} = target) do
1499 target.ap_id not in user.muted_reblogs
1500 end
1501
1502 @doc """
1503 The function returns a query to get users with no activity for given interval of days.
1504 Inactive users are those who didn't read any notification, or had any activity where
1505 the user is the activity's actor, during `inactivity_threshold` days.
1506 Deactivated users will not appear in this list.
1507
1508 ## Examples
1509
1510 iex> Pleroma.User.list_inactive_users()
1511 %Ecto.Query{}
1512 """
1513 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1514 def list_inactive_users_query(inactivity_threshold \\ 7) do
1515 negative_inactivity_threshold = -inactivity_threshold
1516 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1517 # Subqueries are not supported in `where` clauses, join gets too complicated.
1518 has_read_notifications =
1519 from(n in Pleroma.Notification,
1520 where: n.seen == true,
1521 group_by: n.id,
1522 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1523 select: n.user_id
1524 )
1525 |> Pleroma.Repo.all()
1526
1527 from(u in Pleroma.User,
1528 left_join: a in Pleroma.Activity,
1529 on: u.ap_id == a.actor,
1530 where: not is_nil(u.nickname),
1531 where: u.deactivated != ^true,
1532 where: u.id not in ^has_read_notifications,
1533 group_by: u.id,
1534 having:
1535 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1536 is_nil(max(a.inserted_at))
1537 )
1538 end
1539
1540 @doc """
1541 Enable or disable email notifications for user
1542
1543 ## Examples
1544
1545 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1546 Pleroma.User{email_notifications: %{"digest" => true}}
1547
1548 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1549 Pleroma.User{email_notifications: %{"digest" => false}}
1550 """
1551 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1552 {:ok, t()} | {:error, Ecto.Changeset.t()}
1553 def switch_email_notifications(user, type, status) do
1554 User.update_email_notifications(user, %{type => status})
1555 end
1556
1557 @doc """
1558 Set `last_digest_emailed_at` value for the user to current time
1559 """
1560 @spec touch_last_digest_emailed_at(t()) :: t()
1561 def touch_last_digest_emailed_at(user) do
1562 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1563
1564 {:ok, updated_user} =
1565 user
1566 |> change(%{last_digest_emailed_at: now})
1567 |> update_and_set_cache()
1568
1569 updated_user
1570 end
1571
1572 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1573 def toggle_confirmation(%User{} = user) do
1574 user
1575 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1576 |> update_and_set_cache()
1577 end
1578
1579 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1580 def toggle_confirmation(users) do
1581 Enum.map(users, &toggle_confirmation/1)
1582 end
1583
1584 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1585 mascot
1586 end
1587
1588 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1589 # use instance-default
1590 config = Pleroma.Config.get([:assets, :mascots])
1591 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1592 mascot = Keyword.get(config, default_mascot)
1593
1594 %{
1595 "id" => "default-mascot",
1596 "url" => mascot[:url],
1597 "preview_url" => mascot[:url],
1598 "pleroma" => %{
1599 "mime_type" => mascot[:mime_type]
1600 }
1601 }
1602 end
1603
1604 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1605
1606 def ensure_keys_present(%User{} = user) do
1607 with {:ok, pem} <- Keys.generate_rsa_pem() do
1608 user
1609 |> cast(%{keys: pem}, [:keys])
1610 |> validate_required([:keys])
1611 |> update_and_set_cache()
1612 end
1613 end
1614
1615 def get_ap_ids_by_nicknames(nicknames) do
1616 from(u in User,
1617 where: u.nickname in ^nicknames,
1618 select: u.ap_id
1619 )
1620 |> Repo.all()
1621 end
1622
1623 defdelegate search(query, opts \\ []), to: User.Search
1624
1625 defp put_password_hash(
1626 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1627 ) do
1628 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1629 end
1630
1631 defp put_password_hash(changeset), do: changeset
1632
1633 def is_internal_user?(%User{nickname: nil}), do: true
1634 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1635 def is_internal_user?(_), do: false
1636
1637 # A hack because user delete activities have a fake id for whatever reason
1638 # TODO: Get rid of this
1639 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1640
1641 def get_delivered_users_by_object_id(object_id) do
1642 from(u in User,
1643 inner_join: delivery in assoc(u, :deliveries),
1644 where: delivery.object_id == ^object_id
1645 )
1646 |> Repo.all()
1647 end
1648
1649 def change_email(user, email) do
1650 user
1651 |> cast(%{email: email}, [:email])
1652 |> validate_required([:email])
1653 |> unique_constraint(:email)
1654 |> validate_format(:email, @email_regex)
1655 |> update_and_set_cache()
1656 end
1657
1658 # Internal function; public one is `deactivate/2`
1659 defp set_activation_status(user, deactivated) do
1660 user
1661 |> cast(%{deactivated: deactivated}, [:deactivated])
1662 |> update_and_set_cache()
1663 end
1664
1665 def update_banner(user, banner) do
1666 user
1667 |> cast(%{banner: banner}, [:banner])
1668 |> update_and_set_cache()
1669 end
1670
1671 def update_background(user, background) do
1672 user
1673 |> cast(%{background: background}, [:background])
1674 |> update_and_set_cache()
1675 end
1676
1677 def update_source_data(user, source_data) do
1678 user
1679 |> cast(%{source_data: source_data}, [:source_data])
1680 |> update_and_set_cache()
1681 end
1682
1683 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1684 %{
1685 admin: is_admin,
1686 moderator: is_moderator
1687 }
1688 end
1689
1690 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1691 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1692 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1693 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1694
1695 attachment
1696 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1697 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1698 |> Enum.take(limit)
1699 end
1700
1701 def fields(%{fields: nil}), do: []
1702
1703 def fields(%{fields: fields}), do: fields
1704
1705 def validate_fields(changeset, remote? \\ false) do
1706 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1707 limit = Pleroma.Config.get([:instance, limit_name], 0)
1708
1709 changeset
1710 |> validate_length(:fields, max: limit)
1711 |> validate_change(:fields, fn :fields, fields ->
1712 if Enum.all?(fields, &valid_field?/1) do
1713 []
1714 else
1715 [fields: "invalid"]
1716 end
1717 end)
1718 end
1719
1720 defp valid_field?(%{"name" => name, "value" => value}) do
1721 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1722 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1723
1724 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1725 String.length(value) <= value_limit
1726 end
1727
1728 defp valid_field?(_), do: false
1729
1730 defp truncate_field(%{"name" => name, "value" => value}) do
1731 {name, _chopped} =
1732 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1733
1734 {value, _chopped} =
1735 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1736
1737 %{"name" => name, "value" => value}
1738 end
1739
1740 def admin_api_update(user, params) do
1741 user
1742 |> cast(params, [
1743 :is_moderator,
1744 :is_admin,
1745 :show_role
1746 ])
1747 |> update_and_set_cache()
1748 end
1749
1750 def mascot_update(user, url) do
1751 user
1752 |> cast(%{mascot: url}, [:mascot])
1753 |> validate_required([:mascot])
1754 |> update_and_set_cache()
1755 end
1756
1757 def mastodon_settings_update(user, settings) do
1758 user
1759 |> cast(%{settings: settings}, [:settings])
1760 |> validate_required([:settings])
1761 |> update_and_set_cache()
1762 end
1763
1764 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1765 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1766 params =
1767 if need_confirmation? do
1768 %{
1769 confirmation_pending: true,
1770 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1771 }
1772 else
1773 %{
1774 confirmation_pending: false,
1775 confirmation_token: nil
1776 }
1777 end
1778
1779 cast(user, params, [:confirmation_pending, :confirmation_token])
1780 end
1781
1782 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1783 if id not in user.pinned_activities do
1784 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1785 params = %{pinned_activities: user.pinned_activities ++ [id]}
1786
1787 user
1788 |> cast(params, [:pinned_activities])
1789 |> validate_length(:pinned_activities,
1790 max: max_pinned_statuses,
1791 message: "You have already pinned the maximum number of statuses"
1792 )
1793 else
1794 change(user)
1795 end
1796 |> update_and_set_cache()
1797 end
1798
1799 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1800 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1801
1802 user
1803 |> cast(params, [:pinned_activities])
1804 |> update_and_set_cache()
1805 end
1806
1807 def update_email_notifications(user, settings) do
1808 email_notifications =
1809 user.email_notifications
1810 |> Map.merge(settings)
1811 |> Map.take(["digest"])
1812
1813 params = %{email_notifications: email_notifications}
1814 fields = [:email_notifications]
1815
1816 user
1817 |> cast(params, fields)
1818 |> validate_required(fields)
1819 |> update_and_set_cache()
1820 end
1821
1822 defp set_subscribers(user, subscribers) do
1823 params = %{subscribers: subscribers}
1824
1825 user
1826 |> cast(params, [:subscribers])
1827 |> validate_required([:subscribers])
1828 |> update_and_set_cache()
1829 end
1830
1831 def add_to_subscribers(user, subscribed) do
1832 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1833 end
1834
1835 def remove_from_subscribers(user, subscribed) do
1836 set_subscribers(user, List.delete(user.subscribers, subscribed))
1837 end
1838
1839 defp set_domain_blocks(user, domain_blocks) do
1840 params = %{domain_blocks: domain_blocks}
1841
1842 user
1843 |> cast(params, [:domain_blocks])
1844 |> validate_required([:domain_blocks])
1845 |> update_and_set_cache()
1846 end
1847
1848 def block_domain(user, domain_blocked) do
1849 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1850 end
1851
1852 def unblock_domain(user, domain_blocked) do
1853 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1854 end
1855
1856 defp set_blocks(user, blocks) do
1857 params = %{blocks: blocks}
1858
1859 user
1860 |> cast(params, [:blocks])
1861 |> validate_required([:blocks])
1862 |> update_and_set_cache()
1863 end
1864
1865 def add_to_block(user, blocked) do
1866 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1867 end
1868
1869 def remove_from_block(user, blocked) do
1870 set_blocks(user, List.delete(user.blocks, blocked))
1871 end
1872
1873 defp set_mutes(user, mutes) do
1874 params = %{mutes: mutes}
1875
1876 user
1877 |> cast(params, [:mutes])
1878 |> validate_required([:mutes])
1879 |> update_and_set_cache()
1880 end
1881
1882 def add_to_mutes(user, muted, notifications?) do
1883 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1884 set_notification_mutes(
1885 user,
1886 Enum.uniq([muted | user.muted_notifications]),
1887 notifications?
1888 )
1889 end
1890 end
1891
1892 def remove_from_mutes(user, muted) do
1893 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1894 set_notification_mutes(
1895 user,
1896 List.delete(user.muted_notifications, muted),
1897 true
1898 )
1899 end
1900 end
1901
1902 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1903 {:ok, user}
1904 end
1905
1906 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1907 params = %{muted_notifications: muted_notifications}
1908
1909 user
1910 |> cast(params, [:muted_notifications])
1911 |> validate_required([:muted_notifications])
1912 |> update_and_set_cache()
1913 end
1914
1915 def add_reblog_mute(user, ap_id) do
1916 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1917
1918 user
1919 |> cast(params, [:muted_reblogs])
1920 |> update_and_set_cache()
1921 end
1922
1923 def remove_reblog_mute(user, ap_id) do
1924 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1925
1926 user
1927 |> cast(params, [:muted_reblogs])
1928 |> update_and_set_cache()
1929 end
1930
1931 def set_invisible(user, invisible) do
1932 params = %{invisible: invisible}
1933
1934 user
1935 |> cast(params, [:invisible])
1936 |> validate_required([:invisible])
1937 |> update_and_set_cache()
1938 end
1939 end