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