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