Merge branch 'chat-moderation' into 'develop'
Chat moderation MVP See merge request pleroma/pleroma!2937
This commit is contained in:
commit
5426eb5997
|
@ -1334,3 +1334,124 @@ Loads json generated from `config/descriptions.exs`.
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## GET /api/pleroma/admin/users/:nickname/chats
|
||||||
|
|
||||||
|
### List a user's chats
|
||||||
|
|
||||||
|
- Params: None
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sender": {
|
||||||
|
"id": "someflakeid",
|
||||||
|
"username": "somenick",
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"receiver": {
|
||||||
|
"id": "someflakeid",
|
||||||
|
"username": "somenick",
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"id" : "1",
|
||||||
|
"unread" : 2,
|
||||||
|
"last_message" : {...}, // The last message in that chat
|
||||||
|
"updated_at": "2020-04-21T15:11:46.000Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## GET /api/pleroma/admin/chats/:chat_id
|
||||||
|
|
||||||
|
### View a single chat
|
||||||
|
|
||||||
|
- Params: None
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sender": {
|
||||||
|
"id": "someflakeid",
|
||||||
|
"username": "somenick",
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"receiver": {
|
||||||
|
"id": "someflakeid",
|
||||||
|
"username": "somenick",
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"id" : "1",
|
||||||
|
"unread" : 2,
|
||||||
|
"last_message" : {...}, // The last message in that chat
|
||||||
|
"updated_at": "2020-04-21T15:11:46.000Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## GET /api/pleroma/admin/chats/:chat_id/messages
|
||||||
|
|
||||||
|
### List the messages in a chat
|
||||||
|
|
||||||
|
- Params: `max_id`, `min_id`
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"account_id": "someflakeid",
|
||||||
|
"chat_id": "1",
|
||||||
|
"content": "Check this out :firefox:",
|
||||||
|
"created_at": "2020-04-21T15:11:46.000Z",
|
||||||
|
"emojis": [
|
||||||
|
{
|
||||||
|
"shortcode": "firefox",
|
||||||
|
"static_url": "https://dontbulling.me/emoji/Firefox.gif",
|
||||||
|
"url": "https://dontbulling.me/emoji/Firefox.gif",
|
||||||
|
"visible_in_picker": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "13",
|
||||||
|
"unread": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"account_id": "someflakeid",
|
||||||
|
"chat_id": "1",
|
||||||
|
"content": "Whats' up?",
|
||||||
|
"created_at": "2020-04-21T15:06:45.000Z",
|
||||||
|
"emojis": [],
|
||||||
|
"id": "12",
|
||||||
|
"unread": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## DELETE /api/pleroma/admin/chats/:chat_id/messages/:message_id
|
||||||
|
|
||||||
|
### Delete a single message
|
||||||
|
|
||||||
|
- Params: None
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"account_id": "someflakeid",
|
||||||
|
"chat_id": "1",
|
||||||
|
"content": "Check this out :firefox:",
|
||||||
|
"created_at": "2020-04-21T15:11:46.000Z",
|
||||||
|
"emojis": [
|
||||||
|
{
|
||||||
|
"shortcode": "firefox",
|
||||||
|
"static_url": "https://dontbulling.me/emoji/Firefox.gif",
|
||||||
|
"url": "https://dontbulling.me/emoji/Firefox.gif",
|
||||||
|
"visible_in_picker": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "13",
|
||||||
|
"unread": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -6,7 +6,9 @@ defmodule Pleroma.Chat do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
alias Pleroma.Chat
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@ -69,4 +71,12 @@ def bump_or_create(user_id, recipient) do
|
||||||
conflict_target: [:user_id, :recipient]
|
conflict_target: [:user_id, :recipient]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec for_user_query(FlakeId.Ecto.CompatType.t()) :: Ecto.Query.t()
|
||||||
|
def for_user_query(user_id) do
|
||||||
|
from(c in Chat,
|
||||||
|
where: c.user_id == ^user_id,
|
||||||
|
order_by: [desc: c.updated_at]
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -320,6 +320,19 @@ def insert_log(%{
|
||||||
|> insert_log_entry_with_message()
|
|> insert_log_entry_with_message()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) ::
|
||||||
|
{:ok, ModerationLog} | {:error, any}
|
||||||
|
def insert_log(%{actor: %User{} = actor, action: "chat_message_delete", subject_id: subject_id}) do
|
||||||
|
%ModerationLog{
|
||||||
|
data: %{
|
||||||
|
"actor" => %{"nickname" => actor.nickname},
|
||||||
|
"action" => "chat_message_delete",
|
||||||
|
"subject_id" => subject_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> insert_log_entry_with_message()
|
||||||
|
end
|
||||||
|
|
||||||
@spec insert_log_entry_with_message(ModerationLog) :: {:ok, ModerationLog} | {:error, any}
|
@spec insert_log_entry_with_message(ModerationLog) :: {:ok, ModerationLog} | {:error, any}
|
||||||
defp insert_log_entry_with_message(entry) do
|
defp insert_log_entry_with_message(entry) do
|
||||||
entry.data["message"]
|
entry.data["message"]
|
||||||
|
@ -627,6 +640,17 @@ def get_log_entry_message(%ModerationLog{
|
||||||
"@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}"
|
"@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||||
|
def get_log_entry_message(%ModerationLog{
|
||||||
|
data: %{
|
||||||
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
|
"action" => "chat_message_delete",
|
||||||
|
"subject_id" => subject_id
|
||||||
|
}
|
||||||
|
}) do
|
||||||
|
"@#{actor_nickname} deleted chat message ##{subject_id}"
|
||||||
|
end
|
||||||
|
|
||||||
defp nicknames_to_string(nicknames) do
|
defp nicknames_to_string(nicknames) do
|
||||||
nicknames
|
nicknames
|
||||||
|> Enum.map(&"@#{&1}")
|
|> Enum.map(&"@#{&1}")
|
||||||
|
|
|
@ -23,8 +23,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
alias Pleroma.Web.Endpoint
|
alias Pleroma.Web.Endpoint
|
||||||
alias Pleroma.Web.Router
|
alias Pleroma.Web.Router
|
||||||
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
@users_page_size 50
|
@users_page_size 50
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
|
@ -68,6 +66,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
when action in [:list_user_statuses, :list_instance_statuses]
|
when action in [:list_user_statuses, :list_instance_statuses]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
plug(
|
||||||
|
OAuthScopesPlug,
|
||||||
|
%{scopes: ["read:chats"], admin: true}
|
||||||
|
when action in [:list_user_chats]
|
||||||
|
)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["read"], admin: true}
|
%{scopes: ["read"], admin: true}
|
||||||
|
@ -256,6 +260,20 @@ def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickna
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = _params) do
|
||||||
|
with %User{id: user_id} <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
|
||||||
|
chats =
|
||||||
|
Pleroma.Chat.for_user_query(user_id)
|
||||||
|
|> Pleroma.Repo.all()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_view(AdminAPI.ChatView)
|
||||||
|
|> render("index.json", chats: chats)
|
||||||
|
else
|
||||||
|
_ -> {:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
|
def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
|
||||||
user = User.get_cached_by_nickname(nickname)
|
user = User.get_cached_by_nickname(nickname)
|
||||||
|
|
||||||
|
|
85
lib/pleroma/web/admin_api/controllers/chat_controller.ex
Normal file
85
lib/pleroma/web/admin_api/controllers/chat_controller.ex
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.AdminAPI.ChatController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Chat
|
||||||
|
alias Pleroma.Chat.MessageReference
|
||||||
|
alias Pleroma.ModerationLog
|
||||||
|
alias Pleroma.Pagination
|
||||||
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
|
alias Pleroma.Web.AdminAPI
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
|
||||||
|
plug(
|
||||||
|
OAuthScopesPlug,
|
||||||
|
%{scopes: ["read:chats"], admin: true} when action in [:show, :messages]
|
||||||
|
)
|
||||||
|
|
||||||
|
plug(
|
||||||
|
OAuthScopesPlug,
|
||||||
|
%{scopes: ["write:chats"], admin: true} when action in [:delete_message]
|
||||||
|
)
|
||||||
|
|
||||||
|
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
|
||||||
|
|
||||||
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ChatOperation
|
||||||
|
|
||||||
|
def delete_message(%{assigns: %{user: user}} = conn, %{
|
||||||
|
message_id: message_id,
|
||||||
|
id: chat_id
|
||||||
|
}) do
|
||||||
|
with %MessageReference{object: %{data: %{"id" => object_ap_id}}} = cm_ref <-
|
||||||
|
MessageReference.get_by_id(message_id),
|
||||||
|
^chat_id <- to_string(cm_ref.chat_id),
|
||||||
|
%Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object_ap_id),
|
||||||
|
{:ok, _} <- CommonAPI.delete(activity_id, user) do
|
||||||
|
ModerationLog.insert_log(%{
|
||||||
|
action: "chat_message_delete",
|
||||||
|
actor: user,
|
||||||
|
subject_id: message_id
|
||||||
|
})
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_view(MessageReferenceView)
|
||||||
|
|> render("show.json", chat_message_reference: cm_ref)
|
||||||
|
else
|
||||||
|
_e ->
|
||||||
|
{:error, :could_not_delete}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def messages(conn, %{id: id} = params) do
|
||||||
|
with %Chat{} = chat <- Chat.get_by_id(id) do
|
||||||
|
cm_refs =
|
||||||
|
chat
|
||||||
|
|> MessageReference.for_chat_query()
|
||||||
|
|> Pagination.fetch_paginated(params)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_view(MessageReferenceView)
|
||||||
|
|> render("index.json", chat_message_references: cm_refs)
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
conn
|
||||||
|
|> put_status(:not_found)
|
||||||
|
|> json(%{error: "not found"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show(conn, %{id: id}) do
|
||||||
|
with %Chat{} = chat <- Chat.get_by_id(id) do
|
||||||
|
conn
|
||||||
|
|> put_view(AdminAPI.ChatView)
|
||||||
|
|> render("show.json", chat: chat)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
30
lib/pleroma/web/admin_api/views/chat_view.ex
Normal file
30
lib/pleroma/web/admin_api/views/chat_view.ex
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.AdminAPI.ChatView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
alias Pleroma.Chat
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.MastodonAPI
|
||||||
|
alias Pleroma.Web.PleromaAPI
|
||||||
|
|
||||||
|
def render("index.json", %{chats: chats} = opts) do
|
||||||
|
render_many(chats, __MODULE__, "show.json", Map.delete(opts, :chats))
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("show.json", %{chat: %Chat{user_id: user_id}} = opts) do
|
||||||
|
user = User.get_by_id(user_id)
|
||||||
|
sender = MastodonAPI.AccountView.render("show.json", user: user, skip_visibility_check: true)
|
||||||
|
|
||||||
|
serialized_chat = PleromaAPI.ChatView.render("show.json", opts)
|
||||||
|
|
||||||
|
serialized_chat
|
||||||
|
|> Map.put(:sender, sender)
|
||||||
|
|> Map.put(:receiver, serialized_chat[:account])
|
||||||
|
|> Map.delete(:account)
|
||||||
|
end
|
||||||
|
|
||||||
|
def render(view, opts), do: PleromaAPI.ChatView.render(view, opts)
|
||||||
|
end
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.AdminAPI.StatusView do
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
alias Pleroma.Web.AdminAPI
|
alias Pleroma.Web.AdminAPI
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.MastodonAPI
|
alias Pleroma.Web.MastodonAPI
|
||||||
|
|
||||||
defdelegate merge_account_views(user), to: AdminAPI.AccountView
|
defdelegate merge_account_views(user), to: AdminAPI.AccountView
|
||||||
|
@ -17,7 +18,7 @@ def render("index.json", opts) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
|
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
|
||||||
user = MastodonAPI.StatusView.get_user(activity.data["actor"])
|
user = CommonAPI.get_user(activity.data["actor"])
|
||||||
|
|
||||||
MastodonAPI.StatusView.render("show.json", opts)
|
MastodonAPI.StatusView.render("show.json", opts)
|
||||||
|> Map.merge(%{account: merge_account_views(user)})
|
|> Map.merge(%{account: merge_account_views(user)})
|
||||||
|
|
96
lib/pleroma/web/api_spec/operations/admin/chat_operation.ex
Normal file
96
lib/pleroma/web/api_spec/operations/admin/chat_operation.ex
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do
|
||||||
|
alias OpenApiSpex.Operation
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.Chat
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.ChatMessage
|
||||||
|
|
||||||
|
import Pleroma.Web.ApiSpec.Helpers
|
||||||
|
|
||||||
|
def open_api_operation(action) do
|
||||||
|
operation = String.to_existing_atom("#{action}_operation")
|
||||||
|
apply(__MODULE__, operation, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_message_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["admin", "chat"],
|
||||||
|
summary: "Delete an individual chat message",
|
||||||
|
operationId: "AdminAPI.ChatController.delete_message",
|
||||||
|
parameters: [
|
||||||
|
Operation.parameter(:id, :path, :string, "The ID of the Chat"),
|
||||||
|
Operation.parameter(:message_id, :path, :string, "The ID of the message")
|
||||||
|
],
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response(
|
||||||
|
"The deleted ChatMessage",
|
||||||
|
"application/json",
|
||||||
|
ChatMessage
|
||||||
|
)
|
||||||
|
},
|
||||||
|
security: [
|
||||||
|
%{
|
||||||
|
"oAuth" => ["write:chats"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def messages_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["admin", "chat"],
|
||||||
|
summary: "Get the most recent messages of the chat",
|
||||||
|
operationId: "AdminAPI.ChatController.messages",
|
||||||
|
parameters:
|
||||||
|
[Operation.parameter(:id, :path, :string, "The ID of the Chat")] ++
|
||||||
|
pagination_params(),
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response(
|
||||||
|
"The messages in the chat",
|
||||||
|
"application/json",
|
||||||
|
Pleroma.Web.ApiSpec.ChatOperation.chat_messages_response()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
security: [
|
||||||
|
%{
|
||||||
|
"oAuth" => ["read:chats"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def show_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["chat"],
|
||||||
|
summary: "Create a chat",
|
||||||
|
operationId: "AdminAPI.ChatController.show",
|
||||||
|
parameters: [
|
||||||
|
Operation.parameter(
|
||||||
|
:id,
|
||||||
|
:path,
|
||||||
|
:string,
|
||||||
|
"The id of the chat",
|
||||||
|
required: true,
|
||||||
|
example: "1234"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response(
|
||||||
|
"The existing chat",
|
||||||
|
"application/json",
|
||||||
|
Chat
|
||||||
|
)
|
||||||
|
},
|
||||||
|
security: [
|
||||||
|
%{
|
||||||
|
"oAuth" => ["read"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -550,4 +550,21 @@ def hide_reblogs(%User{} = user, %User{} = target) do
|
||||||
def show_reblogs(%User{} = user, %User{} = target) do
|
def show_reblogs(%User{} = user, %User{} = target) do
|
||||||
UserRelationship.delete_reblog_mute(user, target)
|
UserRelationship.delete_reblog_mute(user, target)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_user(ap_id, fake_record_fallback \\ true) do
|
||||||
|
cond do
|
||||||
|
user = User.get_cached_by_ap_id(ap_id) ->
|
||||||
|
user
|
||||||
|
|
||||||
|
user = User.get_by_guessed_nickname(ap_id) ->
|
||||||
|
user
|
||||||
|
|
||||||
|
fake_record_fallback ->
|
||||||
|
# TODO: refactor (fake records is never a good idea)
|
||||||
|
User.error_user(ap_id)
|
||||||
|
|
||||||
|
true ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -55,23 +55,6 @@ defp get_replied_to_activities(activities) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_user(ap_id, fake_record_fallback \\ true) do
|
|
||||||
cond do
|
|
||||||
user = User.get_cached_by_ap_id(ap_id) ->
|
|
||||||
user
|
|
||||||
|
|
||||||
user = User.get_by_guessed_nickname(ap_id) ->
|
|
||||||
user
|
|
||||||
|
|
||||||
fake_record_fallback ->
|
|
||||||
# TODO: refactor (fake records is never a good idea)
|
|
||||||
User.error_user(ap_id)
|
|
||||||
|
|
||||||
true ->
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_context_id(%{data: %{"context_id" => context_id}}) when not is_nil(context_id),
|
defp get_context_id(%{data: %{"context_id" => context_id}}) when not is_nil(context_id),
|
||||||
do: context_id
|
do: context_id
|
||||||
|
|
||||||
|
@ -119,7 +102,7 @@ def render("index.json", opts) do
|
||||||
# Note: unresolved users are filtered out
|
# Note: unresolved users are filtered out
|
||||||
actors =
|
actors =
|
||||||
(activities ++ parent_activities)
|
(activities ++ parent_activities)
|
||||||
|> Enum.map(&get_user(&1.data["actor"], false))
|
|> Enum.map(&CommonAPI.get_user(&1.data["actor"], false))
|
||||||
|> Enum.filter(& &1)
|
|> Enum.filter(& &1)
|
||||||
|
|
||||||
UserRelationship.view_relationships_option(reading_user, actors, subset: :source_mutes)
|
UserRelationship.view_relationships_option(reading_user, actors, subset: :source_mutes)
|
||||||
|
@ -138,7 +121,7 @@ def render(
|
||||||
"show.json",
|
"show.json",
|
||||||
%{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts
|
%{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts
|
||||||
) do
|
) do
|
||||||
user = get_user(activity.data["actor"])
|
user = CommonAPI.get_user(activity.data["actor"])
|
||||||
created_at = Utils.to_masto_date(activity.data["published"])
|
created_at = Utils.to_masto_date(activity.data["published"])
|
||||||
activity_object = Object.normalize(activity)
|
activity_object = Object.normalize(activity)
|
||||||
|
|
||||||
|
@ -211,7 +194,7 @@ def render(
|
||||||
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
|
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
user = get_user(activity.data["actor"])
|
user = CommonAPI.get_user(activity.data["actor"])
|
||||||
user_follower_address = user.follower_address
|
user_follower_address = user.follower_address
|
||||||
|
|
||||||
like_count = object.data["like_count"] || 0
|
like_count = object.data["like_count"] || 0
|
||||||
|
@ -265,7 +248,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
|
|
||||||
reply_to = get_reply_to(activity, opts)
|
reply_to = get_reply_to(activity, opts)
|
||||||
|
|
||||||
reply_to_user = reply_to && get_user(reply_to.data["actor"])
|
reply_to_user = reply_to && CommonAPI.get_user(reply_to.data["actor"])
|
||||||
|
|
||||||
content =
|
content =
|
||||||
object
|
object
|
||||||
|
|
|
@ -146,11 +146,8 @@ def index(%{assigns: %{user: %{id: user_id} = user}} = conn, _params) do
|
||||||
blocked_ap_ids = User.blocked_users_ap_ids(user)
|
blocked_ap_ids = User.blocked_users_ap_ids(user)
|
||||||
|
|
||||||
chats =
|
chats =
|
||||||
from(c in Chat,
|
Chat.for_user_query(user_id)
|
||||||
where: c.user_id == ^user_id,
|
|> where([c], c.recipient not in ^blocked_ap_ids)
|
||||||
where: c.recipient not in ^blocked_ap_ids,
|
|
||||||
order_by: [desc: c.updated_at]
|
|
||||||
)
|
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -10,14 +10,14 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleView do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.HTML
|
alias Pleroma.HTML
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.CommonAPI.Utils
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
|
||||||
|
|
||||||
def render("show.json", %{activity: %Activity{data: %{"type" => "Listen"}} = activity} = opts) do
|
def render("show.json", %{activity: %Activity{data: %{"type" => "Listen"}} = activity} = opts) do
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
user = StatusView.get_user(activity.data["actor"])
|
user = CommonAPI.get_user(activity.data["actor"])
|
||||||
created_at = Utils.to_masto_date(activity.data["published"])
|
created_at = Utils.to_masto_date(activity.data["published"])
|
||||||
|
|
||||||
%{
|
%{
|
||||||
|
|
|
@ -178,6 +178,7 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/users", AdminAPIController, :list_users)
|
get("/users", AdminAPIController, :list_users)
|
||||||
get("/users/:nickname", AdminAPIController, :user_show)
|
get("/users/:nickname", AdminAPIController, :user_show)
|
||||||
get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
|
get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
|
||||||
|
get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
|
||||||
|
|
||||||
get("/instances/:instance/statuses", AdminAPIController, :list_instance_statuses)
|
get("/instances/:instance/statuses", AdminAPIController, :list_instance_statuses)
|
||||||
|
|
||||||
|
@ -214,6 +215,10 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/media_proxy_caches", MediaProxyCacheController, :index)
|
get("/media_proxy_caches", MediaProxyCacheController, :index)
|
||||||
post("/media_proxy_caches/delete", MediaProxyCacheController, :delete)
|
post("/media_proxy_caches/delete", MediaProxyCacheController, :delete)
|
||||||
post("/media_proxy_caches/purge", MediaProxyCacheController, :purge)
|
post("/media_proxy_caches/purge", MediaProxyCacheController, :purge)
|
||||||
|
|
||||||
|
get("/chats/:id", ChatController, :show)
|
||||||
|
get("/chats/:id/messages", ChatController, :messages)
|
||||||
|
delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
|
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
|
||||||
|
|
|
@ -1510,6 +1510,56 @@ test "excludes reblogs by default", %{conn: conn, user: user} do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "GET /api/pleroma/admin/users/:nickname/chats" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
recipients = insert_list(3, :user)
|
||||||
|
|
||||||
|
Enum.each(recipients, fn recipient ->
|
||||||
|
CommonAPI.post_chat_message(user, recipient, "yo")
|
||||||
|
end)
|
||||||
|
|
||||||
|
%{user: user}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders user's chats", %{conn: conn, user: user} do
|
||||||
|
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/chats")
|
||||||
|
|
||||||
|
assert json_response(conn, 200) |> length() == 3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "GET /api/pleroma/admin/users/:nickname/chats unauthorized" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
recipient = insert(:user)
|
||||||
|
CommonAPI.post_chat_message(user, recipient, "yo")
|
||||||
|
%{conn: conn} = oauth_access(["read:chats"])
|
||||||
|
%{conn: conn, user: user}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns 403", %{conn: conn, user: user} do
|
||||||
|
conn
|
||||||
|
|> get("/api/pleroma/admin/users/#{user.nickname}/chats")
|
||||||
|
|> json_response(403)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "GET /api/pleroma/admin/users/:nickname/chats unauthenticated" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
recipient = insert(:user)
|
||||||
|
CommonAPI.post_chat_message(user, recipient, "yo")
|
||||||
|
%{conn: build_conn(), user: user}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns 403", %{conn: conn, user: user} do
|
||||||
|
conn
|
||||||
|
|> get("/api/pleroma/admin/users/#{user.nickname}/chats")
|
||||||
|
|> json_response(403)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "GET /api/pleroma/admin/moderation_log" do
|
describe "GET /api/pleroma/admin/moderation_log" do
|
||||||
setup do
|
setup do
|
||||||
moderator = insert(:user, is_moderator: true)
|
moderator = insert(:user, is_moderator: true)
|
||||||
|
|
219
test/web/admin_api/controllers/chat_controller_test.exs
Normal file
219
test/web/admin_api/controllers/chat_controller_test.exs
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.AdminAPI.ChatControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
alias Pleroma.Chat
|
||||||
|
alias Pleroma.Chat.MessageReference
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.ModerationLog
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
defp admin_setup do
|
||||||
|
admin = insert(:user, is_admin: true)
|
||||||
|
token = insert(:oauth_admin_token, user: admin)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, admin)
|
||||||
|
|> assign(:token, token)
|
||||||
|
|
||||||
|
{:ok, %{admin: admin, token: token, conn: conn}}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "DELETE /api/pleroma/admin/chats/:id/messages/:message_id" do
|
||||||
|
setup do: admin_setup()
|
||||||
|
|
||||||
|
test "it deletes a message from the chat", %{conn: conn, admin: admin} do
|
||||||
|
user = insert(:user)
|
||||||
|
recipient = insert(:user)
|
||||||
|
|
||||||
|
{:ok, message} =
|
||||||
|
CommonAPI.post_chat_message(user, recipient, "Hello darkness my old friend")
|
||||||
|
|
||||||
|
object = Object.normalize(message, false)
|
||||||
|
|
||||||
|
chat = Chat.get(user.id, recipient.ap_id)
|
||||||
|
recipient_chat = Chat.get(recipient.id, user.ap_id)
|
||||||
|
|
||||||
|
cm_ref = MessageReference.for_chat_and_object(chat, object)
|
||||||
|
recipient_cm_ref = MessageReference.for_chat_and_object(recipient_chat, object)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> delete("/api/pleroma/admin/chats/#{chat.id}/messages/#{cm_ref.id}")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
log_entry = Repo.one(ModerationLog)
|
||||||
|
|
||||||
|
assert ModerationLog.get_log_entry_message(log_entry) ==
|
||||||
|
"@#{admin.nickname} deleted chat message ##{cm_ref.id}"
|
||||||
|
|
||||||
|
assert result["id"] == cm_ref.id
|
||||||
|
refute MessageReference.get_by_id(cm_ref.id)
|
||||||
|
refute MessageReference.get_by_id(recipient_cm_ref.id)
|
||||||
|
assert %{data: %{"type" => "Tombstone"}} = Object.get_by_id(object.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "GET /api/pleroma/admin/chats/:id/messages" do
|
||||||
|
setup do: admin_setup()
|
||||||
|
|
||||||
|
test "it paginates", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
recipient = insert(:user)
|
||||||
|
|
||||||
|
Enum.each(1..30, fn _ ->
|
||||||
|
{:ok, _} = CommonAPI.post_chat_message(user, recipient, "hey")
|
||||||
|
end)
|
||||||
|
|
||||||
|
chat = Chat.get(user.id, recipient.ap_id)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/pleroma/admin/chats/#{chat.id}/messages")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert length(result) == 20
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/pleroma/admin/chats/#{chat.id}/messages?max_id=#{List.last(result)["id"]}")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert length(result) == 10
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns the messages for a given chat", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
third_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _} = CommonAPI.post_chat_message(user, other_user, "hey")
|
||||||
|
{:ok, _} = CommonAPI.post_chat_message(user, third_user, "hey")
|
||||||
|
{:ok, _} = CommonAPI.post_chat_message(user, other_user, "how are you?")
|
||||||
|
{:ok, _} = CommonAPI.post_chat_message(other_user, user, "fine, how about you?")
|
||||||
|
|
||||||
|
chat = Chat.get(user.id, other_user.ap_id)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/pleroma/admin/chats/#{chat.id}/messages")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
result
|
||||||
|
|> Enum.each(fn message ->
|
||||||
|
assert message["chat_id"] == chat.id |> to_string()
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert length(result) == 3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "GET /api/pleroma/admin/chats/:id" do
|
||||||
|
setup do: admin_setup()
|
||||||
|
|
||||||
|
test "it returns a chat", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/pleroma/admin/chats/#{chat.id}")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert result["id"] == to_string(chat.id)
|
||||||
|
assert %{} = result["sender"]
|
||||||
|
assert %{} = result["receiver"]
|
||||||
|
refute result["account"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "unauthorized chat moderation" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
recipient = insert(:user)
|
||||||
|
|
||||||
|
{:ok, message} = CommonAPI.post_chat_message(user, recipient, "Yo")
|
||||||
|
object = Object.normalize(message, false)
|
||||||
|
chat = Chat.get(user.id, recipient.ap_id)
|
||||||
|
cm_ref = MessageReference.for_chat_and_object(chat, object)
|
||||||
|
|
||||||
|
%{conn: conn} = oauth_access(["read:chats", "write:chats"])
|
||||||
|
%{conn: conn, chat: chat, cm_ref: cm_ref}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "DELETE /api/pleroma/admin/chats/:id/messages/:message_id", %{
|
||||||
|
conn: conn,
|
||||||
|
chat: chat,
|
||||||
|
cm_ref: cm_ref
|
||||||
|
} do
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> delete("/api/pleroma/admin/chats/#{chat.id}/messages/#{cm_ref.id}")
|
||||||
|
|> json_response(403)
|
||||||
|
|
||||||
|
assert MessageReference.get_by_id(cm_ref.id) == cm_ref
|
||||||
|
end
|
||||||
|
|
||||||
|
test "GET /api/pleroma/admin/chats/:id/messages", %{conn: conn, chat: chat} do
|
||||||
|
conn
|
||||||
|
|> get("/api/pleroma/admin/chats/#{chat.id}/messages")
|
||||||
|
|> json_response(403)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "GET /api/pleroma/admin/chats/:id", %{conn: conn, chat: chat} do
|
||||||
|
conn
|
||||||
|
|> get("/api/pleroma/admin/chats/#{chat.id}")
|
||||||
|
|> json_response(403)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "unauthenticated chat moderation" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
recipient = insert(:user)
|
||||||
|
|
||||||
|
{:ok, message} = CommonAPI.post_chat_message(user, recipient, "Yo")
|
||||||
|
object = Object.normalize(message, false)
|
||||||
|
chat = Chat.get(user.id, recipient.ap_id)
|
||||||
|
cm_ref = MessageReference.for_chat_and_object(chat, object)
|
||||||
|
|
||||||
|
%{conn: build_conn(), chat: chat, cm_ref: cm_ref}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "DELETE /api/pleroma/admin/chats/:id/messages/:message_id", %{
|
||||||
|
conn: conn,
|
||||||
|
chat: chat,
|
||||||
|
cm_ref: cm_ref
|
||||||
|
} do
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> delete("/api/pleroma/admin/chats/#{chat.id}/messages/#{cm_ref.id}")
|
||||||
|
|> json_response(403)
|
||||||
|
|
||||||
|
assert MessageReference.get_by_id(cm_ref.id) == cm_ref
|
||||||
|
end
|
||||||
|
|
||||||
|
test "GET /api/pleroma/admin/chats/:id/messages", %{conn: conn, chat: chat} do
|
||||||
|
conn
|
||||||
|
|> get("/api/pleroma/admin/chats/#{chat.id}/messages")
|
||||||
|
|> json_response(403)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "GET /api/pleroma/admin/chats/:id", %{conn: conn, chat: chat} do
|
||||||
|
conn
|
||||||
|
|> get("/api/pleroma/admin/chats/#{chat.id}")
|
||||||
|
|> json_response(403)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1193,4 +1193,24 @@ test "respects visibility=private" do
|
||||||
assert Visibility.get_visibility(activity) == "private"
|
assert Visibility.get_visibility(activity) == "private"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "get_user/1" do
|
||||||
|
test "gets user by ap_id" do
|
||||||
|
user = insert(:user)
|
||||||
|
assert CommonAPI.get_user(user.ap_id) == user
|
||||||
|
end
|
||||||
|
|
||||||
|
test "gets user by guessed nickname" do
|
||||||
|
user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom")
|
||||||
|
assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user
|
||||||
|
end
|
||||||
|
|
||||||
|
test "fallback" do
|
||||||
|
assert %User{
|
||||||
|
name: "",
|
||||||
|
ap_id: "",
|
||||||
|
nickname: "erroruser@example.com"
|
||||||
|
} = CommonAPI.get_user("")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue