parent
07ea4d73e1
commit
0f132b802d
@ -1,255 +0,0 @@
|
|||||||
# Chats
|
|
||||||
|
|
||||||
Chats are a way to represent an IM-style conversation between two actors. They are not the same as direct messages and they are not `Status`es, even though they have a lot in common.
|
|
||||||
|
|
||||||
## Why Chats?
|
|
||||||
|
|
||||||
There are no 'visibility levels' in ActivityPub, their definition is purely a Mastodon convention. Direct Messaging between users on the fediverse has mostly been modeled by using ActivityPub addressing following Mastodon conventions on normal `Note` objects. In this case, a 'direct message' would be a message that has no followers addressed and also does not address the special public actor, but just the recipients in the `to` field. It would still be a `Note` and is presented with other `Note`s as a `Status` in the API.
|
|
||||||
|
|
||||||
This is an awkward setup for a few reasons:
|
|
||||||
|
|
||||||
- As DMs generally still follow the usual `Status` conventions, it is easy to accidentally pull somebody into a DM thread by mentioning them. (e.g. "I hate @badguy so much")
|
|
||||||
- It is possible to go from a publicly addressed `Status` to a DM reply, back to public, then to a 'followers only' reply, and so on. This can be become very confusing, as it is unclear which user can see which part of the conversation.
|
|
||||||
- The standard `Status` format of implicit addressing also leads to rather ugly results if you try to display the messages as a chat, because all the recipients are always mentioned by name in the message.
|
|
||||||
- As direct messages are posted with the same api call (and usually same frontend component) as public messages, accidentally making a public message private or vice versa can happen easily. Client bugs can also lead to this, accidentally making private messages public.
|
|
||||||
|
|
||||||
As a measure to improve this situation, the `Conversation` concept and related Akkoma extensions were introduced. While it made it possible to work around a few of the issues, many of the problems remained and it didn't see much adoption because it was too complicated to use correctly.
|
|
||||||
|
|
||||||
## Chats explained
|
|
||||||
For this reasons, Chats are a new and different entity, both in the API as well as in ActivityPub. A quick overview:
|
|
||||||
|
|
||||||
- Chats are meant to represent an instant message conversation between two actors. For now these are only 1-on-1 conversations, but the other actor can be a group in the future.
|
|
||||||
- Chat messages have the ActivityPub type `ChatMessage`. They are not `Note`s. Servers that don't understand them will just drop them.
|
|
||||||
- The only addressing allowed in `ChatMessage`s is one single ActivityPub actor in the `to` field.
|
|
||||||
- There's always only one Chat between two actors. If you start chatting with someone and later start a 'new' Chat, the old Chat will be continued.
|
|
||||||
- `ChatMessage`s are posted with a different api, making it very hard to accidentally send a message to the wrong person.
|
|
||||||
- `ChatMessage`s don't show up in the existing timelines.
|
|
||||||
- Chats can never go from private to public. They are always private between the two actors.
|
|
||||||
|
|
||||||
## Caveats
|
|
||||||
|
|
||||||
- Chats are NOT E2E encrypted (yet). Security is still the same as email.
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
In general, the way to send a `ChatMessage` is to first create a `Chat`, then post a message to that `Chat`. `Group`s will later be supported by making them a sub-type of `Account`.
|
|
||||||
|
|
||||||
This is the overview of using the API. The API is also documented via OpenAPI, so you can view it and play with it by pointing SwaggerUI or a similar OpenAPI tool to `https://yourinstance.tld/api/openapi`.
|
|
||||||
|
|
||||||
### Creating or getting a chat.
|
|
||||||
|
|
||||||
To create or get an existing Chat for a certain recipient (identified by Account ID)
|
|
||||||
you can call:
|
|
||||||
|
|
||||||
`POST /api/v1/pleroma/chats/by-account-id/:account_id`
|
|
||||||
|
|
||||||
The account id is the normal FlakeId of the user
|
|
||||||
```
|
|
||||||
POST /api/v1/pleroma/chats/by-account-id/someflakeid
|
|
||||||
```
|
|
||||||
|
|
||||||
If you already have the id of a chat, you can also use
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/v1/pleroma/chats/:id
|
|
||||||
```
|
|
||||||
|
|
||||||
There will only ever be ONE Chat for you and a given recipient, so this call
|
|
||||||
will return the same Chat if you already have one with that user.
|
|
||||||
|
|
||||||
Returned data:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"account": {
|
|
||||||
"id": "someflakeid",
|
|
||||||
"username": "somenick",
|
|
||||||
...
|
|
||||||
},
|
|
||||||
"id" : "1",
|
|
||||||
"unread" : 2,
|
|
||||||
"last_message" : {...}, // The last message in that chat
|
|
||||||
"updated_at": "2020-04-21T15:11:46.000Z"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Marking a chat as read
|
|
||||||
|
|
||||||
To mark a number of messages in a chat up to a certain message as read, you can use
|
|
||||||
|
|
||||||
`POST /api/v1/pleroma/chats/:id/read`
|
|
||||||
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- last_read_id: Given this id, all chat messages until this one will be marked as read. Required.
|
|
||||||
|
|
||||||
|
|
||||||
Returned data:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"account": {
|
|
||||||
"id": "someflakeid",
|
|
||||||
"username": "somenick",
|
|
||||||
...
|
|
||||||
},
|
|
||||||
"id" : "1",
|
|
||||||
"unread" : 0,
|
|
||||||
"updated_at": "2020-04-21T15:11:46.000Z"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Marking a single chat message as read
|
|
||||||
|
|
||||||
To set the `unread` property of a message to `false`
|
|
||||||
|
|
||||||
`POST /api/v1/pleroma/chats/:id/messages/:message_id/read`
|
|
||||||
|
|
||||||
Returned data:
|
|
||||||
|
|
||||||
The modified chat message
|
|
||||||
|
|
||||||
### Getting a list of Chats
|
|
||||||
|
|
||||||
`GET /api/v1/pleroma/chats`
|
|
||||||
|
|
||||||
This will return a list of chats that you have been involved in, sorted by their
|
|
||||||
last update (so new chats will be at the top).
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
|
|
||||||
- with_muted: Include chats from muted users (boolean).
|
|
||||||
|
|
||||||
Returned data:
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"account": {
|
|
||||||
"id": "someflakeid",
|
|
||||||
"username": "somenick",
|
|
||||||
...
|
|
||||||
},
|
|
||||||
"id" : "1",
|
|
||||||
"unread" : 2,
|
|
||||||
"last_message" : {...}, // The last message in that chat
|
|
||||||
"updated_at": "2020-04-21T15:11:46.000Z"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
The recipient of messages that are sent to this chat is given by their AP ID.
|
|
||||||
No pagination is implemented for now.
|
|
||||||
|
|
||||||
### Getting the messages for a Chat
|
|
||||||
|
|
||||||
For a given Chat id, you can get the associated messages with
|
|
||||||
|
|
||||||
`GET /api/v1/pleroma/chats/:id/messages`
|
|
||||||
|
|
||||||
This will return all messages, sorted by most recent to least recent. The usual
|
|
||||||
pagination options are implemented.
|
|
||||||
|
|
||||||
Returned data:
|
|
||||||
|
|
||||||
```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,
|
|
||||||
"idempotency_key": "75442486-0874-440c-9db1-a7006c25a31f"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
- idempotency_key: The copy of the `idempotency-key` HTTP request header that can be used for optimistic message sending. Included only during the first few minutes after the message creation.
|
|
||||||
|
|
||||||
### Posting a chat message
|
|
||||||
|
|
||||||
Posting a chat message for given Chat id works like this:
|
|
||||||
|
|
||||||
`POST /api/v1/pleroma/chats/:id/messages`
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- content: The text content of the message. Optional if media is attached.
|
|
||||||
- media_id: The id of an upload that will be attached to the message.
|
|
||||||
|
|
||||||
Currently, no formatting beyond basic escaping and emoji is implemented.
|
|
||||||
|
|
||||||
Returned data:
|
|
||||||
|
|
||||||
```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
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Deleting a chat message
|
|
||||||
|
|
||||||
Deleting a chat message for given Chat id works like this:
|
|
||||||
|
|
||||||
`DELETE /api/v1/pleroma/chats/:chat_id/messages/:message_id`
|
|
||||||
|
|
||||||
Returned data is the deleted message.
|
|
||||||
|
|
||||||
### Notifications
|
|
||||||
|
|
||||||
There's a new `pleroma:chat_mention` notification, which has this form. It is not given out in the notifications endpoint by default, you need to explicitly request it with `include_types[]=pleroma:chat_mention`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "someid",
|
|
||||||
"type": "pleroma:chat_mention",
|
|
||||||
"account": { ... } // User account of the sender,
|
|
||||||
"chat_message": {
|
|
||||||
"chat_id": "1",
|
|
||||||
"id": "10",
|
|
||||||
"content": "Hello",
|
|
||||||
"account_id": "someflakeid",
|
|
||||||
"unread": false
|
|
||||||
},
|
|
||||||
"created_at": "somedate"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Streaming
|
|
||||||
|
|
||||||
There is an additional `user:pleroma_chat` stream. Incoming chat messages will make the current chat be sent to this `user` stream. The `event` of an incoming chat message is `pleroma:chat_update`. The payload is the updated chat with the incoming chat message in the `last_message` field.
|
|
||||||
|
|
||||||
### Web Push
|
|
||||||
|
|
||||||
If you want to receive push messages for this type, you'll need to add the `pleroma:chat_mention` type to your alerts in the push subscription.
|
|
@ -1,97 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Chat do
|
|
||||||
use Ecto.Schema
|
|
||||||
|
|
||||||
import Ecto.Changeset
|
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
alias Pleroma.Chat
|
|
||||||
alias Pleroma.Repo
|
|
||||||
alias Pleroma.User
|
|
||||||
|
|
||||||
@moduledoc """
|
|
||||||
Chat keeps a reference to ChatMessage conversations between a user and an recipient. The recipient can be a user (for now) or a group (not implemented yet).
|
|
||||||
|
|
||||||
It is a helper only, to make it easy to display a list of chats with other people, ordered by last bump. The actual messages are retrieved by querying the recipients of the ChatMessages.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
|
||||||
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
|
|
||||||
|
|
||||||
schema "chats" do
|
|
||||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
|
||||||
field(:recipient, :string)
|
|
||||||
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
def changeset(struct, params) do
|
|
||||||
struct
|
|
||||||
|> cast(params, [:user_id, :recipient])
|
|
||||||
|> validate_change(:recipient, fn
|
|
||||||
:recipient, recipient ->
|
|
||||||
case User.get_cached_by_ap_id(recipient) do
|
|
||||||
nil -> [recipient: "must be an existing user"]
|
|
||||||
_ -> []
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|> validate_required([:user_id, :recipient])
|
|
||||||
|> unique_constraint(:user_id, name: :chats_user_id_recipient_index)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec get_by_user_and_id(User.t(), FlakeId.Ecto.CompatType.t()) ::
|
|
||||||
{:ok, t()} | {:error, :not_found}
|
|
||||||
def get_by_user_and_id(%User{id: user_id}, id) do
|
|
||||||
from(c in __MODULE__,
|
|
||||||
where: c.id == ^id,
|
|
||||||
where: c.user_id == ^user_id
|
|
||||||
)
|
|
||||||
|> Repo.find_resource()
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec get_by_id(FlakeId.Ecto.CompatType.t()) :: t() | nil
|
|
||||||
def get_by_id(id) do
|
|
||||||
Repo.get(__MODULE__, id)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec get(FlakeId.Ecto.CompatType.t(), String.t()) :: t() | nil
|
|
||||||
def get(user_id, recipient) do
|
|
||||||
Repo.get_by(__MODULE__, user_id: user_id, recipient: recipient)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec get_or_create(FlakeId.Ecto.CompatType.t(), String.t()) ::
|
|
||||||
{:ok, t()} | {:error, Ecto.Changeset.t()}
|
|
||||||
def get_or_create(user_id, recipient) do
|
|
||||||
%__MODULE__{}
|
|
||||||
|> changeset(%{user_id: user_id, recipient: recipient})
|
|
||||||
|> Repo.insert(
|
|
||||||
# Need to set something, otherwise we get nothing back at all
|
|
||||||
on_conflict: [set: [recipient: recipient]],
|
|
||||||
returning: true,
|
|
||||||
conflict_target: [:user_id, :recipient]
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec bump_or_create(FlakeId.Ecto.CompatType.t(), String.t()) ::
|
|
||||||
{:ok, t()} | {:error, Ecto.Changeset.t()}
|
|
||||||
def bump_or_create(user_id, recipient) do
|
|
||||||
%__MODULE__{}
|
|
||||||
|> changeset(%{user_id: user_id, recipient: recipient})
|
|
||||||
|> Repo.insert(
|
|
||||||
on_conflict: [set: [updated_at: NaiveDateTime.utc_now()]],
|
|
||||||
returning: true,
|
|
||||||
conflict_target: [:user_id, :recipient]
|
|
||||||
)
|
|
||||||
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
|
|
@ -1,117 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Chat.MessageReference do
|
|
||||||
@moduledoc """
|
|
||||||
A reference that builds a relation between an AP chat message that a user can see and whether it has been seen
|
|
||||||
by them, or should be displayed to them. Used to build the chat view that is presented to the user.
|
|
||||||
"""
|
|
||||||
|
|
||||||
use Ecto.Schema
|
|
||||||
|
|
||||||
alias Pleroma.Chat
|
|
||||||
alias Pleroma.Object
|
|
||||||
alias Pleroma.Repo
|
|
||||||
|
|
||||||
import Ecto.Changeset
|
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
@primary_key {:id, FlakeId.Ecto.Type, autogenerate: true}
|
|
||||||
|
|
||||||
schema "chat_message_references" do
|
|
||||||
belongs_to(:object, Object)
|
|
||||||
belongs_to(:chat, Chat, type: FlakeId.Ecto.CompatType)
|
|
||||||
|
|
||||||
field(:unread, :boolean, default: true)
|
|
||||||
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
def changeset(struct, params) do
|
|
||||||
struct
|
|
||||||
|> cast(params, [:object_id, :chat_id, :unread])
|
|
||||||
|> validate_required([:object_id, :chat_id, :unread])
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_by_id(id) do
|
|
||||||
__MODULE__
|
|
||||||
|> Repo.get(id)
|
|
||||||
|> Repo.preload(:object)
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(cm_ref) do
|
|
||||||
cm_ref
|
|
||||||
|> Repo.delete()
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_for_object(%{id: object_id}) do
|
|
||||||
from(cr in __MODULE__,
|
|
||||||
where: cr.object_id == ^object_id
|
|
||||||
)
|
|
||||||
|> Repo.delete_all()
|
|
||||||
end
|
|
||||||
|
|
||||||
def for_chat_and_object(%{id: chat_id}, %{id: object_id}) do
|
|
||||||
__MODULE__
|
|
||||||
|> Repo.get_by(chat_id: chat_id, object_id: object_id)
|
|
||||||
|> Repo.preload(:object)
|
|
||||||
end
|
|
||||||
|
|
||||||
def for_chat_query(chat) do
|
|
||||||
from(cr in __MODULE__,
|
|
||||||
where: cr.chat_id == ^chat.id,
|
|
||||||
order_by: [desc: :id],
|
|
||||||
preload: [:object]
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def last_message_for_chat(chat) do
|
|
||||||
chat
|
|
||||||
|> for_chat_query()
|
|
||||||
|> limit(1)
|
|
||||||
|> Repo.one()
|
|
||||||
end
|
|
||||||
|
|
||||||
def create(chat, object, unread) do
|
|
||||||
params = %{
|
|
||||||
chat_id: chat.id,
|
|
||||||
object_id: object.id,
|
|
||||||
unread: unread
|
|
||||||
}
|
|
||||||
|
|
||||||
%__MODULE__{}
|
|
||||||
|> changeset(params)
|
|
||||||
|> Repo.insert()
|
|
||||||
end
|
|
||||||
|
|
||||||
def unread_count_for_chat(chat) do
|
|
||||||
chat
|
|
||||||
|> for_chat_query()
|
|
||||||
|> where([cmr], cmr.unread == true)
|
|
||||||
|> Repo.aggregate(:count)
|
|
||||||
end
|
|
||||||
|
|
||||||
def mark_as_read(cm_ref) do
|
|
||||||
cm_ref
|
|
||||||
|> changeset(%{unread: false})
|
|
||||||
|> Repo.update()
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_all_seen_for_chat(chat, last_read_id \\ nil) do
|
|
||||||
query =
|
|
||||||
chat
|
|
||||||
|> for_chat_query()
|
|
||||||
|> exclude(:order_by)
|
|
||||||
|> exclude(:preload)
|
|
||||||
|> where([cmr], cmr.unread == true)
|
|
||||||
|
|
||||||
if last_read_id do
|
|
||||||
query
|
|
||||||
|> where([cmr], cmr.id <= ^last_read_id)
|
|
||||||
else
|
|
||||||
query
|
|
||||||
end
|
|
||||||
|> Repo.update_all(set: [unread: false])
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,45 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.User.WelcomeChatMessage do
|
|
||||||
alias Pleroma.Config
|
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Web.CommonAPI
|
|
||||||
|
|
||||||
@spec enabled?() :: boolean()
|
|
||||||
def enabled?, do: Config.get([:welcome, :chat_message, :enabled], false)
|
|
||||||
|
|
||||||
@spec post_message(User.t()) :: {:ok, Pleroma.Activity.t() | nil}
|
|
||||||
def post_message(user) do
|
|
||||||
[:welcome, :chat_message, :sender_nickname]
|
|
||||||
|> Config.get(nil)
|
|
||||||
|> fetch_sender()
|
|
||||||
|> do_post(user, welcome_message())
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_post(%User{} = sender, recipient, message)
|
|
||||||
when is_binary(message) do
|
|
||||||
CommonAPI.post_chat_message(
|
|
||||||
sender,
|
|
||||||
recipient,
|
|
||||||
message
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_post(_sender, _recipient, _message), do: {:ok, nil}
|
|
||||||
|
|
||||||
defp fetch_sender(nickname) when is_binary(nickname) do
|
|
||||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
|
||||||
user
|
|
||||||
else
|
|
||||||
_ -> nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp fetch_sender(_), do: nil
|
|
||||||
|
|
||||||
defp welcome_message do
|
|
||||||
Config.get([:welcome, :chat_message, :message], nil)
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,129 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
|
|
||||||
use Ecto.Schema
|
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
|
||||||
|
|
||||||
import Ecto.Changeset
|
|
||||||
import Pleroma.Web.ActivityPub.Transmogrifier, only: [fix_emoji: 1]
|
|
||||||
|
|
||||||
@primary_key false
|
|
||||||
@derive Jason.Encoder
|
|
||||||
|
|
||||||
embedded_schema do
|
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
|
||||||
field(:type, :string)
|
|
||||||
field(:content, ObjectValidators.SafeText)
|
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
|
||||||
field(:published, ObjectValidators.DateTime)
|
|
||||||
field(:emoji, ObjectValidators.Emoji, default: %{})
|
|
||||||
|
|
||||||
embeds_one(:attachment, AttachmentValidator)
|
|
||||||
end
|
|
||||||
|
|
||||||
def cast_and_apply(data) do
|
|
||||||
data
|
|
||||||
|> cast_data
|
|
||||||
|> apply_action(:insert)
|
|
||||||
end
|
|
||||||
|
|
||||||
def cast_and_validate(data) do
|
|
||||||
data
|
|
||||||
|> cast_data()
|
|
||||||
|> validate_data()
|
|
||||||
end
|
|
||||||
|
|
||||||
def cast_data(data) do
|
|
||||||
%__MODULE__{}
|
|
||||||
|> changeset(data)
|
|
||||||
end
|
|
||||||
|
|
||||||
def fix(data) do
|
|
||||||
data
|
|
||||||
|> fix_emoji()
|
|
||||||
|> fix_attachment()
|
|
||||||
|> Map.put_new("actor", data["attributedTo"])
|
|
||||||
end
|
|
||||||
|
|
||||||
# Throws everything but the first one away
|
|
||||||
def fix_attachment(%{"attachment" => [attachment | _]} = data) do
|
|
||||||
data
|
|
||||||
|> Map.put("attachment", attachment)
|
|
||||||
end
|
|
||||||
|
|
||||||
def fix_attachment(data), do: data
|
|
||||||
|
|
||||||
def changeset(struct, data) do
|
|
||||||
data = fix(data)
|
|
||||||
|
|
||||||
struct
|
|
||||||
|> cast(data, List.delete(__schema__(:fields), :attachment))
|
|
||||||
|> cast_embed(:attachment)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp validate_data(data_cng) do
|
|
||||||
data_cng
|
|
||||||
|> validate_inclusion(:type, ["ChatMessage"])
|
|
||||||
|> validate_required([:id, :actor, :to, :type, :published])
|
|
||||||
|> validate_content_or_attachment()
|
|
||||||
|> validate_length(:to, is: 1)
|
|
||||||
|> validate_length(:content, max: Pleroma.Config.get([:instance, :remote_limit]))
|
|
||||||
|> validate_local_concern()
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_content_or_attachment(cng) do
|
|
||||||
attachment = get_field(cng, :attachment)
|
|
||||||
|
|
||||||
if attachment do
|
|
||||||
cng
|
|
||||||
else
|
|
||||||
cng
|
|
||||||
|> validate_required([:content])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Validates the following
|
|
||||||
- If both users are in our system
|
|
||||||
- If at least one of the users in this ChatMessage is a local user
|
|
||||||
- If the recipient is not blocking the actor
|
|
||||||
- If the recipient is explicitly not accepting chat messages
|
|
||||||
"""
|
|
||||||
def validate_local_concern(cng) do
|
|
||||||
with actor_ap <- get_field(cng, :actor),
|
|
||||||
{_, %User{} = actor} <- {:find_actor, User.get_cached_by_ap_id(actor_ap)},
|
|
||||||
{_, %User{} = recipient} <-
|
|
||||||
{:find_recipient, User.get_cached_by_ap_id(get_field(cng, :to) |> hd())},
|
|
||||||
{_, false} <- {:not_accepting_chats?, recipient.accepts_chat_messages == false},
|
|
||||||
{_, false} <- {:blocking_actor?, User.blocks?(recipient, actor)},
|
|
||||||
{_, true} <- {:local?, Enum.any?([actor, recipient], & &1.local)} do
|
|
||||||
cng
|
|
||||||
else
|
|
||||||
{:blocking_actor?, true} ->
|
|
||||||
cng
|
|
||||||
|> add_error(:actor, "actor is blocked by recipient")
|
|
||||||
|
|
||||||
{:not_accepting_chats?, true} ->
|
|
||||||
cng
|
|
||||||
|> add_error(:to, "recipient does not accept chat messages")
|
|
||||||
|
|
||||||
{:local?, false} ->
|
|
||||||
cng
|
|
||||||
|> add_error(:actor, "actor and recipient are both remote")
|
|
||||||
|
|
||||||
{:find_actor, _} ->
|
|
||||||
cng
|
|
||||||
|> add_error(:actor, "can't find user")
|
|
||||||
|
|
||||||
{:find_recipient, _} ->
|
|
||||||
cng
|
|
||||||
|> add_error(:to, "can't find user")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,96 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
# NOTES
|
|
||||||
# - Can probably be a generic create validator
|
|
||||||
# - doesn't embed, will only get the object id
|
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator do
|
|
||||||
use Ecto.Schema
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
|
||||||
|
|
||||||
alias Pleroma.Object
|
|
||||||
|
|
||||||
import Ecto.Changeset
|
|
||||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
|
||||||
|
|
||||||
@primary_key false
|
|
||||||
|
|
||||||
embedded_schema do
|
|
||||||
quote do
|
|
||||||
unquote do
|
|
||||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
|
||||||
activity_fields()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
|
||||||
field(:type, :string)
|
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
|
||||||
end
|
|
||||||
|
|
||||||
def cast_and_apply(data) do
|
|
||||||
data
|
|
||||||
|> cast_data
|
|
||||||
|> apply_action(:insert)
|
|
||||||
end
|
|
||||||
|
|
||||||
def cast_data(data) do
|
|
||||||
cast(%__MODULE__{}, data, __schema__(:fields))
|
|
||||||
end
|
|
||||||
|
|
||||||
def cast_and_validate(data, meta \\ []) do
|
|
||||||
cast_data(data)
|
|
||||||
|> validate_data(meta)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp validate_data(cng, meta) do
|
|
||||||
cng
|
|
||||||
|> validate_required([:id, :actor, :to, :type, :object])
|
|
||||||
|> validate_inclusion(:type, ["Create"])
|
|
||||||
|> validate_actor_presence()
|
|
||||||
|> validate_recipients_match(meta)
|
|
||||||
|> validate_actors_match(meta)
|
|
||||||
|> validate_object_nonexistence()
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_object_nonexistence(cng) do
|
|
||||||
cng
|
|
||||||
|> validate_change(:object, fn :object, object_id ->
|
|
||||||
if Object.get_cached_by_ap_id(object_id) do
|
|
||||||
[{:object, "The object to create already exists"}]
|
|
||||||
else
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_actors_match(cng, meta) do
|
|
||||||
object_actor = meta[:object_data]["actor"]
|
|
||||||
|
|
||||||
cng
|
|
||||||
|> validate_change(:actor, fn :actor, actor ->
|
|
||||||
if actor == object_actor do
|
|
||||||
[]
|
|
||||||
else
|
|
||||||
[{:actor, "Actor doesn't match with object actor"}]
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_recipients_match(cng, meta) do
|
|
||||||
object_recipients = meta[:object_data]["to"] || []
|
|
||||||
|
|
||||||
cng
|
|
||||||
|> validate_change(:to, fn :to, recipients ->
|
|
||||||
activity_set = MapSet.new(recipients)
|
|
||||||
object_set = MapSet.new(object_recipients)
|
|
||||||
|
|
||||||
if MapSet.equal?(activity_set, object_set) do
|
|
||||||
[]
|
|
||||||
else
|
|
||||||
[{:to, "Recipients don't match with object recipients"}]
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,85 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 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.Web.AdminAPI
|
|
||||||
alias Pleroma.Web.CommonAPI
|
|
||||||
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
|
|
||||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
|
||||||
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
|
||||||
|
|
||||||
plug(
|
|
||||||
OAuthScopesPlug,
|
|
||||||
%{scopes: ["admin:read:chats"]} when action in [:show, :messages]
|
|
||||||
)
|
|
||||||
|
|
||||||
plug(
|
|
||||||
OAuthScopesPlug,
|
|
||||||
%{scopes: ["admin:write:chats"]} 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
|
|
@ -1,30 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 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
|
|
@ -1,96 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 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: ["Chat administration"],
|
|
||||||
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" => ["admin:write:chats"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def messages_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Chat administration"],
|
|
||||||
summary: "Get chat's messages",
|
|
||||||
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" => ["admin:read:chats"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def show_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Chat administration"],
|
|
||||||
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" => ["admin:read"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,361 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.ApiSpec.ChatOperation do
|
|
||||||
alias OpenApiSpex.Operation
|
|
||||||
alias OpenApiSpex.Schema
|
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.Chat
|
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.ChatMessage
|
|
||||||
|
|
||||||
import Pleroma.Web.ApiSpec.Helpers
|
|
||||||
|
|
||||||
@spec open_api_operation(atom) :: Operation.t()
|
|
||||||
def open_api_operation(action) do
|
|
||||||
operation = String.to_existing_atom("#{action}_operation")
|
|
||||||
apply(__MODULE__, operation, [])
|
|
||||||
end
|
|
||||||
|
|
||||||
def mark_as_read_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Chats"],
|
|
||||||
summary: "Mark all messages in the chat as read",
|
|
||||||
operationId: "ChatController.mark_as_read",
|
|
||||||
parameters: [Operation.parameter(:id, :path, :string, "The ID of the Chat")],
|
|
||||||
requestBody: request_body("Parameters", mark_as_read()),
|
|
||||||
responses: %{
|
|
||||||
200 =>
|
|
||||||
Operation.response(
|
|
||||||
"The updated chat",
|
|
||||||
"application/json",
|
|
||||||
Chat
|
|
||||||
)
|
|
||||||
},
|
|
||||||
security: [
|
|
||||||
%{
|
|
||||||
"oAuth" => ["write:chats"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def mark_message_as_read_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Chats"],
|
|
||||||
summary: "Mark a message as read",
|
|
||||||
operationId: "ChatController.mark_message_as_read",
|
|
||||||
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 read ChatMessage",
|
|
||||||
"application/json",
|
|
||||||
ChatMessage
|
|
||||||
)
|
|
||||||
},
|
|
||||||
security: [
|
|
||||||
%{
|
|
||||||
"oAuth" => ["write:chats"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def show_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Chats"],
|
|
||||||
summary: "Retrieve a chat",
|
|
||||||
operationId: "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
|
|
||||||
|
|
||||||
def create_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Chats"],
|
|
||||||
summary: "Create a chat",
|
|
||||||
operationId: "ChatController.create",
|
|
||||||
parameters: [
|
|
||||||
Operation.parameter(
|
|
||||||
:id,
|
|
||||||
:path,
|
|
||||||
:string,
|
|
||||||
"The account id of the recipient of this chat",
|
|
||||||
required: true,
|
|
||||||
example: "someflakeid"
|
|
||||||
)
|
|
||||||
],
|
|
||||||
responses: %{
|
|
||||||
200 =>
|
|
||||||
Operation.response(
|
|
||||||
"The created or existing chat",
|
|
||||||
"application/json",
|
|
||||||
Chat
|
|
||||||
)
|
|
||||||
},
|
|
||||||
security: [
|
|
||||||
%{
|
|
||||||
"oAuth" => ["write:chats"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def index2_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Chats"],
|
|
||||||
summary: "Retrieve list of chats",
|
|
||||||
operationId: "ChatController.index2",
|
|
||||||
parameters: [
|
|
||||||
Operation.parameter(:with_muted, :query, BooleanLike, "Include chats from muted users")
|
|
||||||
| pagination_params()
|
|
||||||
],
|
|
||||||
responses: %{
|
|
||||||
200 => Operation.response("The chats of the user", "application/json", chats_response())
|
|
||||||
},
|
|
||||||
security: [
|
|
||||||
%{
|
|
||||||
"oAuth" => ["read:chats"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def messages_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Chats"],
|
|
||||||
summary: "Retrieve chat's messages",
|
|
||||||
operationId: "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",
|
|
||||||
chat_messages_response()
|
|
||||||
),
|
|
||||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
|
||||||
},
|
|
||||||
security: [
|
|
||||||
%{
|
|
||||||
"oAuth" => ["read:chats"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def post_chat_message_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Chats"],
|
|
||||||
summary: "Post a message to the chat",
|
|
||||||
operationId: "ChatController.post_chat_message",
|
|
||||||
parameters: [
|
|
||||||
Operation.parameter(:id, :path, :string, "The ID of the Chat")
|
|
||||||
],
|
|
||||||
requestBody: request_body("Parameters", chat_message_create()),
|
|
||||||
responses: %{
|
|
||||||
200 =>
|
|
||||||
Operation.response(
|
|
||||||
"The newly created ChatMessage",
|
|
||||||
"application/json",
|
|
||||||
ChatMessage
|
|
||||||
),
|
|
||||||
400 => Operation.response("Bad Request", "application/json", ApiError),
|
|
||||||
422 => Operation.response("MRF Rejection", "application/json", ApiError)
|
|
||||||
},
|
|
||||||
security: [
|
|
||||||
%{
|
|
||||||
"oAuth" => ["write:chats"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_message_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Chats"],
|
|
||||||
summary: "Delete message",
|
|
||||||
operationId: "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 chats_response do
|
|
||||||
%Schema{
|
|
||||||
title: "ChatsResponse",
|
|
||||||
description: "Response schema for multiple Chats",
|
|
||||||
type: :array,
|
|
||||||
items: Chat,
|
|
||||||
example: [
|
|
||||||
%{
|
|
||||||
"account" => %{
|
|
||||||
"pleroma" => %{
|
|
||||||
"is_admin" => false,
|
|
||||||
"is_confirmed" => true,
|
|
||||||
"hide_followers_count" => false,
|
|
||||||
"is_moderator" => false,
|
|
||||||
"hide_favorites" => true,
|
|
||||||
"ap_id" => "https://dontbulling.me/users/lain",
|
|
||||||
"hide_follows_count" => false,
|
|
||||||
"hide_follows" => false,
|
|
||||||
"background_image" => nil,
|
|
||||||
"skip_thread_containment" => false,
|
|
||||||
"hide_followers" => false,
|
|
||||||
"relationship" => %{},
|
|
||||||
"tags" => []
|
|
||||||
},
|
|
||||||
"avatar" =>
|
|
||||||
"https://dontbulling.me/media/065a4dd3c6740dab13ff9c71ec7d240bb9f8be9205c9e7467fb2202117da1e32.jpg",
|
|
||||||
"following_count" => 0,
|
|
||||||
"header_static" => "https://originalpatchou.li/images/banner.png",
|
|
||||||
"source" => %{
|
|
||||||
"sensitive" => false,
|
|
||||||
"note" => "lain",
|
|
||||||
"pleroma" => %{
|
|
||||||
"discoverable" => false,
|
|
||||||
"actor_type" => "Person"
|
|
||||||
},
|
|
||||||
"fields" => []
|
|
||||||
},
|
|
||||||
"statuses_count" => 1,
|
|
||||||
"locked" => false,
|
|
||||||
"created_at" => "2020-04-16T13:40:15.000Z",
|
|
||||||
"display_name" => "lain",
|
|
||||||
"fields" => [],
|
|
||||||
"acct" => "lain@dontbulling.me",
|
|
||||||
"id" => "9u6Qw6TAZANpqokMkK",
|
|
||||||
"emojis" => [],
|
|
||||||
"avatar_static" =>
|
|
||||||
"https://dontbulling.me/media/065a4dd3c6740dab13ff9c71ec7d240bb9f8be9205c9e7467fb2202117da1e32.jpg",
|
|
||||||
"username" => "lain",
|
|
||||||
"followers_count" => 0,
|
|
||||||
"header" => "https://originalpatchou.li/images/banner.png",
|
|
||||||
"bot" => false,
|
|
||||||
"note" => "lain",
|
|
||||||
"url" => "https://dontbulling.me/users/lain"
|
|
||||||
},
|
|
||||||
"id" => "1",
|
|
||||||
"unread" => 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def chat_messages_response do
|
|
||||||
%Schema{
|
|
||||||
title: "ChatMessagesResponse",
|
|
||||||
description: "Response schema for multiple ChatMessages",
|
|
||||||
type: :array,
|
|
||||||
items: ChatMessage,
|
|
||||||
example: [
|
|
||||||
%{
|
|
||||||
"emojis" => [
|
|
||||||
%{
|
|
||||||
"static_url" => "https://dontbulling.me/emoji/Firefox.gif",
|
|
||||||
"visible_in_picker" => false,
|
|
||||||
"shortcode" => "firefox",
|
|
||||||
"url" => "https://dontbulling.me/emoji/Firefox.gif"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"created_at" => "2020-04-21T15:11:46.000Z",
|
|
||||||
"content" => "Check this out :firefox:",
|
|
||||||
"id" => "13",
|
|
||||||
"chat_id" => "1",
|
|
||||||
"account_id" => "someflakeid",
|
|
||||||
"unread" => false
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
"account_id" => "someflakeid",
|
|
||||||
"content" => "Whats' up?",
|
|
||||||
"id" => "12",
|
|
||||||
"chat_id" => "1",
|
|
||||||
"emojis" => [],
|
|
||||||
"created_at" => "2020-04-21T15:06:45.000Z",
|
|
||||||
"unread" => false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def chat_message_create do
|
|
||||||
%Schema{
|
|
||||||
title: "ChatMessageCreateRequest",
|
|
||||||
description: "POST body for creating an chat message",
|
|
||||||
type: :object,
|
|
||||||
properties: %{
|
|
||||||
content: %Schema{
|
|
||||||
type: :string,
|
|
||||||
description: "The content of your message. Optional if media_id is present"
|
|
||||||
},
|
|
||||||
media_id: %Schema{type: :string, description: "The id of an upload"}
|
|
||||||
},
|
|
||||||
example: %{
|
|
||||||
"content" => "Hey wanna buy feet pics?",
|
|
||||||
"media_id" => "134234"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def mark_as_read do
|
|
||||||
%Schema{
|
|
||||||
title: "MarkAsReadRequest",
|
|
||||||
description: "POST body for marking a number of chat messages as read",
|
|
||||||
type: :object,
|
|
||||||
required: [:last_read_id],
|
|
||||||
properties: %{
|
|
||||||
last_read_id: %Schema{
|
|
||||||
type: :string,
|
|
||||||
description: "The content of your message."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
example: %{
|
|
||||||
"last_read_id" => "abcdef12456"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,75 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.ApiSpec.Schemas.Chat do
|
|
||||||
alias OpenApiSpex.Schema
|
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.ChatMessage
|
|
||||||
|
|
||||||
require OpenApiSpex
|
|
||||||
|
|
||||||
OpenApiSpex.schema(%{
|
|
||||||
title: "Chat",
|
|
||||||
description: "Response schema for a Chat",
|
|
||||||
type: :object,
|
|
||||||
properties: %{
|
|
||||||
id: %Schema{type: :string},
|
|
||||||
account: %Schema{type: :object},
|
|
||||||
unread: %Schema{type: :integer},
|
|
||||||
last_message: ChatMessage,
|
|
||||||
updated_at: %Schema{type: :string, format: :"date-time"}
|
|
||||||
},
|
|
||||||
example: %{
|
|
||||||
"account" => %{
|
|
||||||
"pleroma" => %{
|
|
||||||
"is_admin" => false,
|
|
||||||
"is_confirmed" => true,
|
|
||||||
"hide_followers_count" => false,
|
|
||||||
"is_moderator" => false,
|
|
||||||
"hide_favorites" => true,
|
|
||||||
"ap_id" => "https://dontbulling.me/users/lain",
|
|
||||||
"hide_follows_count" => false,
|
|
||||||
"hide_follows" => false,
|
|
||||||
"background_image" => nil,
|
|
||||||
"skip_thread_containment" => false,
|
|
||||||
"hide_followers" => false,
|
|
||||||
"relationship" => %{},
|
|
||||||
"tags" => []
|
|
||||||
},
|
|
||||||
"avatar" =>
|
|
||||||
"https://dontbulling.me/media/065a4dd3c6740dab13ff9c71ec7d240bb9f8be9205c9e7467fb2202117da1e32.jpg",
|
|
||||||
"following_count" => 0,
|
|
||||||
"header_static" => "https://originalpatchou.li/images/banner.png",
|
|
||||||
"source" => %{
|
|
||||||
"sensitive" => false,
|
|
||||||
"note" => "lain",
|
|
||||||
"pleroma" => %{
|
|
||||||
"discoverable" => false,
|
|
||||||
"actor_type" => "Person"
|
|
||||||
},
|
|
||||||
"fields" => []
|
|
||||||
},
|
|
||||||
"statuses_count" => 1,
|
|
||||||
"is_locked" => false,
|
|
||||||
"created_at" => "2020-04-16T13:40:15.000Z",
|
|
||||||
"display_name" => "lain",
|
|
||||||
"fields" => [],
|
|
||||||
"acct" => "lain@dontbulling.me",
|
|
||||||
"id" => "9u6Qw6TAZANpqokMkK",
|
|
||||||
"emojis" => [],
|
|
||||||
"avatar_static" =>
|
|
||||||
"https://dontbulling.me/media/065a4dd3c6740dab13ff9c71ec7d240bb9f8be9205c9e7467fb2202117da1e32.jpg",
|
|
||||||
"username" => "lain",
|
|
||||||
"followers_count" => 0,
|
|
||||||
"header" => "https://originalpatchou.li/images/banner.png",
|
|
||||||
"bot" => false,
|
|
||||||
"note" => "lain",
|
|
||||||
"url" => "https://dontbulling.me/users/lain"
|
|
||||||
},
|
|
||||||
"id" => "1",
|
|
||||||
"unread" => 2,
|
|
||||||
"last_message" => ChatMessage.schema().example(),
|
|
||||||
"updated_at" => "2020-04-21T15:06:45.000Z"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
end
|
|
@ -1,77 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do
|
|
||||||
alias OpenApiSpex.Schema
|
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.Emoji
|
|
||||||
|
|
||||||
require OpenApiSpex
|
|
||||||
|
|
||||||
OpenApiSpex.schema(%{
|
|
||||||
title: "ChatMessage",
|
|
||||||
description: "Response schema for a ChatMessage",
|
|
||||||
nullable: true,
|
|
||||||
type: :object,
|
|
||||||
properties: %{
|
|
||||||
id: %Schema{type: :string},
|
|
||||||
account_id: %Schema{type: :string, description: "The Mastodon API id of the actor"},
|
|
||||||
chat_id: %Schema{type: :string},
|
|
||||||
content: %Schema{type: :string, nullable: true},
|
|
||||||
created_at: %Schema{type: :string, format: :"date-time"},
|
|
||||||
emojis: %Schema{type: :array, items: Emoji},
|
|
||||||
attachment: %Schema{type: :object, nullable: true},
|
|
||||||
card: %Schema{
|
|
||||||
type: :object,
|
|
||||||
nullable: true,
|
|
||||||
description: "Preview card for links included within status content",
|
|
||||||
required: [:url, :title, :description, :type],
|
|
||||||
properties: %{
|
|
||||||
type: %Schema{
|
|
||||||
type: :string,
|
|
||||||
enum: ["link", "photo", "video", "rich"],
|
|
||||||
description: "The type of the preview card"
|
|
||||||
},
|
|
||||||
provider_name: %Schema{
|
|
||||||
type: :string,
|
|
||||||
nullable: true,
|
|
||||||
description: "The provider of the original resource"
|
|
||||||
},
|
|
||||||
provider_url: %Schema{
|
|
||||||
type: :string,
|
|
||||||
format: :uri,
|
|
||||||
description: "A link to the provider of the original resource"
|
|
||||||
},
|
|
||||||
url: %Schema{type: :string, format: :uri, description: "Location of linked resource"},
|
|
||||||
image: %Schema{
|
|
||||||
type: :string,
|
|
||||||
nullable: true,
|
|
||||||
format: :uri,
|
|
||||||
description: "Preview thumbnail"
|
|
||||||
},
|
|
||||||
title: %Schema{type: :string, description: "Title of linked resource"},
|
|
||||||
description: %Schema{type: :string, description: "Description of preview"}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
unread: %Schema{type: :boolean, description: "Whether a message has been marked as read."}
|
|
||||||
},
|
|
||||||
example: %{
|
|
||||||
"account_id" => "someflakeid",
|
|
||||||
"chat_id" => "1",
|
|
||||||
"content" => "hey you again",
|
|
||||||
"created_at" => "2020-04-21T15:06:45.000Z",
|
|
||||||
"card" => nil,
|
|
||||||
"emojis" => [
|
|
||||||
%{
|
|
||||||
"static_url" => "https://dontbulling.me/emoji/Firefox.gif",
|
|
||||||
"visible_in_picker" => false,
|
|
||||||
"shortcode" => "firefox",
|
|
||||||
"url" => "https://dontbulling.me/emoji/Firefox.gif"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"id" => "14",
|
|
||||||
"attachment" => nil,
|
|
||||||
"unread" => false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
end
|
|
@ -1,45 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.UserSocket do
|
|
||||||
use Phoenix.Socket
|
|
||||||
alias Pleroma.User
|
|
||||||
|
|
||||||
## Channels
|
|
||||||
# channel "room:*", Pleroma.Web.RoomChannel
|
|
||||||
channel("chat:*", Pleroma.Web.ShoutChannel)
|
|
||||||
|
|
||||||
# Socket params are passed from the client and can
|
|
||||||
# be used to verify and authenticate a user. After
|
|
||||||
# verification, you can put default assigns into
|
|
||||||
# the socket that will be set for all channels, ie
|
|
||||||
#
|
|
||||||
# {:ok, assign(socket, :user_id, verified_user_id)}
|
|
||||||
#
|
|
||||||
# To deny connection, return `:error`.
|
|
||||||
#
|
|
||||||
# See `Phoenix.Token` documentation for examples in
|
|
||||||
# performing token verification on connect.
|
|
||||||
def connect(%{"token" => token}, socket) do
|
|
||||||
with true <- Pleroma.Config.get([:shout, :enabled]),
|
|
||||||
{:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84_600),
|
|
||||||
%User{} = user <- Pleroma.User.get_cached_by_id(user_id) do
|
|
||||||
{:ok, assign(socket, :user_name, user.nickname)}
|
|
||||||
else
|
|
||||||
_e -> :error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Socket id's are topics that allow you to identify all sockets for a given user:
|
|
||||||
#
|
|
||||||
# def id(socket), do: "user_socket:#{socket.assigns.user_id}"
|
|
||||||
#
|
|
||||||
# Would allow you to broadcast a "disconnect" event and terminate
|
|
||||||
# all active sockets and channels for a given user:
|
|
||||||
#
|
|
||||||
# Pleroma.Web.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
|
|
||||||
#
|
|
||||||
# Returning `nil` makes this socket anonymous.
|
|
||||||
def id(_socket), do: nil
|
|
||||||
end
|
|
@ -1,188 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
defmodule Pleroma.Web.PleromaAPI.ChatController do
|
|
||||||
use Pleroma.Web, :controller
|
|
||||||
|
|
||||||
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
|
||||||
|
|
||||||
alias Pleroma.Activity
|
|
||||||
alias Pleroma.Chat
|
|
||||||
alias Pleroma.Chat.MessageReference
|
|
||||||
alias Pleroma.Object
|
|
||||||
alias Pleroma.Pagination
|
|
||||||
alias Pleroma.Repo
|
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Web.CommonAPI
|
|
||||||
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
|
|
||||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
|
||||||
|
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
|
||||||
|
|
||||||
plug(
|
|
||||||
OAuthScopesPlug,
|
|
||||||
%{scopes: ["write:chats"]}
|
|
||||||
when action in [
|
|
||||||
:post_chat_message,
|
|
||||||
:create,
|
|
||||||
:mark_as_read,
|
|
||||||
:mark_message_as_read,
|
|
||||||
:delete_message
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
plug(
|
|
||||||
OAuthScopesPlug,
|
|
||||||
%{scopes: ["read:chats"]} when action in [:messages, :index, :index2, :show]
|
|
||||||
)
|
|
||||||
|
|
||||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
|
||||||
|
|
||||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ChatOperation
|
|
||||||
|
|
||||||
def delete_message(%{assigns: %{user: %{id: user_id} = user}} = conn, %{
|
|
||||||
message_id: message_id,
|
|
||||||
id: chat_id
|
|
||||||
}) do
|
|
||||||
with %MessageReference{} = cm_ref <-
|
|
||||||
MessageReference.get_by_id(message_id),
|
|
||||||
^chat_id <- to_string(cm_ref.chat_id),
|
|
||||||
%Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),
|
|
||||||
{:ok, _} <- remove_or_delete(cm_ref, user) do
|
|
||||||
conn
|
|
||||||
|> put_view(MessageReferenceView)
|
|
||||||
|> render("show.json", chat_message_reference: cm_ref)
|
|
||||||
else
|
|
||||||
_e ->
|
|
||||||
{:error, :could_not_delete}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp remove_or_delete(
|
|
||||||
%{object: %{data: %{"actor" => actor, "id" => id}}},
|
|
||||||
%{ap_id: actor} = user
|
|
||||||
) do
|
|
||||||
with %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
|
|
||||||
CommonAPI.delete(activity.id, user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp remove_or_delete(cm_ref, _), do: MessageReference.delete(cm_ref)
|
|
||||||
|
|
||||||
def post_chat_message(
|
|
||||||
%{body_params: params, assigns: %{user: user}} = conn,
|
|
||||||
%{id: id}
|
|
||||||
) do
|
|
||||||
with {:ok, chat} <- Chat.get_by_user_and_id(user, id),
|
|
||||||
%User{} = recipient <- User.get_cached_by_ap_id(chat.recipient),
|
|
||||||
{:ok, activity} <-
|
|
||||||
CommonAPI.post_chat_message(user, recipient, params[:content],
|
|
||||||
media_id: params[:media_id],
|
|
||||||
idempotency_key: idempotency_key(conn)
|
|
||||||
),
|
|
||||||
message <- Object.normalize(activity, fetch: false),
|
|
||||||
cm_ref <- MessageReference.for_chat_and_object(chat, message) do
|
|
||||||
conn
|
|
||||||
|> put_view(MessageReferenceView)
|
|
||||||
|> render("show.json", chat_message_reference: cm_ref)
|
|
||||||
else
|
|
||||||
{:reject, message} ->
|
|
||||||
conn
|
|
||||||
|> put_status(:unprocessable_entity)
|
|
||||||
|> json(%{error: message})
|
|
||||||
|
|
||||||
{:error, message} ->
|
|
||||||
conn
|
|
||||||
|> put_status(:bad_request)
|
|
||||||
|> json(%{error: message})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def mark_message_as_read(
|
|
||||||
%{assigns: %{user: %{id: user_id}}} = conn,
|
|
||||||
%{id: chat_id, message_id: message_id}
|
|
||||||
) do
|
|
||||||
with %MessageReference{} = cm_ref <- MessageReference.get_by_id(message_id),
|
|
||||||
^chat_id <- to_string(cm_ref.chat_id),
|
|
||||||
%Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),
|
|
||||||
{:ok, cm_ref} <- MessageReference.mark_as_read(cm_ref) do
|
|
||||||
conn
|
|
||||||
|> put_view(MessageReferenceView)
|
|
||||||
|> render("show.json", chat_message_reference: cm_ref)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def mark_as_read(
|
|
||||||
%{body_params: %{last_read_id: last_read_id}, assigns: %{user: user}} = conn,
|
|
||||||
%{id: id}
|
|
||||||
) do
|
|
||||||
with {:ok, chat} <- Chat.get_by_user_and_id(user, id),
|
|
||||||
{_n, _} <- MessageReference.set_all_seen_for_chat(chat, last_read_id) do
|
|
||||||
render(conn, "show.json", chat: chat)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def messages(%{assigns: %{user: user}} = conn, %{id: id} = params) do
|
|
||||||
with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do
|
|
||||||
chat_message_refs =
|
|
||||||
chat
|
|
||||||
|> MessageReference.for_chat_query()
|
|
||||||
|> Pagination.fetch_paginated(params)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> add_link_headers(chat_message_refs)
|
|
||||||
|> put_view(MessageReferenceView)
|
|
||||||
|> render("index.json", chat_message_references: chat_message_refs)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def index(%{assigns: %{user: user}} = conn, params) do
|
|
||||||
chats =
|
|
||||||
index_query(user, params)
|
|
||||||
|> Repo.all()
|
|
||||||
|
|
||||||
render(conn, "index.json", chats: chats)
|
|
||||||
end
|
|
||||||
|
|
||||||
def index2(%{assigns: %{user: user}} = conn, params) do
|
|
||||||
chats =
|
|
||||||
index_query(user, params)
|
|
||||||
|> Pagination.fetch_paginated(params)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> add_link_headers(chats)
|
|
||||||
|> render("index.json", chats: chats)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp index_query(%{id: user_id} = user, params) do
|
|
||||||
exclude_users =
|
|
||||||
User.cached_blocked_users_ap_ids(user) ++
|
|
||||||
if params[:with_muted], do: [], else: User.cached_muted_users_ap_ids(user)
|
|
||||||
|
|
||||||
user_id
|
|
||||||
|> Chat.for_user_query()
|
|
||||||
|> where([c], c.recipient not in ^exclude_users)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create(%{assigns: %{user: user}} = conn, %{id: id}) do
|
|
||||||
with %User{ap_id: recipient} <- User.get_cached_by_id(id),
|
|
||||||
{:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do
|
|
||||||
render(conn, "show.json", chat: chat)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def show(%{assigns: %{user: user}} = conn, %{id: id}) do
|
|
||||||
with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do
|
|
||||||
render(conn, "show.json", chat: chat)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp idempotency_key(conn) do
|
|
||||||
case get_req_header(conn, "idempotency-key") do
|
|
||||||
[key] -> key
|
|
||||||
_ -> nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,63 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do
|
|
||||||
use Pleroma.Web, :view
|
|
||||||
|
|
||||||
alias Pleroma.Maps
|
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Web.CommonAPI.Utils
|
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
|
||||||
|
|
||||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
|
||||||
|
|
||||||
def render(
|
|
||||||
"show.json",
|
|
||||||
%{
|
|
||||||
chat_message_reference: %{
|
|
||||||
id: id,
|
|
||||||
object: %{data: chat_message} = object,
|
|
||||||
chat_id: chat_id,
|
|
||||||
unread: unread
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) do
|
|
||||||
%{
|
|
||||||
id: id |> to_string(),
|
|
||||||
content: chat_message["content"],
|
|
||||||
chat_id: chat_id |> to_string(),
|
|
||||||
account_id: User.get_cached_by_ap_id(chat_message["actor"]).id,
|
|
||||||
created_at: Utils.to_masto_date(chat_message["published"]),
|
|
||||||
emojis: StatusView.build_emojis(chat_message["emoji"]),
|
|
||||||
attachment:
|
|
||||||
chat_message["attachment"] &&
|
|
||||||
StatusView.render("attachment.json", attachment: chat_message["attachment"]),
|
|
||||||
unread: unread,
|
|
||||||
card:
|
|
||||||
StatusView.render(
|
|
||||||
"card.json",
|
|
||||||
Pleroma.Web.RichMedia.Helpers.fetch_data_for_object(object)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|> put_idempotency_key()
|
|
||||||
end
|
|
||||||
|
|
||||||
def render("index.json", opts) do
|
|
||||||
render_many(
|
|
||||||
opts[:chat_message_references],
|
|
||||||
__MODULE__,
|
|
||||||
"show.json",
|
|
||||||
Map.put(opts, :as, :chat_message_reference)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp put_idempotency_key(data) do
|
|
||||||
with {:ok, idempotency_key} <- @cachex.get(:chat_message_id_idempotency_key_cache, data.id) do
|
|
||||||
data
|
|
||||||
|> Maps.put_if_present(:idempotency_key, idempotency_key)
|
|
||||||
else
|
|
||||||
_ -> data
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,44 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.PleromaAPI.ChatView do
|
|
||||||
use Pleroma.Web, :view
|
|
||||||
|
|
||||||
alias Pleroma.Chat
|
|
||||||
alias Pleroma.Chat.MessageReference
|
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Web.CommonAPI.Utils
|
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
|
||||||
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
|
|
||||||
|
|
||||||
def render("show.json", %{chat: %Chat{} = chat} = opts) do
|
|
||||||
recipient = User.get_cached_by_ap_id(chat.recipient)
|
|
||||||
last_message = opts[:last_message] || MessageReference.last_message_for_chat(chat)
|
|
||||||
account_view_opts = account_view_opts(opts, recipient)
|
|
||||||
|
|
||||||
%{
|
|
||||||
id: chat.id |> to_string(),
|
|
||||||
account: AccountView.render("show.json", account_view_opts),
|
|
||||||
unread: MessageReference.unread_count_for_chat(chat),
|
|
||||||
last_message:
|
|
||||||
last_message &&
|
|
||||||
MessageReferenceView.render("show.json", chat_message_reference: last_message),
|
|
||||||
updated_at: Utils.to_masto_date(chat.updated_at)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def render("index.json", %{chats: chats} = opts) do
|
|
||||||
render_many(chats, __MODULE__, "show.json", Map.delete(opts, :chats))
|
|
||||||
end
|
|
||||||
|
|
||||||
defp account_view_opts(opts, recipient) do
|
|
||||||
account_view_opts = Map.put(opts, :user, recipient)
|
|
||||||
|
|
||||||
if Map.has_key?(account_view_opts, :for) do
|
|
||||||
account_view_opts
|
|
||||||
else
|
|
||||||
Map.put(account_view_opts, :skip_visibility_check, true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,59 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.ShoutChannel do
|
|
||||||
use Phoenix.Channel
|
|
||||||
|
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
|
||||||
alias Pleroma.Web.ShoutChannel.ShoutChannelState
|
|
||||||
|
|
||||||
def join("chat:public", _message, socket) do
|
|
||||||
send(self(), :after_join)
|
|
||||||
{:ok, socket}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info(:after_join, socket) do
|
|
||||||
push(socket, "messages", %{messages: ShoutChannelState.messages()})
|
|
||||||
{:noreply, socket}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} = socket) do
|
|
||||||
text = String.trim(text)
|
|
||||||
|
|
||||||
if String.length(text) in 1..Pleroma.Config.get([:shout, :limit]) do
|
|
||||||
author = User.get_cached_by_nickname(user_name)
|
|
||||||
author_json = AccountView.render("show.json", user: author, skip_visibility_check: true)
|
|
||||||
|
|
||||||
message = ShoutChannelState.add_message(%{text: text, author: author_json})
|
|
||||||
|
|
||||||
broadcast!(socket, "new_msg", message)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:noreply, socket}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.ShoutChannel.ShoutChannelState do
|
|
||||||
use Agent
|
|
||||||
|
|
||||||
@max_messages 20
|
|
||||||
|
|
||||||
def start_link(_) do
|
|
||||||
Agent.start_link(fn -> %{max_id: 1, messages: []} end, name: __MODULE__)
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_message(message) do
|
|
||||||
Agent.get_and_update(__MODULE__, fn state ->
|
|
||||||
id = state[:max_id] + 1
|
|
||||||
message = Map.put(message, "id", id)
|
|
||||||
messages = [message | state[:messages]] |> Enum.take(@max_messages)
|
|
||||||
{message, %{max_id: id, messages: messages}}
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def messages do
|
|
||||||
Agent.get(__MODULE__, fn state -> state[:messages] |> Enum.reverse() end)
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,29 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Chat.MessageReferenceTest do
|
|
||||||
use Pleroma.DataCase, async: true
|
|
||||||
|
|
||||||
alias Pleroma.Chat
|
|
||||||
alias Pleroma.Chat.MessageReference
|
|
||||||
alias Pleroma.Web.CommonAPI
|
|
||||||
|
|
||||||
import Pleroma.Factory
|
|
||||||
|
|
||||||
describe "messages" do
|
|
||||||
test "it returns the last message in a chat" do
|
|
||||||
user = insert(:user)
|
|
||||||
recipient = insert(:user)
|
|
||||||
|
|
||||||
{:ok, _message_1} = CommonAPI.post_chat_message(user, recipient, "hey")
|
|
||||||
{:ok, _message_2} = CommonAPI.post_chat_message(recipient, user, "ho")
|
|
||||||
|
|
||||||
{:ok, chat} = Chat.get_or_create(user.id, recipient.ap_id)
|
|
||||||
|
|
||||||
message = MessageReference.last_message_for_chat(chat)
|
|
||||||
|
|
||||||
assert message.object.data["content"] == "ho"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,84 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.ChatTest do
|
|
||||||
use Pleroma.DataCase, async: true
|
|
||||||
|
|
||||||
alias Pleroma.Chat
|
|
||||||
|
|
||||||
import Pleroma.Factory
|
|
||||||
|
|
||||||
describe "creation and getting" do
|
|
||||||
test "it only works if the recipient is a valid user (for now)" do
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
assert {:error, _chat} = Chat.bump_or_create(user.id, "http://some/nonexisting/account")
|
|
||||||
assert {:error, _chat} = Chat.get_or_create(user.id, "http://some/nonexisting/account")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it creates a chat for a user and recipient" do
|
|
||||||
user = insert(:user)
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
|
|
||||||
|
|
||||||
assert chat.id
|
|
||||||
end
|
|
||||||
|
|
||||||
test "deleting the user deletes the chat" do
|
|
||||||
user = insert(:user)
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
|
|
||||||
|
|
||||||
Repo.delete(user)
|
|
||||||
|
|
||||||
refute Chat.get_by_id(chat.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "deleting the recipient deletes the chat" do
|
|
||||||
user = insert(:user)
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
|
|
||||||
|
|
||||||
Repo.delete(other_user)
|
|
||||||
|
|
||||||
refute Chat.get_by_id(chat.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it returns and bumps a chat for a user and recipient if it already exists" do
|
|
||||||
user = insert(:user)
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
|
|
||||||
{:ok, chat_two} = Chat.bump_or_create(user.id, other_user.ap_id)
|
|
||||||
|
|
||||||
assert chat.id == chat_two.id
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it returns a chat for a user and recipient if it already exists" do
|
|
||||||
user = insert(:user)
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
|
|
||||||
{:ok, chat_two} = Chat.get_or_create(user.id, other_user.ap_id)
|
|
||||||
|
|
||||||
assert chat.id == chat_two.id
|
|
||||||
end
|
|
||||||
|
|
||||||
test "a returning chat will have an updated `update_at` field" do
|
|
||||||
user = insert(:user)
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
|
|
||||||
{:ok, chat} = time_travel(chat, -2)
|
|
||||||
|
|
||||||
{:ok, chat_two} = Chat.bump_or_create(user.id, other_user.ap_id)
|
|
||||||
|
|
||||||
assert chat.id == chat_two.id
|
|
||||||
assert chat.updated_at != chat_two.updated_at
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,52 +0,0 @@
|
|||||||
defmodule Pleroma.Repo.Migrations.RenameInstanceChatTest do
|
|
||||||
use Pleroma.DataCase
|
|
||||||
import Pleroma.Factory
|
|
||||||
import Pleroma.Tests.Helpers
|
|
||||||
alias Pleroma.ConfigDB
|
|
||||||
|
|
||||||
setup do: clear_config([:instance])
|
|
||||||
setup do: clear_config([:chat])
|
|
||||||
setup_all do: require_migration("20200806175913_rename_instance_chat")
|
|
||||||
|
|
||||||
describe "up/0" do
|
|
||||||
test "migrates chat settings to shout", %{migration: migration} do
|
|
||||||
insert(:config, group: :pleroma, key: :instance, value: [chat_limit: 6000])
|
|
||||||
insert(:config, group: :pleroma, key: :chat, value: [enabled: true])
|
|
||||||
|
|
||||||
assert migration.up() == :ok
|
|
||||||
|
|
||||||
assert ConfigDB.get_by_params(%{group: :pleroma, key: :chat}) == nil
|
|
||||||
assert ConfigDB.get_by_params(%{group: :pleroma, key: :instance}) == nil
|
|
||||||
|
|
||||||
assert ConfigDB.get_by_params(%{group: :pleroma, key: :shout}).value == [
|
|
||||||
limit: 6000,
|
|
||||||
enabled: true
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "does nothing when chat settings are not set", %{migration: migration} do
|
|
||||||
assert migration.up() == :noop
|
|
||||||
assert ConfigDB.get_by_params(%{group: :pleroma, key: :chat}) == nil
|
|
||||||
assert ConfigDB.get_by_params(%{group: :pleroma, key: :shout}) == nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "down/0" do
|
|
||||||
test "migrates shout settings back to instance and chat", %{migration: migration} do
|
|
||||||
insert(:config, group: :pleroma, key: :shout, value: [limit: 42, enabled: true])
|
|
||||||
|
|
||||||
assert migration.down() == :ok
|
|
||||||
|
|
||||||
assert ConfigDB.get_by_params(%{group: :pleroma, key: :chat}).value == [enabled: true]
|
|
||||||
assert ConfigDB.get_by_params(%{group: :pleroma, key: :instance}).value == [chat_limit: 42]
|
|
||||||
assert ConfigDB.get_by_params(%{group: :pleroma, key: :shout}) == nil
|
|
||||||
end
|
|
||||||
|
|
||||||
test "does nothing when shout settings are not set", %{migration: migration} do
|
|
||||||
assert migration.down() == :noop
|
|
||||||
assert ConfigDB.get_by_params(%{group: :pleroma, key: :chat}) == nil
|
|
||||||
assert ConfigDB.get_by_params(%{group: :pleroma, key: :instance}) == nil
|
|
||||||
assert ConfigDB.get_by_params(%{group: :pleroma, key: :shout}) == nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,36 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.User.WelcomeChatMessageTest do
|
|
||||||
use Pleroma.DataCase
|
|
||||||
|
|
||||||
alias Pleroma.User.WelcomeChatMessage
|
|
||||||
|
|
||||||
import Pleroma.Factory
|
|
||||||
|
|
||||||
setup do: clear_config([:welcome])
|
|
||||||
|
|
||||||
describe "post_message/1" do
|
|
||||||
test "send a chat welcome message" do
|
|
||||||
welcome_user = insert(:user, name: "mewmew")
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
clear_config([:welcome, :chat_message, :enabled], true)
|
|
||||||
clear_config([:welcome, :chat_message, :sender_nickname], welcome_user.nickname)
|
|
||||||
|
|
||||||
clear_config(
|
|
||||||
[:welcome, :chat_message, :message],
|
|
||||||
"Hello, welcome to Blob/Cat!"
|
|
||||||
)
|
|
||||||
|
|
||||||
{:ok, %Pleroma.Activity{} = activity} = WelcomeChatMessage.post_message(user)
|
|
||||||
|
|
||||||
assert user.ap_id in activity.recipients
|
|
||||||
assert Pleroma.Object.normalize(activity, fetch: false).data["type"] == "ChatMessage"
|
|
||||||
|
|
||||||
assert Pleroma.Object.normalize(activity, fetch: false).data["content"] ==
|
|
||||||
"Hello, welcome to Blob/Cat!"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,212 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatValidationTest do
|
|
||||||
use Pleroma.DataCase
|
|
||||||
alias Pleroma.Object
|
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
|
||||||
alias Pleroma.Web.ActivityPub.Builder
|
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
|
||||||
alias Pleroma.Web.CommonAPI
|
|
||||||
|
|
||||||
import Pleroma.Factory
|
|
||||||
|
|
||||||
describe "chat message create activities" do
|
|
||||||
test "it is invalid if the object already exists" do
|
|
||||||
user = insert(:user)
|
|
||||||
recipient = insert(:user)
|
|
||||||
{:ok, activity} = CommonAPI.post_chat_message(user, recipient, "hey")
|
|
||||||
object = Object.normalize(activity, fetch: false)
|
|
||||||
|
|
||||||
{:ok, create_data, _} = Builder.create(user, object.data, [recipient.ap_id])
|
|
||||||
|
|
||||||
{:error, cng} = ObjectValidator.validate(create_data, [])
|
|
||||||
|
|
||||||
assert {:object, {"The object to create already exists", []}} in cng.errors
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it is invalid if the object data has a different `to` or `actor` field" do
|
|
||||||
user = insert(:user)
|
|
||||||
recipient = insert(:user)
|
|
||||||
{:ok, object_data, _} = Builder.chat_message(recipient, user.ap_id, "Hey")
|
|
||||||
|
|
||||||
{:ok, create_data, _} = Builder.create(user, object_data, [recipient.ap_id])
|
|
||||||
|
|
||||||
{:error, cng} = ObjectValidator.validate(create_data, [])
|
|
||||||
|
|
||||||
assert {:to, {"Recipients don't match with object recipients", []}} in cng.errors
|
|
||||||
assert {:actor, {"Actor doesn't match with object actor", []}} in cng.errors
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "chat messages" do
|
|
||||||
setup do
|
|
||||||
clear_config([:instance, :remote_limit])
|
|
||||||
user = insert(:user)
|
|
||||||
recipient = insert(:user, local: false)
|
|
||||||
|
|
||||||
{:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey :firefox:")
|
|
||||||
|
|
||||||
%{user: user, recipient: recipient, valid_chat_message: valid_chat_message}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "let's through some basic html", %{user: user, recipient: recipient} do
|
|
||||||
{:ok, valid_chat_message, _} =
|
|
||||||
Builder.chat_message(
|
|
||||||
user,
|
|
||||||
recipient.ap_id,
|
|
||||||
"hey <a href='https://example.org'>example</a> <script>alert('uguu')</script>"
|
|
||||||
)
|
|
||||||
|
|
||||||
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
|
||||||
|
|
||||||
assert object["content"] ==
|
|
||||||
"hey <a href=\"https://example.org\">example</a> alert('uguu')"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "validates for a basic object we build", %{valid_chat_message: valid_chat_message} do
|
|
||||||
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
|
||||||
|
|
||||||
assert valid_chat_message == object
|
|
||||||
assert match?(%{"firefox" => _}, object["emoji"])
|
|
||||||
end
|
|
||||||
|
|
||||||
test "validates for a basic object with an attachment", %{
|
|
||||||
valid_chat_message: valid_chat_message,
|
|
||||||
user: user
|
|
||||||
} do
|
|
||||||
file = %Plug.Upload{
|
|
||||||
content_type: "image/jpeg",
|
|
||||||
path: Path.absname("test/fixtures/image.jpg"),
|
|
||||||
filename: "an_image.jpg"
|
|
||||||
}
|
|
||||||
|
|
||||||
{:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
|
|
||||||
|
|
||||||
valid_chat_message =
|
|
||||||
valid_chat_message
|
|
||||||
|> Map.put("attachment", attachment.data)
|
|
||||||
|
|
||||||
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
|
||||||
|
|
||||||
assert object["attachment"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "validates for a basic object with an attachment in an array", %{
|
|
||||||
valid_chat_message: valid_chat_message,
|
|
||||||
user: user
|
|
||||||
} do
|
|
||||||
file = %Plug.Upload{
|
|
||||||
content_type: "image/jpeg",
|
|
||||||
path: Path.absname("test/fixtures/image.jpg"),
|
|
||||||
filename: "an_image.jpg"
|
|
||||||
}
|
|
||||||
|
|
||||||
{:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
|
|
||||||
|
|
||||||
valid_chat_message =
|
|
||||||
valid_chat_message
|
|
||||||
|> Map.put("attachment", [attachment.data])
|
|
||||||
|
|
||||||
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
|
||||||
|
|
||||||
assert object["attachment"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "validates for a basic object with an attachment but without content", %{
|
|
||||||
valid_chat_message: valid_chat_message,
|
|
||||||
user: user
|
|
||||||
} do
|
|
||||||
file = %Plug.Upload{
|
|
||||||
content_type: "image/jpeg",
|
|
||||||
path: Path.absname("test/fixtures/image.jpg"),
|
|
||||||
filename: "an_image.jpg"
|
|
||||||
}
|
|
||||||
|
|
||||||
{:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
|
|
||||||
|
|
||||||
valid_chat_message =
|
|
||||||
valid_chat_message
|
|
||||||
|> Map.put("attachment", attachment.data)
|
|
||||||
|> Map.delete("content")
|
|
||||||
|
|
||||||
assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
|
|
||||||
|
|
||||||
assert object["attachment"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "does not validate if the message has no content", %{
|
|
||||||
valid_chat_message: valid_chat_message
|
|
||||||
} do
|
|
||||||
contentless =
|
|
||||||
valid_chat_message
|
|
||||||
|> Map.delete("content")
|
|
||||||
|
|
||||||
refute match?({:ok, _object, _meta}, ObjectValidator.validate(contentless, []))
|
|
||||||
end
|
|
||||||
|
|
||||||
test "does not validate if the message is longer than the remote_limit", %{
|
|
||||||
valid_chat_message: valid_chat_message
|
|
||||||
} do
|
|
||||||
clear_config([:instance, :remote_limit], 2)
|
|
||||||
refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
|
|
||||||
end
|
|
||||||
|
|
||||||
test "does not validate if the recipient is blocking the actor", %{
|
|
||||||
valid_chat_message: valid_chat_message,
|
|
||||||
user: user,
|
|
||||||
recipient: recipient
|
|
||||||
} do
|
|
||||||
Pleroma.User.block(recipient, user)
|
|
||||||
refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
|
|
||||||
end
|
|
||||||
|
|
||||||
test "does not validate if the recipient is not accepting chat messages", %{
|
|
||||||
valid_chat_message: valid_chat_message,
|
|
||||||
recipient: recipient
|
|
||||||
} do
|
|
||||||
recipient
|
|
||||||
|> Ecto.Changeset.change(%{accepts_chat_messages: false})
|
|
||||||
|> Pleroma.Repo.update!()
|
|
||||||
|
|
||||||
refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
|
|
||||||
end
|
|
||||||
|
|
||||||
test "does not validate if the actor or the recipient is not in our system", %{
|
|
||||||
valid_chat_message: valid_chat_message
|
|
||||||
} do
|
|
||||||
chat_message =
|
|
||||||
valid_chat_message
|
|
||||||
|> Map.put("actor", "https://raymoo.com/raymoo")
|
|
||||||
|
|
||||||
{:error, _} = ObjectValidator.validate(chat_message, [])
|
|
||||||
|
|
||||||
chat_message =
|
|
||||||
valid_chat_message
|
|
||||||
|> Map.put("to", ["https://raymoo.com/raymoo"])
|
|
||||||
|
|
||||||
{:error, _} = ObjectValidator.validate(chat_message, [])
|
|
||||||
end
|
|
||||||
|
|
||||||
test "does not validate for a message with multiple recipients", %{
|
|
||||||
valid_chat_message: valid_chat_message,
|
|
||||||
user: user,
|
|
||||||
recipient: recipient
|
|
||||||
} do
|
|
||||||
chat_message =
|
|
||||||
valid_chat_message
|
|
||||||
|> Map.put("to", [user.ap_id, recipient.ap_id])
|
|
||||||
|
|
||||||
assert {:error, _} = ObjectValidator.validate(chat_message, [])
|
|
||||||
end
|
|
||||||
|
|
||||||
test "does not validate if it doesn't concern local users" do
|
|
||||||
user = insert(:user, local: false)
|
|
||||||
recipient = insert(:user, local: false)
|
|
||||||
|
|
||||||
{:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey")
|
|
||||||
assert {:error, _} = ObjectValidator.validate(valid_chat_message, [])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,171 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.Transmogrifier.ChatMessageTest do
|
|
||||||
use Pleroma.DataCase
|
|
||||||
|
|
||||||
import Pleroma.Factory
|
|
||||||
|
|
||||||
alias Pleroma.Activity
|
|
||||||
alias Pleroma.Chat
|
|
||||||
alias Pleroma.Object
|
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
|
||||||
|
|
||||||
describe "handle_incoming" do
|
|
||||||
test "handles chonks with attachment" do
|
|
||||||
data = %{
|
|
||||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
|
||||||
"actor" => "https://honk.tedunangst.com/u/tedu",
|
|
||||||
"id" => "https://honk.tedunangst.com/u/tedu/honk/x6gt8X8PcyGkQcXxzg1T",
|
|
||||||
"object" => %{
|
|
||||||
"attachment" => [
|
|
||||||
%{
|
|
||||||
"mediaType" => "image/jpeg",
|
|
||||||
"name" => "298p3RG7j27tfsZ9RQ.jpg",
|
|
||||||
"summary" => "298p3RG7j27tfsZ9RQ.jpg",
|
|
||||||
"type" => "Document",
|
|
||||||
"url" => "https://honk.tedunangst.com/d/298p3RG7j27tfsZ9RQ.jpg"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"attributedTo" => "https://honk.tedunangst.com/u/tedu",
|
|
||||||
"content" => "",
|
|
||||||
"id" => "https://honk.tedunangst.com/u/tedu/chonk/26L4wl5yCbn4dr4y1b",
|
|
||||||
"published" => "2020-05-18T01:13:03Z",
|
|
||||||
"to" => [
|
|
||||||
"https://dontbulling.me/users/lain"
|
|
||||||
],
|
|
||||||
"type" => "ChatMessage"
|
|
||||||
},
|
|
||||||
"published" => "2020-05-18T01:13:03Z",
|
|
||||||
"to" => [
|
|
||||||
"https://dontbulling.me/users/lain"
|
|
||||||
],
|
|
||||||
"type" => "Create"
|
|
||||||
}
|
|
||||||
|
|
||||||
_user = insert(:user, ap_id: data["actor"])
|
|
||||||
_user = insert(:user, ap_id: hd(data["to"]))
|
|
||||||
|
|
||||||
assert {:ok, _activity} = Transmogrifier.handle_incoming(data)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it rejects messages that don't contain content" do
|
|
||||||
data =
|
|
||||||
File.read!("test/fixtures/create-chat-message.json")
|
|
||||||
|> Jason.decode!()
|
|
||||||
|
|
||||||
object =
|
|
||||||
data["object"]
|
|
||||||
|> Map.delete("content")
|
|
||||||
|
|
||||||
data =
|
|
||||||
data
|
|
||||||
|> Map.put("object", object)
|
|
||||||
|
|
||||||
_author =
|
|
||||||
insert(:user, ap_id: data["actor"], local: false, last_refreshed_at: DateTime.utc_now())
|
|
||||||
|
|
||||||
_recipient =
|
|
||||||
insert(:user,
|
|
||||||
ap_id: List.first(data["to"]),
|
|
||||||
local: true,
|
|
||||||
last_refreshed_at: DateTime.utc_now()
|
|
||||||
)
|
|
||||||
|
|
||||||
{:error, _} = Transmogrifier.handle_incoming(data)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it rejects messages that don't concern local users" do
|
|
||||||
data =
|
|
||||||
File.read!("test/fixtures/create-chat-message.json")
|
|
||||||
|> Jason.decode!()
|
|
||||||
|
|
||||||
_author =
|
|
||||||
insert(:user, ap_id: data["actor"], local: false, last_refreshed_at: DateTime.utc_now())
|
|
||||||
|
|
||||||
_recipient =
|
|
||||||
insert(:user,
|
|
||||||
ap_id: List.first(data["to"]),
|
|
||||||
local: false,
|
|
||||||
last_refreshed_at: DateTime.utc_now()
|
|
||||||
)
|
|
||||||
|
|
||||||
{:error, _} = Transmogrifier.handle_incoming(data)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it rejects messages where the `to` field of activity and object don't match" do
|
|
||||||
data =
|
|
||||||
File.read!("test/fixtures/create-chat-message.json")
|
|
||||||
|> Jason.decode!()
|
|
||||||
|
|
||||||
author = insert(:user, ap_id: data["actor"])
|
|
||||||
_recipient = insert(:user, ap_id: List.first(data["to"]))
|
|
||||||
|
|
||||||
data =
|
|
||||||
data
|
|
||||||
|> Map.put("to", author.ap_id)
|
|
||||||
|
|
||||||
assert match?({:error, _}, Transmogrifier.handle_incoming(data))
|
|
||||||
refute Object.get_by_ap_id(data["object"]["id"])
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it fetches the actor if they aren't in our system" do
|
|
||||||
Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
|
||||||
|
|
||||||
data =
|
|
||||||
File.read!("test/fixtures/create-chat-message.json")
|
|
||||||
|> Jason.decode!()
|
|
||||||
|> Map.put("actor", "http://mastodon.example.org/users/admin")
|
|
||||||
|> put_in(["object", "actor"], "http://mastodon.example.org/users/admin")
|
|
||||||
|
|
||||||
_recipient = insert(:user, ap_id: List.first(data["to"]), local: true)
|
|
||||||
|
|
||||||
{:ok, %Activity{} = _activity} = Transmogrifier.handle_incoming(data)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it doesn't work for deactivated users" do
|
|
||||||
data =
|
|
||||||
File.read!("test/fixtures/create-chat-message.json")
|
|
||||||
|> Jason.decode!()
|
|
||||||
|
|
||||||
_author =
|
|
||||||
insert(:user,
|
|
||||||
ap_id: data["actor"],
|
|
||||||
local: false,
|
|
||||||
last_refreshed_at: DateTime.utc_now(),
|
|
||||||
is_active: false
|
|
||||||
)
|
|
||||||
|
|
||||||
_recipient = insert(:user, ap_id: List.first(data["to"]), local: true)
|
|
||||||
|
|
||||||
assert {:error, _} = Transmogrifier.handle_incoming(data)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it inserts it and creates a chat" do
|
|
||||||
data =
|
|
||||||
File.read!("test/fixtures/create-chat-message.json")
|
|
||||||
|> Jason.decode!()
|
|
||||||
|
|
||||||
author =
|
|
||||||
insert(:user, ap_id: data["actor"], local: false, last_refreshed_at: DateTime.utc_now())
|
|
||||||
|
|
||||||
recipient = insert(:user, ap_id: List.first(data["to"]), local: true)
|
|
||||||
|
|
||||||
{:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(data)
|
|
||||||
assert activity.local == false
|
|
||||||
|
|
||||||
assert activity.actor == author.ap_id
|
|
||||||
assert activity.recipients == [recipient.ap_id, author.ap_id]
|
|
||||||
|
|
||||||
%Object{} = object = Object.get_by_ap_id(activity.data["object"])
|
|
||||||
|
|
||||||
assert object
|
|
||||||
assert object.data["content"] == "You expected a cute girl? Too bad. alert('XSS')"
|
|
||||||
assert match?(%{"firefox" => _}, object.data["emoji"])
|
|
||||||
|
|
||||||
refute Chat.get(author.id, recipient.ap_id)
|
|
||||||
assert Chat.get(recipient.id, author.ap_id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,218 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.AdminAPI.ChatControllerTest do
|
|
||||||
use Pleroma.Web.ConnCase, async: true
|
|
||||||
|
|
||||||
import Pleroma.Factory
|
|
||||||
|
|
||||||
alias Pleroma.Chat
|
|
||||||
alias Pleroma.Chat.MessageReference
|
|
||||||
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, fetch: 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, fetch: 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, fetch: 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
|
|
File diff suppressed because it is too large
Load Diff
@ -1,453 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do
|
|
||||||
use Pleroma.Web.ConnCase
|
|
||||||
|
|
||||||
alias Pleroma.Chat
|
|
||||||
alias Pleroma.Chat.MessageReference
|
|
||||||
alias Pleroma.Object
|
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
|
||||||
alias Pleroma.Web.CommonAPI
|
|
||||||
|
|
||||||
import Pleroma.Factory
|
|
||||||
|
|
||||||
describe "POST /api/v1/pleroma/chats/:id/messages/:message_id/read" do
|
|
||||||
setup do: oauth_access(["write:chats"])
|
|
||||||
|
|
||||||
test "it marks one message as read", %{conn: conn, user: user} do
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, create} = CommonAPI.post_chat_message(other_user, user, "sup")
|
|
||||||
{:ok, _create} = CommonAPI.post_chat_message(other_user, user, "sup part 2")
|
|
||||||
{:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
|
|
||||||
object = Object.normalize(create, fetch: false)
|
|
||||||
cm_ref = MessageReference.for_chat_and_object(chat, object)
|
|
||||||
|
|
||||||
assert cm_ref.unread == true
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> post("/api/v1/pleroma/chats/#{chat.id}/messages/#{cm_ref.id}/read")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert result["unread"] == false
|
|
||||||
|
|
||||||
cm_ref = MessageReference.for_chat_and_object(chat, object)
|
|
||||||
|
|
||||||
assert cm_ref.unread == false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "POST /api/v1/pleroma/chats/:id/read" do
|
|
||||||
setup do: oauth_access(["write:chats"])
|
|
||||||
|
|
||||||
test "given a `last_read_id`, it marks everything until then as read", %{
|
|
||||||
conn: conn,
|
|
||||||
user: user
|
|
||||||
} do
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, create} = CommonAPI.post_chat_message(other_user, user, "sup")
|
|
||||||
{:ok, _create} = CommonAPI.post_chat_message(other_user, user, "sup part 2")
|
|
||||||
{:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
|
|
||||||
object = Object.normalize(create, fetch: false)
|
|
||||||
cm_ref = MessageReference.for_chat_and_object(chat, object)
|
|
||||||
|
|
||||||
assert cm_ref.unread == true
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> put_req_header("content-type", "application/json")
|
|
||||||
|> post("/api/v1/pleroma/chats/#{chat.id}/read", %{"last_read_id" => cm_ref.id})
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert result["unread"] == 1
|
|
||||||
|
|
||||||
cm_ref = MessageReference.for_chat_and_object(chat, object)
|
|
||||||
|
|
||||||
assert cm_ref.unread == false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "POST /api/v1/pleroma/chats/:id/messages" do
|
|
||||||
setup do: oauth_access(["write:chats"])
|
|
||||||
|
|
||||||
test "it posts a message to the chat", %{conn: conn, user: user} do
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> put_req_header("content-type", "application/json")
|
|
||||||
|> put_req_header("idempotency-key", "123")
|
|
||||||
|> post("/api/v1/pleroma/chats/#{chat.id}/messages", %{"content" => "Hallo!!"})
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert result["content"] == "Hallo!!"
|
|
||||||
assert result["chat_id"] == chat.id |> to_string()
|
|
||||||
assert result["idempotency_key"] == "123"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it fails if there is no content", %{conn: conn, user: user} do
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> put_req_header("content-type", "application/json")
|
|
||||||
|> post("/api/v1/pleroma/chats/#{chat.id}/messages")
|
|
||||||
|> json_response_and_validate_schema(400)
|
|
||||||
|
|
||||||
assert %{"error" => "no_content"} == result
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it works with an attachment", %{conn: conn, user: user} do
|
|
||||||
clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
|
||||||
clear_config([Pleroma.Uploaders.Local, :uploads], "uploads")
|
|
||||||
|
|
||||||
file = %Plug.Upload{
|
|
||||||
content_type: "image/jpeg",
|
|
||||||
path: Path.absname("test/fixtures/image.jpg"),
|
|
||||||
filename: "an_image.jpg"
|
|
||||||
}
|
|
||||||
|
|
||||||
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
|
|
||||||
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> put_req_header("content-type", "application/json")
|
|
||||||
|> post("/api/v1/pleroma/chats/#{chat.id}/messages", %{
|
|
||||||
"media_id" => to_string(upload.id)
|
|
||||||
})
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert result["attachment"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "gets MRF reason when rejected", %{conn: conn, user: user} do
|
|
||||||
clear_config([:mrf_keyword, :reject], ["GNO"])
|
|
||||||
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
|
|
||||||
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> put_req_header("content-type", "application/json")
|
|
||||||
|> post("/api/v1/pleroma/chats/#{chat.id}/messages", %{"content" => "GNO/Linux"})
|
|
||||||
|> json_response_and_validate_schema(422)
|
|
||||||
|
|
||||||
assert %{"error" => "[KeywordPolicy] Matches with rejected keyword"} == result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "DELETE /api/v1/pleroma/chats/:id/messages/:message_id" do
|
|
||||||
setup do: oauth_access(["write:chats"])
|
|
||||||
|
|
||||||
test "it deletes a message from the chat", %{conn: conn, user: user} do
|
|
||||||
recipient = insert(:user)
|
|
||||||
|
|
||||||
{:ok, message} =
|
|
||||||
CommonAPI.post_chat_message(user, recipient, "Hello darkness my old friend")
|
|
||||||
|
|
||||||
{:ok, other_message} = CommonAPI.post_chat_message(recipient, user, "nico nico ni")
|
|
||||||
|
|
||||||
object = Object.normalize(message, fetch: false)
|
|
||||||
|
|
||||||
chat = Chat.get(user.id, recipient.ap_id)
|
|
||||||
|
|
||||||
cm_ref = MessageReference.for_chat_and_object(chat, object)
|
|
||||||
|
|
||||||
# Deleting your own message removes the message and the reference
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> put_req_header("content-type", "application/json")
|
|
||||||
|> delete("/api/v1/pleroma/chats/#{chat.id}/messages/#{cm_ref.id}")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert result["id"] == cm_ref.id
|
|
||||||
refute MessageReference.get_by_id(cm_ref.id)
|
|
||||||
assert %{data: %{"type" => "Tombstone"}} = Object.get_by_id(object.id)
|
|
||||||
|
|
||||||
# Deleting other people's messages just removes the reference
|
|
||||||
object = Object.normalize(other_message, fetch: false)
|
|
||||||
cm_ref = MessageReference.for_chat_and_object(chat, object)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> put_req_header("content-type", "application/json")
|
|
||||||
|> delete("/api/v1/pleroma/chats/#{chat.id}/messages/#{cm_ref.id}")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert result["id"] == cm_ref.id
|
|
||||||
refute MessageReference.get_by_id(cm_ref.id)
|
|
||||||
assert Object.get_by_id(object.id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "GET /api/v1/pleroma/chats/:id/messages" do
|
|
||||||
setup do: oauth_access(["read:chats"])
|
|
||||||
|
|
||||||
test "it paginates", %{conn: conn, user: user} do
|
|
||||||
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)
|
|
||||||
|
|
||||||
response = get(conn, "/api/v1/pleroma/chats/#{chat.id}/messages")
|
|
||||||
result = json_response_and_validate_schema(response, 200)
|
|
||||||
|
|
||||||
[next, prev] = get_resp_header(response, "link") |> hd() |> String.split(", ")
|
|
||||||
api_endpoint = "/api/v1/pleroma/chats/"
|
|
||||||
|
|
||||||
assert String.match?(
|
|
||||||
next,
|
|
||||||
~r(#{api_endpoint}.*/messages\?limit=\d+&max_id=.*; rel=\"next\"$)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert String.match?(
|
|
||||||
prev,
|
|
||||||
~r(#{api_endpoint}.*/messages\?limit=\d+&min_id=.*; rel=\"prev\"$)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert length(result) == 20
|
|
||||||
|
|
||||||
response =
|
|
||||||
get(conn, "/api/v1/pleroma/chats/#{chat.id}/messages?max_id=#{List.last(result)["id"]}")
|
|
||||||
|
|
||||||
result = json_response_and_validate_schema(response, 200)
|
|
||||||
[next, prev] = get_resp_header(response, "link") |> hd() |> String.split(", ")
|
|
||||||
|
|
||||||
assert String.match?(
|
|
||||||
next,
|
|
||||||
~r(#{api_endpoint}.*/messages\?limit=\d+&max_id=.*; rel=\"next\"$)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert String.match?(
|
|
||||||
prev,
|
|
||||||
~r(#{api_endpoint}.*/messages\?limit=\d+&max_id=.*&min_id=.*; rel=\"prev\"$)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert length(result) == 10
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it returns the messages for a given chat", %{conn: conn, user: user} do
|
|
||||||
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/v1/pleroma/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
|
|
||||||
|
|
||||||
# Trying to get the chat of a different user
|
|
||||||
other_user_chat = Chat.get(other_user.id, user.ap_id)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/pleroma/chats/#{other_user_chat.id}/messages")
|
|
||||||
|> json_response_and_validate_schema(404)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "POST /api/v1/pleroma/chats/by-account-id/:id" do
|
|
||||||
setup do: oauth_access(["write:chats"])
|
|
||||||
|
|
||||||
test "it creates or returns a chat", %{conn: conn} do
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> post("/api/v1/pleroma/chats/by-account-id/#{other_user.id}")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert result["id"]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "GET /api/v1/pleroma/chats/:id" do
|
|
||||||
setup do: oauth_access(["read:chats"])
|
|
||||||
|
|
||||||
test "it returns a chat", %{conn: conn, user: user} do
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/pleroma/chats/#{chat.id}")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert result["id"] == to_string(chat.id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "GET /api/v2/pleroma/chats" do
|
|
||||||
setup do: oauth_access(["read:chats"])
|
|
||||||
|
|
||||||
test "it does not return chats with deleted users", %{conn: conn, user: user} do
|
|
||||||
recipient = insert(:user)
|
|
||||||
{:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
|
|
||||||
|
|
||||||
Pleroma.Repo.delete(recipient)
|
|
||||||
User.invalidate_cache(recipient)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v2/pleroma/chats")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert length(result) == 0
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it does not return chats with users you blocked", %{conn: conn, user: user} do
|
|
||||||
recipient = insert(:user)
|
|
||||||
|
|
||||||
{:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v2/pleroma/chats")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert length(result) == 1
|
|
||||||
|
|
||||||
User.block(user, recipient)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v2/pleroma/chats")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert length(result) == 0
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it does not return chats with users you muted", %{conn: conn, user: user} do
|
|
||||||
recipient = insert(:user)
|
|
||||||
|
|
||||||
{:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v2/pleroma/chats")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert length(result) == 1
|
|
||||||
|
|
||||||
User.mute(user, recipient)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v2/pleroma/chats")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert length(result) == 0
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v2/pleroma/chats?with_muted=true")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert length(result) == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it paginates chats", %{conn: conn, user: user} do
|
|
||||||
Enum.each(1..30, fn _ ->
|
|
||||||
recipient = insert(:user)
|
|
||||||
{:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
|
|
||||||
end)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v2/pleroma/chats")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert length(result) == 20
|
|
||||||
last_id = List.last(result)["id"]
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v2/pleroma/chats?max_id=#{last_id}")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert length(result) == 10
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it return a list of chats the current user is participating in, in descending order of updates",
|
|
||||||
%{conn: conn, user: user} do
|
|
||||||
har = insert(:user)
|
|
||||||
jafnhar = insert(:user)
|
|
||||||
tridi = insert(:user)
|
|
||||||
|
|
||||||
{:ok, chat_1} = Chat.get_or_create(user.id, har.ap_id)
|
|
||||||
{:ok, chat_1} = time_travel(chat_1, -3)
|
|
||||||
{:ok, chat_2} = Chat.get_or_create(user.id, jafnhar.ap_id)
|
|
||||||
{:ok, _chat_2} = time_travel(chat_2, -2)
|
|
||||||
{:ok, chat_3} = Chat.get_or_create(user.id, tridi.ap_id)
|
|
||||||
{:ok, chat_3} = time_travel(chat_3, -1)
|
|
||||||
|
|
||||||
# bump the second one
|
|
||||||
{:ok, chat_2} = Chat.bump_or_create(user.id, jafnhar.ap_id)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v2/pleroma/chats")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
ids = Enum.map(result, & &1["id"])
|
|
||||||
|
|
||||||
assert ids == [
|
|
||||||
chat_2.id |> to_string(),
|
|
||||||
chat_3.id |> to_string(),
|
|
||||||
chat_1.id |> to_string()
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it is not affected by :restrict_unauthenticated setting (issue #1973)", %{
|
|
||||||
conn: conn,
|
|
||||||
user: user
|
|
||||||
} do
|
|
||||||
clear_config([:restrict_unauthenticated, :profiles, :local], true)
|
|
||||||
clear_config([:restrict_unauthenticated, :profiles, :remote], true)
|
|
||||||
|
|
||||||
user2 = insert(:user)
|
|
||||||
user3 = insert(:user, local: false)
|
|
||||||
|
|
||||||
{:ok, _chat_12} = Chat.get_or_create(user.id, user2.ap_id)
|
|
||||||
{:ok, _chat_13} = Chat.get_or_create(user.id, user3.ap_id)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v2/pleroma/chats")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
account_ids = Enum.map(result, &get_in(&1, ["account", "id"]))
|
|
||||||
assert Enum.sort(account_ids) == Enum.sort([user2.id, user3.id])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,75 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.PleromaAPI.ChatMessageReferenceViewTest do
|
|
||||||
use Pleroma.DataCase
|
|
||||||
|
|
||||||
alias Pleroma.Chat
|
|
||||||
alias Pleroma.Chat.MessageReference
|
|
||||||
alias Pleroma.Object
|
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
|
||||||
alias Pleroma.Web.CommonAPI
|
|
||||||
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
|
|
||||||
|
|
||||||
import Pleroma.Factory
|
|
||||||
|
|
||||||
test "it displays a chat message" do
|
|
||||||
user = insert(:user)
|
|
||||||
recipient = insert(:user)
|
|
||||||
|
|
||||||
file = %Plug.Upload{
|
|
||||||
content_type: "image/jpeg",
|
|
||||||
path: Path.absname("test/fixtures/image.jpg"),
|
|
||||||
filename: "an_image.jpg"
|
|
||||||
}
|
|
||||||
|
|
||||||
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
|
|
||||||
|
|
||||||
{:ok, activity} =
|
|
||||||
CommonAPI.post_chat_message(user, recipient, "kippis :firefox:", idempotency_key: "123")
|
|
||||||
|
|
||||||
chat = Chat.get(user.id, recipient.ap_id)
|
|
||||||
|
|
||||||
object = Object.normalize(activity, fetch: false)
|
|
||||||
|
|
||||||
cm_ref = MessageReference.for_chat_and_object(chat, object)
|
|
||||||
|
|
||||||
chat_message = MessageReferenceView.render("show.json", chat_message_reference: cm_ref)
|
|
||||||
|
|
||||||
assert chat_message[:id] == cm_ref.id
|
|
||||||
assert chat_message[:content] == "kippis :firefox:"
|
|
||||||
assert chat_message[:account_id] == user.id
|
|
||||||
assert chat_message[:chat_id]
|
|
||||||
assert chat_message[:created_at]
|
|
||||||
assert chat_message[:unread] == false
|
|
||||||
assert match?([%{shortcode: "firefox"}], chat_message[:emojis])
|
|
||||||
assert chat_message[:idempotency_key] == "123"
|
|
||||||
|
|
||||||
clear_config([:rich_media, :enabled], true)
|
|
||||||
|
|
||||||
Tesla.Mock.mock_global(fn
|
|
||||||
%{url: "https://example.com/ogp"} ->
|
|
||||||
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}
|
|
||||||
end)
|
|
||||||
|
|
||||||
{:ok, activity} =
|
|
||||||
CommonAPI.post_chat_message(recipient, user, "gkgkgk https://example.com/ogp",
|
|
||||||
media_id: upload.id
|
|
||||||
)
|
|
||||||
|
|
||||||
object = Object.normalize(activity, fetch: false)
|
|
||||||
|
|
||||||
cm_ref = MessageReference.for_chat_and_object(chat, object)
|
|
||||||
|
|
||||||
chat_message_two = MessageReferenceView.render("show.json", chat_message_reference: cm_ref)
|
|
||||||
|
|
||||||
assert chat_message_two[:id] == cm_ref.id
|
|
||||||
assert chat_message_two[:content] == object.data["content"]
|
|
||||||
assert chat_message_two[:account_id] == recipient.id
|
|
||||||
assert chat_message_two[:chat_id] == chat_message[:chat_id]
|
|
||||||
assert chat_message_two[:attachment]
|
|
||||||
assert chat_message_two[:unread] == true
|
|
||||||
assert chat_message_two[:card]
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,49 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.PleromaAPI.ChatViewTest do
|
|
||||||
use Pleroma.DataCase, async: true
|
|
||||||
|
|
||||||
alias Pleroma.Chat
|
|
||||||
alias Pleroma.Chat.MessageReference
|
|
||||||
alias Pleroma.Object
|
|
||||||
alias Pleroma.Web.CommonAPI
|
|
||||||
alias Pleroma.Web.CommonAPI.Utils
|
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
|
||||||
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
|
|
||||||
alias Pleroma.Web.PleromaAPI.ChatView
|
|
||||||
|
|
||||||
import Pleroma.Factory
|
|
||||||
|
|
||||||
test "it represents a chat" do
|
|
||||||
user = insert(:user)
|
|
||||||
recipient = insert(:user)
|
|
||||||
|
|
||||||
{:ok, chat} = Chat.get_or_create(user.id, recipient.ap_id)
|
|
||||||
|
|
||||||
represented_chat = ChatView.render("show.json", chat: chat)
|
|
||||||
|
|
||||||
assert represented_chat == %{
|
|
||||||
id: "#{chat.id}",
|
|
||||||
account:
|
|
||||||
AccountView.render("show.json", user: recipient, skip_visibility_check: true),
|
|
||||||
unread: 0,
|
|
||||||
last_message: nil,
|
|
||||||
updated_at: Utils.to_masto_date(chat.updated_at)
|
|
||||||
}
|
|
||||||
|
|
||||||
{:ok, chat_message_creation} = CommonAPI.post_chat_message(user, recipient, "hello")
|
|
||||||
|
|
||||||
chat_message = Object.normalize(chat_message_creation, fetch: false)
|
|
||||||
|
|
||||||
{:ok, chat} = Chat.get_or_create(user.id, recipient.ap_id)
|
|
||||||
|
|
||||||
represented_chat = ChatView.render("show.json", chat: chat)
|
|
||||||
|
|
||||||
cm_ref = MessageReference.for_chat_and_object(chat, chat_message)
|
|
||||||
|
|
||||||
assert represented_chat[:last_message] ==
|
|
||||||
MessageReferenceView.render("show.json", chat_message_reference: cm_ref)
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,41 +0,0 @@
|
|||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.ShoutChannelTest do
|
|
||||||
use Pleroma.Web.ChannelCase
|
|
||||||
alias Pleroma.Web.ShoutChannel
|
|
||||||
alias Pleroma.Web.UserSocket
|
|
||||||
|
|
||||||
import Pleroma.Factory
|
|
||||||
|
|
||||||
setup do
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, _, socket} =
|
|
||||||
socket(UserSocket, "", %{user_name: user.nickname})
|
|
||||||
|> subscribe_and_join(ShoutChannel, "chat:public")
|
|
||||||
|
|
||||||
{:ok, socket: socket}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it broadcasts a message", %{socket: socket} do
|
|
||||||
push(socket, "new_msg", %{"text" => "why is tenshi eating a corndog so cute?"})
|
|
||||||
assert_broadcast("new_msg", %{text: "why is tenshi eating a corndog so cute?"})
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "message lengths" do
|
|
||||||
setup do: clear_config([:shout, :limit])
|
|
||||||
|
|
||||||
test "it ignores messages of length zero", %{socket: socket} do
|
|
||||||
push(socket, "new_msg", %{"text" => ""})
|
|
||||||
refute_broadcast("new_msg", %{text: ""})
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it ignores messages above a certain length", %{socket: socket} do
|
|
||||||
clear_config([:shout, :limit], 2)
|
|
||||||
push(socket, "new_msg", %{"text" => "123"})
|
|
||||||
refute_broadcast("new_msg", %{text: "123"})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
Loading…
Reference in new issue