1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.ModerationLog do
9 alias Pleroma.ModerationLog
15 @type t :: %__MODULE__{}
16 @type log_subject :: Activity.t() | User.t() | list(User.t())
17 @type log_params :: %{
18 required(:actor) => User.t(),
19 required(:action) => String.t(),
20 optional(:subject) => log_subject(),
21 optional(:subject_actor) => User.t(),
22 optional(:subject_id) => String.t(),
23 optional(:subjects) => list(User.t()),
24 optional(:permission) => String.t(),
25 optional(:text) => String.t(),
26 optional(:sensitive) => String.t(),
27 optional(:visibility) => String.t(),
28 optional(:followed) => User.t(),
29 optional(:follower) => User.t(),
30 optional(:nicknames) => list(String.t()),
31 optional(:tags) => list(String.t()),
32 optional(:target) => String.t()
35 schema "moderation_log" do
41 def get_all(params) do
44 |> maybe_filter_by_date(params)
45 |> maybe_filter_by_user(params)
46 |> maybe_filter_by_search(params)
48 query_with_pagination = base_query |> paginate_query(params)
51 items: Repo.all(query_with_pagination),
52 count: Repo.aggregate(base_query, :count, :id)
56 defp maybe_filter_by_date(query, %{start_date: nil, end_date: nil}), do: query
58 defp maybe_filter_by_date(query, %{start_date: start_date, end_date: nil}) do
60 where: q.inserted_at >= ^parse_datetime(start_date)
64 defp maybe_filter_by_date(query, %{start_date: nil, end_date: end_date}) do
66 where: q.inserted_at <= ^parse_datetime(end_date)
70 defp maybe_filter_by_date(query, %{start_date: start_date, end_date: end_date}) do
72 where: q.inserted_at >= ^parse_datetime(start_date),
73 where: q.inserted_at <= ^parse_datetime(end_date)
77 defp maybe_filter_by_user(query, %{user_id: nil}), do: query
79 defp maybe_filter_by_user(query, %{user_id: user_id}) do
81 where: fragment("(?)->'actor'->>'id' = ?", q.data, ^user_id)
85 defp maybe_filter_by_search(query, %{search: search}) when is_nil(search) or search == "",
88 defp maybe_filter_by_search(query, %{search: search}) do
90 where: fragment("(?)->>'message' ILIKE ?", q.data, ^"%#{search}%")
94 defp paginate_query(query, %{page: page, page_size: page_size}) do
97 offset: ^((page - 1) * page_size)
101 defp get_all_query do
102 from(q in __MODULE__,
103 order_by: [desc: q.inserted_at]
107 defp parse_datetime(datetime) do
108 {:ok, parsed_datetime, _} = DateTime.from_iso8601(datetime)
113 defp prepare_log_data(%{actor: actor, action: action} = attrs) do
115 "actor" => user_to_map(actor),
119 |> Pleroma.Maps.put_if_present("subject_actor", user_to_map(attrs[:subject_actor]))
122 defp prepare_log_data(attrs), do: attrs
124 @spec insert_log(log_params()) :: {:ok, ModerationLog.t()} | {:error, any}
125 def insert_log(%{actor: %User{}, subject: subjects, permission: permission} = attrs) do
129 |> Map.merge(%{"subject" => user_to_map(subjects), "permission" => permission})
131 insert_log_entry_with_message(%ModerationLog{data: data})
134 def insert_log(%{actor: %User{}, action: action, subject: %Activity{} = subject} = attrs)
135 when action in ["report_note_delete", "report_update", "report_note"] do
139 |> Pleroma.Maps.put_if_present("text", attrs[:text])
140 |> Map.merge(%{"subject" => report_to_map(subject)})
142 insert_log_entry_with_message(%ModerationLog{data: data})
149 subject: %Activity{} = subject,
150 sensitive: sensitive,
151 visibility: visibility
154 when action == "status_update" do
159 "subject" => status_to_map(subject),
160 "sensitive" => sensitive,
161 "visibility" => visibility
164 insert_log_entry_with_message(%ModerationLog{data: data})
167 def insert_log(%{actor: %User{}, action: action, subject_id: subject_id} = attrs)
168 when action == "status_delete" do
172 |> Map.merge(%{"subject_id" => subject_id})
174 insert_log_entry_with_message(%ModerationLog{data: data})
177 def insert_log(%{actor: %User{}, subject: subject, action: _action} = attrs) do
181 |> Map.merge(%{"subject" => user_to_map(subject)})
183 insert_log_entry_with_message(%ModerationLog{data: data})
186 def insert_log(%{actor: %User{}, subjects: subjects, action: _action} = attrs) do
190 |> Map.merge(%{"subjects" => user_to_map(subjects)})
192 insert_log_entry_with_message(%ModerationLog{data: data})
198 followed: %User{} = followed,
199 follower: %User{} = follower,
203 when action in ["unfollow", "follow"] do
207 |> Map.merge(%{"followed" => user_to_map(followed), "follower" => user_to_map(follower)})
209 insert_log_entry_with_message(%ModerationLog{data: data})
213 actor: %User{} = actor,
214 nicknames: nicknames,
220 "actor" => user_to_map(actor),
221 "nicknames" => nicknames,
227 |> insert_log_entry_with_message()
230 def insert_log(%{actor: %User{}, action: action, target: target} = attrs)
231 when action in ["relay_follow", "relay_unfollow"] do
235 |> Map.merge(%{"target" => target})
237 insert_log_entry_with_message(%ModerationLog{data: data})
240 def insert_log(%{actor: %User{} = actor, action: "chat_message_delete", subject_id: subject_id}) do
243 "actor" => %{"nickname" => actor.nickname},
244 "action" => "chat_message_delete",
245 "subject_id" => subject_id
248 |> insert_log_entry_with_message()
251 @spec insert_log_entry_with_message(ModerationLog.t()) ::
252 {:ok, ModerationLog.t()} | {:error, any}
253 defp insert_log_entry_with_message(entry) do
254 entry.data["message"]
255 |> put_in(get_log_entry_message(entry))
259 defp user_to_map(users) when is_list(users) do
260 Enum.map(users, &user_to_map/1)
263 defp user_to_map(%User{} = user) do
265 |> Map.take([:id, :nickname])
266 |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end)
267 |> Map.put("type", "user")
270 defp user_to_map(_), do: nil
272 defp report_to_map(%Activity{} = report) do
273 %{"type" => "report", "id" => report.id, "state" => report.data["state"]}
276 defp status_to_map(%Activity{} = status) do
277 %{"type" => "status", "id" => status.id}
280 @spec get_log_entry_message(ModerationLog.t()) :: String.t()
281 def get_log_entry_message(%ModerationLog{
283 "actor" => %{"nickname" => actor_nickname},
285 "followed" => %{"nickname" => followed_nickname},
286 "follower" => %{"nickname" => follower_nickname}
289 "@#{actor_nickname} made @#{follower_nickname} #{action} @#{followed_nickname}"
292 def get_log_entry_message(%ModerationLog{
294 "actor" => %{"nickname" => actor_nickname},
295 "action" => "delete",
296 "subject" => subjects
299 "@#{actor_nickname} deleted users: #{users_to_nicknames_string(subjects)}"
302 def get_log_entry_message(%ModerationLog{
304 "actor" => %{"nickname" => actor_nickname},
305 "action" => "create",
306 "subjects" => subjects
309 "@#{actor_nickname} created users: #{users_to_nicknames_string(subjects)}"
312 def get_log_entry_message(%ModerationLog{
314 "actor" => %{"nickname" => actor_nickname},
315 "action" => "activate",
319 "@#{actor_nickname} activated users: #{users_to_nicknames_string(users)}"
322 def get_log_entry_message(%ModerationLog{
324 "actor" => %{"nickname" => actor_nickname},
325 "action" => "deactivate",
329 "@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}"
332 def get_log_entry_message(%ModerationLog{
334 "actor" => %{"nickname" => actor_nickname},
335 "action" => "approve",
339 "@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
342 def get_log_entry_message(%ModerationLog{
344 "actor" => %{"nickname" => actor_nickname},
345 "action" => "add_suggestion",
349 "@#{actor_nickname} added suggested users: #{users_to_nicknames_string(users)}"
352 def get_log_entry_message(%ModerationLog{
354 "actor" => %{"nickname" => actor_nickname},
355 "action" => "remove_suggestion",
359 "@#{actor_nickname} removed suggested users: #{users_to_nicknames_string(users)}"
362 def get_log_entry_message(%ModerationLog{
364 "actor" => %{"nickname" => actor_nickname},
365 "nicknames" => nicknames,
370 tags_string = tags |> Enum.join(", ")
372 "@#{actor_nickname} added tags: #{tags_string} to users: #{nicknames_to_string(nicknames)}"
375 def get_log_entry_message(%ModerationLog{
377 "actor" => %{"nickname" => actor_nickname},
378 "nicknames" => nicknames,
383 tags_string = tags |> Enum.join(", ")
385 "@#{actor_nickname} removed tags: #{tags_string} from users: #{nicknames_to_string(nicknames)}"
388 def get_log_entry_message(%ModerationLog{
390 "actor" => %{"nickname" => actor_nickname},
393 "permission" => permission
396 "@#{actor_nickname} made #{users_to_nicknames_string(users)} #{permission}"
399 def get_log_entry_message(%ModerationLog{
401 "actor" => %{"nickname" => actor_nickname},
402 "action" => "revoke",
404 "permission" => permission
407 "@#{actor_nickname} revoked #{permission} role from #{users_to_nicknames_string(users)}"
410 def get_log_entry_message(%ModerationLog{
412 "actor" => %{"nickname" => actor_nickname},
413 "action" => "relay_follow",
417 "@#{actor_nickname} followed relay: #{target}"
420 def get_log_entry_message(%ModerationLog{
422 "actor" => %{"nickname" => actor_nickname},
423 "action" => "relay_unfollow",
427 "@#{actor_nickname} unfollowed relay: #{target}"
430 def get_log_entry_message(
433 "actor" => %{"nickname" => actor_nickname},
434 "action" => "report_update",
435 "subject" => %{"id" => subject_id, "state" => state, "type" => "report"}
439 "@#{actor_nickname} updated report ##{subject_id}" <>
440 subject_actor_nickname(log, " (on user ", ")") <>
441 " with '#{state}' state"
444 def get_log_entry_message(
447 "actor" => %{"nickname" => actor_nickname},
448 "action" => "report_note",
449 "subject" => %{"id" => subject_id, "type" => "report"},
454 "@#{actor_nickname} added note '#{text}' to report ##{subject_id}" <>
455 subject_actor_nickname(log, " on user ")
458 def get_log_entry_message(
461 "actor" => %{"nickname" => actor_nickname},
462 "action" => "report_note_delete",
463 "subject" => %{"id" => subject_id, "type" => "report"},
468 "@#{actor_nickname} deleted note '#{text}' from report ##{subject_id}" <>
469 subject_actor_nickname(log, " on user ")
472 def get_log_entry_message(%ModerationLog{
474 "actor" => %{"nickname" => actor_nickname},
475 "action" => "status_update",
476 "subject" => %{"id" => subject_id, "type" => "status"},
478 "visibility" => visibility
481 "@#{actor_nickname} updated status ##{subject_id}, set visibility: '#{visibility}'"
484 def get_log_entry_message(%ModerationLog{
486 "actor" => %{"nickname" => actor_nickname},
487 "action" => "status_update",
488 "subject" => %{"id" => subject_id, "type" => "status"},
489 "sensitive" => sensitive,
493 "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}'"
496 def get_log_entry_message(%ModerationLog{
498 "actor" => %{"nickname" => actor_nickname},
499 "action" => "status_update",
500 "subject" => %{"id" => subject_id, "type" => "status"},
501 "sensitive" => sensitive,
502 "visibility" => visibility
505 "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{visibility}'"
508 def get_log_entry_message(%ModerationLog{
510 "actor" => %{"nickname" => actor_nickname},
511 "action" => "status_delete",
512 "subject_id" => subject_id
515 "@#{actor_nickname} deleted status ##{subject_id}"
518 def get_log_entry_message(%ModerationLog{
520 "actor" => %{"nickname" => actor_nickname},
521 "action" => "force_password_reset",
522 "subject" => subjects
525 "@#{actor_nickname} forced password reset for users: #{users_to_nicknames_string(subjects)}"
528 def get_log_entry_message(%ModerationLog{
530 "actor" => %{"nickname" => actor_nickname},
531 "action" => "confirm_email",
532 "subject" => subjects
535 "@#{actor_nickname} confirmed email for users: #{users_to_nicknames_string(subjects)}"
538 def get_log_entry_message(%ModerationLog{
540 "actor" => %{"nickname" => actor_nickname},
541 "action" => "resend_confirmation_email",
542 "subject" => subjects
545 "@#{actor_nickname} re-sent confirmation email for users: #{users_to_nicknames_string(subjects)}"
548 def get_log_entry_message(%ModerationLog{
550 "actor" => %{"nickname" => actor_nickname},
551 "action" => "updated_users",
552 "subject" => subjects
555 "@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}"
558 def get_log_entry_message(%ModerationLog{
560 "actor" => %{"nickname" => actor_nickname},
561 "action" => "chat_message_delete",
562 "subject_id" => subject_id
565 "@#{actor_nickname} deleted chat message ##{subject_id}"
568 def get_log_entry_message(%ModerationLog{
570 "actor" => %{"nickname" => actor_nickname},
571 "action" => "create_backup",
572 "subject" => %{"nickname" => user_nickname}
575 "@#{actor_nickname} requested account backup for @#{user_nickname}"
578 defp nicknames_to_string(nicknames) do
580 |> Enum.map(&"@#{&1}")
584 defp users_to_nicknames_string(users) do
586 |> Enum.map(&"@#{&1["nickname"]}")
590 defp subject_actor_nickname(%ModerationLog{data: data}, prefix_msg, postfix_msg \\ "") do
592 %{"subject_actor" => %{"nickname" => subject_actor}} ->
593 [prefix_msg, "@#{subject_actor}", postfix_msg]
594 |> Enum.reject(&(&1 == ""))