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