mirror of
https://git.youjo.love/youjo/youjo-be.git
synced 2024-11-20 05:49:54 +01:00
I wish painful death on floatingghost
This commit is contained in:
parent
43e6b6d275
commit
16e950e4c3
91 changed files with 6141 additions and 40 deletions
|
@ -176,12 +176,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- `/api/v1/pleroma/scrobble`
|
- `/api/v1/pleroma/scrobble`
|
||||||
- `/api/v1/pleroma/accounts/{id}/scrobbles`
|
- `/api/v1/pleroma/accounts/{id}/scrobbles`
|
||||||
- Deprecated endpoints
|
- Deprecated endpoints
|
||||||
- `/api/v1/pleroma/chats`
|
|
||||||
- `/api/v1/notifications/dismiss`
|
- `/api/v1/notifications/dismiss`
|
||||||
- `/api/v1/search`
|
- `/api/v1/search`
|
||||||
- `/api/v1/statuses/{id}/card`
|
- `/api/v1/statuses/{id}/card`
|
||||||
- Chats, they were half-baked. Just use PMs.
|
|
||||||
- Prometheus, it causes massive slowdown
|
- Prometheus, it causes massive slowdown
|
||||||
|
- LDAP authenticator
|
||||||
|
|
||||||
## 2022.07
|
## 2022.07
|
||||||
|
|
||||||
|
|
|
@ -269,6 +269,11 @@ config :pleroma, :welcome,
|
||||||
sender_nickname: nil,
|
sender_nickname: nil,
|
||||||
message: nil
|
message: nil
|
||||||
],
|
],
|
||||||
|
chat_message: [
|
||||||
|
enabled: false,
|
||||||
|
sender_nickname: nil,
|
||||||
|
message: nil
|
||||||
|
],
|
||||||
email: [
|
email: [
|
||||||
enabled: false,
|
enabled: false,
|
||||||
sender: nil,
|
sender: nil,
|
||||||
|
@ -763,8 +768,7 @@ config :pleroma, :frontends,
|
||||||
"mastodon-fe" => %{
|
"mastodon-fe" => %{
|
||||||
"name" => "mastodon-fe",
|
"name" => "mastodon-fe",
|
||||||
"git" => "https://akkoma.dev/AkkomaGang/masto-fe",
|
"git" => "https://akkoma.dev/AkkomaGang/masto-fe",
|
||||||
"build_url" =>
|
"build_url" => "https://akkoma-updates.s3-website.fr-par.scw.cloud/frontend/${ref}/masto-fe.zip",
|
||||||
"https://akkoma-updates.s3-website.fr-par.scw.cloud/frontend/${ref}/masto-fe.zip",
|
|
||||||
"build_dir" => "distribution",
|
"build_dir" => "distribution",
|
||||||
"ref" => "akkoma"
|
"ref" => "akkoma"
|
||||||
},
|
},
|
||||||
|
|
|
@ -957,7 +957,7 @@ config :pleroma, :config_description, [
|
||||||
key: :privileged_staff,
|
key: :privileged_staff,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description:
|
description:
|
||||||
"Let moderators access sensitive data (e.g. updating user credentials, get password reset token, delete users, index and read private statuses)"
|
"Let moderators access sensitive data (e.g. updating user credentials, get password reset token, delete users, index and read private statuses and chats)"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :local_bubble,
|
key: :local_bubble,
|
||||||
|
@ -1006,6 +1006,35 @@ config :pleroma, :config_description, [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
key: :chat_message,
|
||||||
|
type: :keyword,
|
||||||
|
descpiption: "Chat message settings",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :enabled,
|
||||||
|
type: :boolean,
|
||||||
|
description: "Enables sending a chat message to newly registered users"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :message,
|
||||||
|
type: :string,
|
||||||
|
description:
|
||||||
|
"A message that will be sent to newly registered users as a chat message",
|
||||||
|
suggestions: [
|
||||||
|
"Hello, welcome on board!"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :sender_nickname,
|
||||||
|
type: :string,
|
||||||
|
description: "The nickname of the local user that sends a welcome chat message",
|
||||||
|
suggestions: [
|
||||||
|
"lain"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
key: :email,
|
key: :email,
|
||||||
type: :keyword,
|
type: :keyword,
|
||||||
|
@ -2624,6 +2653,27 @@ config :pleroma, :config_description, [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
group: :pleroma,
|
||||||
|
key: :shout,
|
||||||
|
type: :group,
|
||||||
|
description: "Pleroma shout settings",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :enabled,
|
||||||
|
type: :boolean,
|
||||||
|
description: "Enables the backend Shoutbox chat feature."
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :limit,
|
||||||
|
type: :integer,
|
||||||
|
description: "Shout message character limit.",
|
||||||
|
suggestions: [
|
||||||
|
5_000
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :http,
|
key: :http,
|
||||||
|
|
|
@ -8,6 +8,11 @@ For from source installations Akkoma configuration works by first importing the
|
||||||
|
|
||||||
To add configuration to your config file, you can copy it from the base config. The latest version of it can be viewed [here](https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/config/config.exs). You can also use this file if you don't know how an option is supposed to be formatted.
|
To add configuration to your config file, you can copy it from the base config. The latest version of it can be viewed [here](https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/config/config.exs). You can also use this file if you don't know how an option is supposed to be formatted.
|
||||||
|
|
||||||
|
## :shout
|
||||||
|
|
||||||
|
* `enabled` - Enables the backend Shoutbox chat feature. Defaults to `true`.
|
||||||
|
* `limit` - Shout character limit. Defaults to `5_000`
|
||||||
|
|
||||||
## :instance
|
## :instance
|
||||||
* `name`: The instance’s name.
|
* `name`: The instance’s name.
|
||||||
* `email`: Email used to reach an Administrator/Moderator of the instance.
|
* `email`: Email used to reach an Administrator/Moderator of the instance.
|
||||||
|
@ -76,6 +81,10 @@ To add configuration to your config file, you can copy it from the base config.
|
||||||
* `enabled`: Enables the send a direct message to a newly registered user. Defaults to `false`.
|
* `enabled`: Enables the send a direct message to a newly registered user. Defaults to `false`.
|
||||||
* `sender_nickname`: The nickname of the local user that sends the welcome message.
|
* `sender_nickname`: The nickname of the local user that sends the welcome message.
|
||||||
* `message`: A message that will be send to a newly registered users as a direct message.
|
* `message`: A message that will be send to a newly registered users as a direct message.
|
||||||
|
* `chat_message`: - welcome message sent as a chat message.
|
||||||
|
* `enabled`: Enables the send a chat message to a newly registered user. Defaults to `false`.
|
||||||
|
* `sender_nickname`: The nickname of the local user that sends the welcome message.
|
||||||
|
* `message`: A message that will be send to a newly registered users as a chat message.
|
||||||
* `email`: - welcome message sent as a email.
|
* `email`: - welcome message sent as a email.
|
||||||
* `enabled`: Enables the send a welcome email to a newly registered user. Defaults to `false`.
|
* `enabled`: Enables the send a welcome email to a newly registered user. Defaults to `false`.
|
||||||
* `sender`: The email address or tuple with `{nickname, email}` that will use as sender to the welcome email.
|
* `sender`: The email address or tuple with `{nickname, email}` that will use as sender to the welcome email.
|
||||||
|
|
|
@ -1031,6 +1031,7 @@ Most of the settings will be applied in `runtime`, this means that you don't nee
|
||||||
- `:hackney_pools`
|
- `:hackney_pools`
|
||||||
- `:connections_pool`
|
- `:connections_pool`
|
||||||
- `:pools`
|
- `:pools`
|
||||||
|
- `:chat`
|
||||||
- partially settings inside these keys:
|
- partially settings inside these keys:
|
||||||
- `:seconds_valid` in `Pleroma.Captcha`
|
- `:seconds_valid` in `Pleroma.Captcha`
|
||||||
- `:proxy_remote` in `Pleroma.Upload`
|
- `:proxy_remote` in `Pleroma.Upload`
|
||||||
|
@ -1393,6 +1394,127 @@ Loads json generated from `config/descriptions.exs`.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## GET /api/v1/pleroma/admin/users/:nickname/chats
|
||||||
|
|
||||||
|
### List a user's chats
|
||||||
|
|
||||||
|
- Params: None
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sender": {
|
||||||
|
"id": "someflakeid",
|
||||||
|
"username": "somenick",
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"receiver": {
|
||||||
|
"id": "someflakeid",
|
||||||
|
"username": "somenick",
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"id" : "1",
|
||||||
|
"unread" : 2,
|
||||||
|
"last_message" : {...}, // The last message in that chat
|
||||||
|
"updated_at": "2020-04-21T15:11:46.000Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## GET /api/v1/pleroma/admin/chats/:chat_id
|
||||||
|
|
||||||
|
### View a single chat
|
||||||
|
|
||||||
|
- Params: None
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sender": {
|
||||||
|
"id": "someflakeid",
|
||||||
|
"username": "somenick",
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"receiver": {
|
||||||
|
"id": "someflakeid",
|
||||||
|
"username": "somenick",
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"id" : "1",
|
||||||
|
"unread" : 2,
|
||||||
|
"last_message" : {...}, // The last message in that chat
|
||||||
|
"updated_at": "2020-04-21T15:11:46.000Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## GET /api/v1/pleroma/admin/chats/:chat_id/messages
|
||||||
|
|
||||||
|
### List the messages in a chat
|
||||||
|
|
||||||
|
- Params: `max_id`, `min_id`
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"account_id": "someflakeid",
|
||||||
|
"chat_id": "1",
|
||||||
|
"content": "Check this out :firefox:",
|
||||||
|
"created_at": "2020-04-21T15:11:46.000Z",
|
||||||
|
"emojis": [
|
||||||
|
{
|
||||||
|
"shortcode": "firefox",
|
||||||
|
"static_url": "https://dontbulling.me/emoji/Firefox.gif",
|
||||||
|
"url": "https://dontbulling.me/emoji/Firefox.gif",
|
||||||
|
"visible_in_picker": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "13",
|
||||||
|
"unread": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"account_id": "someflakeid",
|
||||||
|
"chat_id": "1",
|
||||||
|
"content": "Whats' up?",
|
||||||
|
"created_at": "2020-04-21T15:06:45.000Z",
|
||||||
|
"emojis": [],
|
||||||
|
"id": "12",
|
||||||
|
"unread": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## DELETE /api/v1/pleroma/admin/chats/:chat_id/messages/:message_id
|
||||||
|
|
||||||
|
### Delete a single message
|
||||||
|
|
||||||
|
- Params: None
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"account_id": "someflakeid",
|
||||||
|
"chat_id": "1",
|
||||||
|
"content": "Check this out :firefox:",
|
||||||
|
"created_at": "2020-04-21T15:11:46.000Z",
|
||||||
|
"emojis": [
|
||||||
|
{
|
||||||
|
"shortcode": "firefox",
|
||||||
|
"static_url": "https://dontbulling.me/emoji/Firefox.gif",
|
||||||
|
"url": "https://dontbulling.me/emoji/Firefox.gif",
|
||||||
|
"visible_in_picker": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "13",
|
||||||
|
"unread": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## `GET /api/v1/pleroma/admin/instance_document/:document_name`
|
## `GET /api/v1/pleroma/admin/instance_document/:document_name`
|
||||||
|
|
||||||
### Get an instance document
|
### Get an instance document
|
||||||
|
|
255
docs/docs/development/API/chats.md
Normal file
255
docs/docs/development/API/chats.md
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
# 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.
|
|
@ -103,11 +103,13 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `hide_followers_count`: boolean, true when the user has follower stat hiding enabled
|
- `hide_followers_count`: boolean, true when the user has follower stat hiding enabled
|
||||||
- `hide_follows_count`: boolean, true when the user has follow stat hiding enabled
|
- `hide_follows_count`: boolean, true when the user has follow stat hiding enabled
|
||||||
- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `/api/v1/accounts/verify_credentials` and `/api/v1/accounts/update_credentials`
|
- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `/api/v1/accounts/verify_credentials` and `/api/v1/accounts/update_credentials`
|
||||||
|
- `chat_token`: The token needed for Akkoma shoutbox. Only returned in `/api/v1/accounts/verify_credentials`
|
||||||
- `deactivated`: boolean, true when the user is deactivated
|
- `deactivated`: boolean, true when the user is deactivated
|
||||||
- `allow_following_move`: boolean, true when the user allows automatically follow moved following accounts
|
- `allow_following_move`: boolean, true when the user allows automatically follow moved following accounts
|
||||||
- `unread_conversation_count`: The count of unread conversations. Only returned to the account owner.
|
- `unread_conversation_count`: The count of unread conversations. Only returned to the account owner.
|
||||||
- `unread_notifications_count`: The count of unread notifications. Only returned to the account owner.
|
- `unread_notifications_count`: The count of unread notifications. Only returned to the account owner.
|
||||||
- `notification_settings`: object, can be absent. See `/api/v1/pleroma/notification_settings` for the parameters/keys returned.
|
- `notification_settings`: object, can be absent. See `/api/v1/pleroma/notification_settings` for the parameters/keys returned.
|
||||||
|
- `accepts_chat_messages`: boolean, but can be null if we don't have that information about a user
|
||||||
- `favicon`: nullable URL string, Favicon image of the user's instance
|
- `favicon`: nullable URL string, Favicon image of the user's instance
|
||||||
|
|
||||||
### Source
|
### Source
|
||||||
|
@ -161,6 +163,15 @@ The `type` value is `pleroma:emoji_reaction`. Has these fields:
|
||||||
- `account`: The account of the user who reacted
|
- `account`: The account of the user who reacted
|
||||||
- `status`: The status that was reacted on
|
- `status`: The status that was reacted on
|
||||||
|
|
||||||
|
### ChatMention Notification (not default)
|
||||||
|
|
||||||
|
This notification has to be requested explicitly.
|
||||||
|
|
||||||
|
The `type` value is `pleroma:chat_mention`
|
||||||
|
|
||||||
|
- `account`: The account who sent the message
|
||||||
|
- `chat_message`: The chat message
|
||||||
|
|
||||||
### Report Notification (not default)
|
### Report Notification (not default)
|
||||||
|
|
||||||
This notification has to be requested explicitly.
|
This notification has to be requested explicitly.
|
||||||
|
@ -175,7 +186,7 @@ The `type` value is `pleroma:report`
|
||||||
Accepts additional parameters:
|
Accepts additional parameters:
|
||||||
|
|
||||||
- `exclude_visibilities`: will exclude the notifications for activities with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`). Usage example: `GET /api/v1/notifications?exclude_visibilities[]=direct&exclude_visibilities[]=private`.
|
- `exclude_visibilities`: will exclude the notifications for activities with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`). Usage example: `GET /api/v1/notifications?exclude_visibilities[]=direct&exclude_visibilities[]=private`.
|
||||||
- `include_types`: will include the notifications for activities with the given types. The parameter accepts an array of types (`mention`, `follow`, `reblog`, `favourite`, `move`, `pleroma:emoji_reaction`, `pleroma:report`). Usage example: `GET /api/v1/notifications?include_types[]=mention&include_types[]=reblog`.
|
- `include_types`: will include the notifications for activities with the given types. The parameter accepts an array of types (`mention`, `follow`, `reblog`, `favourite`, `move`, `pleroma:emoji_reaction`, `pleroma:chat_mention`, `pleroma:report`). Usage example: `GET /api/v1/notifications?include_types[]=mention&include_types[]=reblog`.
|
||||||
|
|
||||||
## DELETE `/api/v1/notifications/destroy_multiple`
|
## DELETE `/api/v1/notifications/destroy_multiple`
|
||||||
|
|
||||||
|
@ -233,6 +244,7 @@ Additional parameters can be added to the JSON body/Form data:
|
||||||
- `pleroma_background_image` - sets the background image of the user. Can be set to "" (an empty string) to reset.
|
- `pleroma_background_image` - sets the background image of the user. Can be set to "" (an empty string) to reset.
|
||||||
- `discoverable` - if true, external services (search bots) etc. are allowed to index / list the account (regardless of this setting, user will still appear in regular search results).
|
- `discoverable` - if true, external services (search bots) etc. are allowed to index / list the account (regardless of this setting, user will still appear in regular search results).
|
||||||
- `actor_type` - the type of this account.
|
- `actor_type` - the type of this account.
|
||||||
|
- `accepts_chat_messages` - if false, this account will reject all chat messages.
|
||||||
- `language` - user's preferred language for receiving emails (digest, confirmation, etc.)
|
- `language` - user's preferred language for receiving emails (digest, confirmation, etc.)
|
||||||
|
|
||||||
All images (avatar, banner and background) can be reset to the default by sending an empty string ("") instead of a file.
|
All images (avatar, banner and background) can be reset to the default by sending an empty string ("") instead of a file.
|
||||||
|
@ -292,6 +304,7 @@ Has these additional parameters (which are the same as in Akkoma-API):
|
||||||
`GET /api/v1/instance` has additional fields
|
`GET /api/v1/instance` has additional fields
|
||||||
|
|
||||||
- `max_toot_chars`: The maximum characters per post
|
- `max_toot_chars`: The maximum characters per post
|
||||||
|
- `chat_limit`: The maximum characters per chat message
|
||||||
- `description_limit`: The maximum characters per image description
|
- `description_limit`: The maximum characters per image description
|
||||||
- `poll_limits`: The limits of polls
|
- `poll_limits`: The limits of polls
|
||||||
- `upload_limit`: The maximum upload file size
|
- `upload_limit`: The maximum upload file size
|
||||||
|
@ -312,6 +325,7 @@ Has these additional parameters (which are the same as in Akkoma-API):
|
||||||
|
|
||||||
Permits these additional alert types:
|
Permits these additional alert types:
|
||||||
|
|
||||||
|
- pleroma:chat_mention
|
||||||
- pleroma:emoji_reaction
|
- pleroma:emoji_reaction
|
||||||
|
|
||||||
## Markers
|
## Markers
|
||||||
|
@ -322,6 +336,10 @@ Has these additional fields under the `pleroma` object:
|
||||||
|
|
||||||
## Streaming
|
## Streaming
|
||||||
|
|
||||||
|
### Chats
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
### Remote timelines
|
### Remote timelines
|
||||||
|
|
||||||
For viewing remote server timelines, there are `public:remote` and `public:remote:media` streams. Each of these accept a parameter like `?instance=lain.com`.
|
For viewing remote server timelines, there are `public:remote` and `public:remote:media` streams. Each of these accept a parameter like `?instance=lain.com`.
|
||||||
|
|
|
@ -44,8 +44,11 @@ See also [the Nodeinfo standard](https://nodeinfo.diaspora.software/).
|
||||||
"shareable_emoji_packs",
|
"shareable_emoji_packs",
|
||||||
"multifetch",
|
"multifetch",
|
||||||
"pleroma:api/v1/notifications:include_types_filter",
|
"pleroma:api/v1/notifications:include_types_filter",
|
||||||
|
"chat",
|
||||||
|
"shout",
|
||||||
"relay",
|
"relay",
|
||||||
"pleroma_emoji_reactions"
|
"pleroma_emoji_reactions",
|
||||||
|
"pleroma_chat_messages"
|
||||||
],
|
],
|
||||||
"federation":{
|
"federation":{
|
||||||
"enabled":true,
|
"enabled":true,
|
||||||
|
@ -201,8 +204,11 @@ See also [the Nodeinfo standard](https://nodeinfo.diaspora.software/).
|
||||||
"shareable_emoji_packs",
|
"shareable_emoji_packs",
|
||||||
"multifetch",
|
"multifetch",
|
||||||
"pleroma:api/v1/notifications:include_types_filter",
|
"pleroma:api/v1/notifications:include_types_filter",
|
||||||
|
"chat",
|
||||||
|
"shout",
|
||||||
"relay",
|
"relay",
|
||||||
"pleroma_emoji_reactions"
|
"pleroma_emoji_reactions",
|
||||||
|
"pleroma_chat_messages"
|
||||||
],
|
],
|
||||||
"federation":{
|
"federation":{
|
||||||
"enabled":true,
|
"enabled":true,
|
||||||
|
|
|
@ -26,3 +26,40 @@ Response: HTTP 201 Created with the object into the body, no `Location` header p
|
||||||
|
|
||||||
The object given in the reponse should then be inserted into an Object's `attachment` field.
|
The object given in the reponse should then be inserted into an Object's `attachment` field.
|
||||||
|
|
||||||
|
## ChatMessages
|
||||||
|
|
||||||
|
`ChatMessage`s are the messages sent in 1-on-1 chats. They are similar to
|
||||||
|
`Note`s, but the addresing is done by having a single AP actor in the `to`
|
||||||
|
field. Addressing multiple actors is not allowed. These messages are always
|
||||||
|
private, there is no public version of them. They are created with a `Create`
|
||||||
|
activity.
|
||||||
|
|
||||||
|
They are part of the `litepub` namespace as `http://litepub.social/ns#ChatMessage`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"actor": "http://2hu.gensokyo/users/raymoo",
|
||||||
|
"id": "http://2hu.gensokyo/objects/1",
|
||||||
|
"object": {
|
||||||
|
"attributedTo": "http://2hu.gensokyo/users/raymoo",
|
||||||
|
"content": "You expected a cute girl? Too bad.",
|
||||||
|
"id": "http://2hu.gensokyo/objects/2",
|
||||||
|
"published": "2020-02-12T14:08:20Z",
|
||||||
|
"to": [
|
||||||
|
"http://2hu.gensokyo/users/marisa"
|
||||||
|
],
|
||||||
|
"type": "ChatMessage"
|
||||||
|
},
|
||||||
|
"published": "2018-02-12T14:08:20Z",
|
||||||
|
"to": [
|
||||||
|
"http://2hu.gensokyo/users/marisa"
|
||||||
|
],
|
||||||
|
"type": "Create"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This setup does not prevent multi-user chats, but these will have to go through
|
||||||
|
a `Group`, which will be the recipient of the messages and then `Announce` them
|
||||||
|
to the users in the `Group`.
|
||||||
|
|
|
@ -78,7 +78,8 @@ defmodule Pleroma.Application do
|
||||||
] ++
|
] ++
|
||||||
elasticsearch_children() ++
|
elasticsearch_children() ++
|
||||||
task_children(@mix_env) ++
|
task_children(@mix_env) ++
|
||||||
dont_run_in_test(@mix_env)
|
dont_run_in_test(@mix_env) ++
|
||||||
|
shout_child(shout_enabled?())
|
||||||
|
|
||||||
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
||||||
# for other strategies and supported options
|
# for other strategies and supported options
|
||||||
|
@ -93,6 +94,7 @@ defmodule Pleroma.Application do
|
||||||
end
|
end
|
||||||
|
|
||||||
opts = [strategy: :one_for_one, name: Pleroma.Supervisor, max_restarts: max_restarts]
|
opts = [strategy: :one_for_one, name: Pleroma.Supervisor, max_restarts: max_restarts]
|
||||||
|
result = Supervisor.start_link(children, opts)
|
||||||
|
|
||||||
with {:ok, data} <- Supervisor.start_link(children, opts) do
|
with {:ok, data} <- Supervisor.start_link(children, opts) do
|
||||||
set_postgres_server_version()
|
set_postgres_server_version()
|
||||||
|
@ -161,6 +163,10 @@ defmodule Pleroma.Application do
|
||||||
build_cachex("instances", default_ttl: :timer.hours(24), ttl_interval: 1000, limit: 2500),
|
build_cachex("instances", default_ttl: :timer.hours(24), ttl_interval: 1000, limit: 2500),
|
||||||
build_cachex("request_signatures", default_ttl: :timer.hours(24 * 30), limit: 3000),
|
build_cachex("request_signatures", default_ttl: :timer.hours(24 * 30), limit: 3000),
|
||||||
build_cachex("rel_me", default_ttl: :timer.hours(24 * 30), limit: 300)
|
build_cachex("rel_me", default_ttl: :timer.hours(24 * 30), limit: 300)
|
||||||
|
build_cachex("chat_message_id_idempotency_key",
|
||||||
|
expiration: chat_message_id_idempotency_key_expiration(),
|
||||||
|
limit: 500_000
|
||||||
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -170,6 +176,9 @@ defmodule Pleroma.Application do
|
||||||
defp idempotency_expiration,
|
defp idempotency_expiration,
|
||||||
do: expiration(default: :timer.seconds(6 * 60 * 60), interval: :timer.seconds(60))
|
do: expiration(default: :timer.seconds(6 * 60 * 60), interval: :timer.seconds(60))
|
||||||
|
|
||||||
|
defp chat_message_id_idempotency_key_expiration,
|
||||||
|
do: expiration(default: :timer.minutes(2), interval: :timer.seconds(60))
|
||||||
|
|
||||||
defp seconds_valid_interval,
|
defp seconds_valid_interval,
|
||||||
do: :timer.seconds(Config.get!([Pleroma.Captcha, :seconds_valid]))
|
do: :timer.seconds(Config.get!([Pleroma.Captcha, :seconds_valid]))
|
||||||
|
|
||||||
|
@ -181,6 +190,8 @@ defmodule Pleroma.Application do
|
||||||
type: :worker
|
type: :worker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defp shout_enabled?, do: Config.get([:shout, :enabled])
|
||||||
|
|
||||||
defp dont_run_in_test(env) when env in [:test, :benchmark], do: []
|
defp dont_run_in_test(env) when env in [:test, :benchmark], do: []
|
||||||
|
|
||||||
defp dont_run_in_test(_) do
|
defp dont_run_in_test(_) do
|
||||||
|
@ -201,6 +212,12 @@ defmodule Pleroma.Application do
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec task_children(atom()) :: [map()]
|
@spec task_children(atom()) :: [map()]
|
||||||
|
defp shout_child(true) do
|
||||||
|
[
|
||||||
|
Pleroma.Web.ShoutChannel.ShoutChannelState,
|
||||||
|
{Phoenix.PubSub, [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
defp task_children(:test) do
|
defp task_children(:test) do
|
||||||
[
|
[
|
||||||
|
|
97
lib/pleroma/chat.ex
Normal file
97
lib/pleroma/chat.ex
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
# 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
|
117
lib/pleroma/chat/message_reference.ex
Normal file
117
lib/pleroma/chat/message_reference.ex
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
# 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
|
|
@ -179,6 +179,7 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
||||||
check_activity_expiration_config(),
|
check_activity_expiration_config(),
|
||||||
check_remote_ip_plug_name(),
|
check_remote_ip_plug_name(),
|
||||||
check_uploders_s3_public_endpoint(),
|
check_uploders_s3_public_endpoint(),
|
||||||
|
check_old_chat_shoutbox(),
|
||||||
check_quarantined_instances_tuples(),
|
check_quarantined_instances_tuples(),
|
||||||
check_transparency_exclusions_tuples(),
|
check_transparency_exclusions_tuples(),
|
||||||
check_simple_policy_tuples(),
|
check_simple_policy_tuples(),
|
||||||
|
@ -337,4 +338,27 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec check_old_chat_shoutbox() :: :ok | nil
|
||||||
|
def check_old_chat_shoutbox do
|
||||||
|
instance_config = Pleroma.Config.get([:instance])
|
||||||
|
chat_config = Pleroma.Config.get([:chat]) || []
|
||||||
|
|
||||||
|
use_old_config =
|
||||||
|
Keyword.has_key?(instance_config, :chat_limit) or
|
||||||
|
Keyword.has_key?(chat_config, :enabled)
|
||||||
|
|
||||||
|
if use_old_config do
|
||||||
|
Logger.error("""
|
||||||
|
!!!DEPRECATION WARNING!!!
|
||||||
|
Your config is using the old namespace for the Shoutbox configuration. You need to convert to the new namespace. e.g.,
|
||||||
|
\n* `config :pleroma, :chat, enabled` and `config :pleroma, :instance, chat_limit` are now equal to:
|
||||||
|
\n* `config :pleroma, :shout, enabled` and `config :pleroma, :shout, limit`
|
||||||
|
""")
|
||||||
|
|
||||||
|
:error
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.Config.TransferTask do
|
||||||
|
|
||||||
defp reboot_time_keys,
|
defp reboot_time_keys,
|
||||||
do: [
|
do: [
|
||||||
|
{:pleroma, :shout},
|
||||||
{:pleroma, Oban},
|
{:pleroma, Oban},
|
||||||
{:pleroma, :rate_limit},
|
{:pleroma, :rate_limit},
|
||||||
{:pleroma, :markup},
|
{:pleroma, :markup},
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.MigrationHelper.NotificationBackfill do
|
defmodule Pleroma.MigrationHelper.NotificationBackfill do
|
||||||
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@ -78,5 +79,14 @@ defmodule Pleroma.MigrationHelper.NotificationBackfill do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp type_from_activity_object(%{data: %{"type" => "Create"}}), do: "mention"
|
defp type_from_activity_object(%{data: %{"type" => "Create", "object" => %{}}}), do: "mention"
|
||||||
|
|
||||||
|
defp type_from_activity_object(%{data: %{"type" => "Create"}} = activity) do
|
||||||
|
object = Object.get_by_ap_id(activity.data["object"])
|
||||||
|
|
||||||
|
case object && object.data["type"] do
|
||||||
|
"ChatMessage" -> "pleroma:chat_mention"
|
||||||
|
_ -> "mention"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -237,8 +237,20 @@ defmodule Pleroma.ModerationLog do
|
||||||
insert_log_entry_with_message(%ModerationLog{data: data})
|
insert_log_entry_with_message(%ModerationLog{data: data})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def insert_log(%{actor: %User{} = actor, action: "chat_message_delete", subject_id: subject_id}) do
|
||||||
|
%ModerationLog{
|
||||||
|
data: %{
|
||||||
|
"actor" => %{"nickname" => actor.nickname},
|
||||||
|
"action" => "chat_message_delete",
|
||||||
|
"subject_id" => subject_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> insert_log_entry_with_message()
|
||||||
|
end
|
||||||
|
|
||||||
@spec insert_log_entry_with_message(ModerationLog.t()) ::
|
@spec insert_log_entry_with_message(ModerationLog.t()) ::
|
||||||
{:ok, ModerationLog.t()} | {:error, any}
|
{:ok, ModerationLog.t()} | {:error, any}
|
||||||
|
|
||||||
defp insert_log_entry_with_message(entry) do
|
defp insert_log_entry_with_message(entry) do
|
||||||
entry.data["message"]
|
entry.data["message"]
|
||||||
|> put_in(get_log_entry_message(entry))
|
|> put_in(get_log_entry_message(entry))
|
||||||
|
@ -544,6 +556,16 @@ defmodule Pleroma.ModerationLog do
|
||||||
"@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}"
|
"@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_log_entry_message(%ModerationLog{
|
||||||
|
data: %{
|
||||||
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
|
"action" => "chat_message_delete",
|
||||||
|
"subject_id" => subject_id
|
||||||
|
}
|
||||||
|
}) do
|
||||||
|
"@#{actor_nickname} deleted chat message ##{subject_id}"
|
||||||
|
end
|
||||||
|
|
||||||
def get_log_entry_message(%ModerationLog{
|
def get_log_entry_message(%ModerationLog{
|
||||||
data: %{
|
data: %{
|
||||||
"actor" => %{"nickname" => actor_nickname},
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
|
|
|
@ -68,6 +68,7 @@ defmodule Pleroma.Notification do
|
||||||
follow_request
|
follow_request
|
||||||
mention
|
mention
|
||||||
move
|
move
|
||||||
|
pleroma:chat_mention
|
||||||
pleroma:emoji_reaction
|
pleroma:emoji_reaction
|
||||||
pleroma:report
|
pleroma:report
|
||||||
reblog
|
reblog
|
||||||
|
@ -463,7 +464,16 @@ defmodule Pleroma.Notification do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp type_from_activity_object(%{data: %{"type" => "Create"}}), do: "mention"
|
defp type_from_activity_object(%{data: %{"type" => "Create", "object" => %{}}}), do: "mention"
|
||||||
|
|
||||||
|
defp type_from_activity_object(%{data: %{"type" => "Create"}} = activity) do
|
||||||
|
object = Object.get_by_ap_id(activity.data["object"])
|
||||||
|
|
||||||
|
case object && object.data["type"] do
|
||||||
|
"ChatMessage" -> "pleroma:chat_mention"
|
||||||
|
_ -> "mention"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# TODO move to sql, too.
|
# TODO move to sql, too.
|
||||||
def create_notification(%Activity{} = activity, %User{} = user, opts \\ []) do
|
def create_notification(%Activity{} = activity, %User{} = user, opts \\ []) do
|
||||||
|
|
|
@ -151,6 +151,7 @@ defmodule Pleroma.User do
|
||||||
field(:also_known_as, {:array, ObjectValidators.ObjectID}, default: [])
|
field(:also_known_as, {:array, ObjectValidators.ObjectID}, default: [])
|
||||||
field(:inbox, :string)
|
field(:inbox, :string)
|
||||||
field(:shared_inbox, :string)
|
field(:shared_inbox, :string)
|
||||||
|
field(:accepts_chat_messages, :boolean, default: nil)
|
||||||
field(:last_active_at, :naive_datetime)
|
field(:last_active_at, :naive_datetime)
|
||||||
field(:disclose_client, :boolean, default: true)
|
field(:disclose_client, :boolean, default: true)
|
||||||
field(:pinned_objects, :map, default: %{})
|
field(:pinned_objects, :map, default: %{})
|
||||||
|
@ -475,6 +476,7 @@ defmodule Pleroma.User do
|
||||||
:invisible,
|
:invisible,
|
||||||
:actor_type,
|
:actor_type,
|
||||||
:also_known_as,
|
:also_known_as,
|
||||||
|
:accepts_chat_messages,
|
||||||
:pinned_objects
|
:pinned_objects
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -537,6 +539,8 @@ defmodule Pleroma.User do
|
||||||
:actor_type,
|
:actor_type,
|
||||||
:disclose_client,
|
:disclose_client,
|
||||||
:status_ttl_days
|
:status_ttl_days
|
||||||
|
:accepts_chat_messages,
|
||||||
|
:disclose_client
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|> unique_constraint(:nickname)
|
|> unique_constraint(:nickname)
|
||||||
|
@ -728,6 +732,7 @@ defmodule Pleroma.User do
|
||||||
bio_limit = Config.get([:instance, :user_bio_length], 5000)
|
bio_limit = Config.get([:instance, :user_bio_length], 5000)
|
||||||
name_limit = Config.get([:instance, :user_name_length], 100)
|
name_limit = Config.get([:instance, :user_name_length], 100)
|
||||||
reason_limit = Config.get([:instance, :registration_reason_length], 500)
|
reason_limit = Config.get([:instance, :registration_reason_length], 500)
|
||||||
|
params = Map.put_new(params, :accepts_chat_messages, true)
|
||||||
|
|
||||||
confirmed? =
|
confirmed? =
|
||||||
if is_nil(opts[:confirmed]) do
|
if is_nil(opts[:confirmed]) do
|
||||||
|
@ -755,6 +760,7 @@ defmodule Pleroma.User do
|
||||||
:password,
|
:password,
|
||||||
:password_confirmation,
|
:password_confirmation,
|
||||||
:emoji,
|
:emoji,
|
||||||
|
:accepts_chat_messages,
|
||||||
:registration_reason,
|
:registration_reason,
|
||||||
:language
|
:language
|
||||||
])
|
])
|
||||||
|
@ -865,7 +871,8 @@ defmodule Pleroma.User do
|
||||||
{:ok, user} <- set_cache(user),
|
{:ok, user} <- set_cache(user),
|
||||||
{:ok, _} <- maybe_send_registration_email(user),
|
{:ok, _} <- maybe_send_registration_email(user),
|
||||||
{:ok, _} <- maybe_send_welcome_email(user),
|
{:ok, _} <- maybe_send_welcome_email(user),
|
||||||
{:ok, _} <- maybe_send_welcome_message(user) do
|
{:ok, _} <- maybe_send_welcome_message(user),
|
||||||
|
{:ok, _} <- maybe_send_welcome_chat_message(user) do
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -899,6 +906,15 @@ defmodule Pleroma.User do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_send_welcome_chat_message(user) do
|
||||||
|
if User.WelcomeChatMessage.enabled?() do
|
||||||
|
User.WelcomeChatMessage.post_message(user)
|
||||||
|
{:ok, :enqueued}
|
||||||
|
else
|
||||||
|
{:ok, :noop}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp maybe_send_welcome_email(%User{email: email} = user) when is_binary(email) do
|
defp maybe_send_welcome_email(%User{email: email} = user) when is_binary(email) do
|
||||||
if User.WelcomeEmail.enabled?() do
|
if User.WelcomeEmail.enabled?() do
|
||||||
User.WelcomeEmail.send_email(user)
|
User.WelcomeEmail.send_email(user)
|
||||||
|
|
45
lib/pleroma/user/welcome_chat_message.ex
Normal file
45
lib/pleroma/user/welcome_chat_message.ex
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
# 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
|
|
@ -97,7 +97,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|
|
||||||
defp increase_replies_count_if_reply(_create_data), do: :noop
|
defp increase_replies_count_if_reply(_create_data), do: :noop
|
||||||
|
|
||||||
@object_types ~w[Question Answer Audio Video Event Article Note Page]
|
@object_types ~w[ChatMessage Question Answer Audio Video Event Article Note Page]
|
||||||
@impl true
|
@impl true
|
||||||
def persist(%{"type" => type} = object, meta) when type in @object_types do
|
def persist(%{"type" => type} = object, meta) when type in @object_types do
|
||||||
with {:ok, object} <- Object.create(object) do
|
with {:ok, object} <- Object.create(object) do
|
||||||
|
@ -1289,7 +1289,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp exclude_invisible_actors(query, %{type: "Flag"}), do: query
|
|
||||||
|
defp exclude_chat_messages(query, %{include_chat_messages: true}), do: query
|
||||||
|
|
||||||
|
defp exclude_chat_messages(query, _) do
|
||||||
|
if has_named_binding?(query, :object) do
|
||||||
|
from([activity, object: o] in query,
|
||||||
|
where: fragment("not(?->>'type' = ?)", o.data, "ChatMessage")
|
||||||
|
)
|
||||||
|
else
|
||||||
|
query
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp exclude_invisible_actors(query, %{invisible_actors: true}), do: query
|
defp exclude_invisible_actors(query, %{invisible_actors: true}), do: query
|
||||||
|
|
||||||
defp exclude_invisible_actors(query, _opts) do
|
defp exclude_invisible_actors(query, _opts) do
|
||||||
|
@ -1429,6 +1441,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|> restrict_filtered(opts)
|
|> restrict_filtered(opts)
|
||||||
|> maybe_restrict_deactivated_users(opts)
|
|> maybe_restrict_deactivated_users(opts)
|
||||||
|> exclude_poll_votes(opts)
|
|> exclude_poll_votes(opts)
|
||||||
|
|> exclude_chat_messages(opts)
|
||||||
|> exclude_invisible_actors(opts)
|
|> exclude_invisible_actors(opts)
|
||||||
|> exclude_visibility(opts)
|
|> exclude_visibility(opts)
|
||||||
|
|
||||||
|
@ -1554,6 +1567,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
end)
|
end)
|
||||||
|
|
||||||
is_locked = data["manuallyApprovesFollowers"] || false
|
is_locked = data["manuallyApprovesFollowers"] || false
|
||||||
|
capabilities = data["capabilities"] || %{}
|
||||||
|
accepts_chat_messages = capabilities["acceptsChatMessages"]
|
||||||
data = Transmogrifier.maybe_fix_user_object(data)
|
data = Transmogrifier.maybe_fix_user_object(data)
|
||||||
is_discoverable = data["discoverable"] || false
|
is_discoverable = data["discoverable"] || false
|
||||||
invisible = data["invisible"] || false
|
invisible = data["invisible"] || false
|
||||||
|
@ -1612,6 +1627,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
shared_inbox: shared_inbox,
|
shared_inbox: shared_inbox,
|
||||||
pinned_objects: pinned_objects,
|
pinned_objects: pinned_objects,
|
||||||
nickname: nickname
|
nickname: nickname
|
||||||
|
accepts_chat_messages: accepts_chat_messages,
|
||||||
|
pinned_objects: pinned_objects
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,28 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
||||||
Map.put(object, "quoteUri", quote_object.data["id"])
|
Map.put(object, "quoteUri", quote_object.data["id"])
|
||||||
else
|
else
|
||||||
_ -> object
|
_ -> object
|
||||||
|
|
||||||
|
def chat_message(actor, recipient, content, opts \\ []) do
|
||||||
|
basic = %{
|
||||||
|
"id" => Utils.generate_object_id(),
|
||||||
|
"actor" => actor.ap_id,
|
||||||
|
"type" => "ChatMessage",
|
||||||
|
"to" => [recipient],
|
||||||
|
"content" => content,
|
||||||
|
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||||
|
"emoji" => Emoji.Formatter.get_emoji_map(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
case opts[:attachment] do
|
||||||
|
%Object{data: attachment_data} ->
|
||||||
|
{
|
||||||
|
:ok,
|
||||||
|
Map.put(basic, "attachment", attachment_data),
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:ok, basic, []}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
|
||||||
|
@ -81,6 +83,21 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate(
|
||||||
|
%{"type" => "Create", "object" => %{"type" => "ChatMessage"} = object} = create_activity,
|
||||||
|
meta
|
||||||
|
) do
|
||||||
|
with {:ok, object_data} <- cast_and_apply(object),
|
||||||
|
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
|
||||||
|
{:ok, create_activity} <-
|
||||||
|
create_activity
|
||||||
|
|> CreateChatMessageValidator.cast_and_validate(meta)
|
||||||
|
|> Ecto.Changeset.apply_action(:insert) do
|
||||||
|
create_activity = stringify_keys(create_activity)
|
||||||
|
{:ok, create_activity, meta}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def validate(
|
def validate(
|
||||||
%{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
|
%{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
|
||||||
meta
|
meta
|
||||||
|
@ -160,7 +177,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
|
|
||||||
def validate(%{"type" => type} = object, meta)
|
def validate(%{"type" => type} = object, meta)
|
||||||
when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
|
when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
|
||||||
Answer] do
|
ChatMessage Answer] do
|
||||||
validator =
|
validator =
|
||||||
case type do
|
case type do
|
||||||
"Accept" -> AcceptRejectValidator
|
"Accept" -> AcceptRejectValidator
|
||||||
|
@ -170,6 +187,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
"Like" -> LikeValidator
|
"Like" -> LikeValidator
|
||||||
"EmojiReact" -> EmojiReactValidator
|
"EmojiReact" -> EmojiReactValidator
|
||||||
"Announce" -> AnnounceValidator
|
"Announce" -> AnnounceValidator
|
||||||
|
"ChatMessage" -> ChatMessageValidator
|
||||||
"Answer" -> AnswerValidator
|
"Answer" -> AnswerValidator
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -202,6 +220,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cast_and_apply(%{"type" => "ChatMessage"} = object) do
|
||||||
|
ChatMessageValidator.cast_and_apply(object)
|
||||||
|
end
|
||||||
|
|
||||||
def cast_and_apply(%{"type" => "Question"} = object) do
|
def cast_and_apply(%{"type" => "Question"} = object) do
|
||||||
QuestionValidator.cast_and_apply(object)
|
QuestionValidator.cast_and_apply(object)
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
# 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
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
|
||||||
|
|
||||||
# Activities and Objects
|
# Activities and Objects, except (Create)ChatMessage
|
||||||
defmacro message_fields do
|
defmacro message_fields do
|
||||||
quote bind_quoted: binding() do
|
quote bind_quoted: binding() do
|
||||||
field(:type, :string)
|
field(:type, :string)
|
||||||
|
@ -39,7 +39,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Basically objects that aren't Answer
|
# Basically objects that aren't ChatMessage and Answer
|
||||||
defmacro status_object_fields do
|
defmacro status_object_fields do
|
||||||
quote bind_quoted: binding() do
|
quote bind_quoted: binding() do
|
||||||
# TODO: Remove actor on objects
|
# TODO: Remove actor on objects
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
# 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
|
|
@ -48,6 +48,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
|
||||||
Answer
|
Answer
|
||||||
Article
|
Article
|
||||||
Audio
|
Audio
|
||||||
|
ChatMessage
|
||||||
Event
|
Event
|
||||||
Note
|
Note
|
||||||
Page
|
Page
|
||||||
|
|
|
@ -10,6 +10,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
collection, and so on.
|
collection, and so on.
|
||||||
"""
|
"""
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Chat
|
||||||
|
alias Pleroma.Chat.MessageReference
|
||||||
alias Pleroma.FollowingRelationship
|
alias Pleroma.FollowingRelationship
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
@ -26,6 +28,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||||
@logger Pleroma.Config.get([:side_effects, :logger], Logger)
|
@logger Pleroma.Config.get([:side_effects, :logger], Logger)
|
||||||
|
|
||||||
@behaviour Pleroma.Web.ActivityPub.SideEffects.Handling
|
@behaviour Pleroma.Web.ActivityPub.SideEffects.Handling
|
||||||
|
@ -313,6 +316,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
Object.decrease_replies_count(in_reply_to)
|
Object.decrease_replies_count(in_reply_to)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
MessageReference.delete_for_object(deleted_object)
|
||||||
|
|
||||||
ap_streamer().stream_out(object)
|
ap_streamer().stream_out(object)
|
||||||
ap_streamer().stream_out_participations(deleted_object, user)
|
ap_streamer().stream_out_participations(deleted_object, user)
|
||||||
:ok
|
:ok
|
||||||
|
@ -472,8 +477,43 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
|
def handle_object_creation(%{"type" => "ChatMessage"} = object, _activity, meta) do
|
||||||
|
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
|
||||||
|
actor = User.get_cached_by_ap_id(object.data["actor"])
|
||||||
|
recipient = User.get_cached_by_ap_id(hd(object.data["to"]))
|
||||||
|
|
||||||
|
streamables =
|
||||||
|
[[actor, recipient], [recipient, actor]]
|
||||||
|
|> Enum.uniq()
|
||||||
|
|> Enum.map(fn [user, other_user] ->
|
||||||
|
if user.local do
|
||||||
|
{:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
|
||||||
|
{:ok, cm_ref} = MessageReference.create(chat, object, user.ap_id != actor.ap_id)
|
||||||
|
|
||||||
|
@cachex.put(
|
||||||
|
:chat_message_id_idempotency_key_cache,
|
||||||
|
cm_ref.id,
|
||||||
|
meta[:idempotency_key]
|
||||||
|
)
|
||||||
|
|
||||||
|
{
|
||||||
|
["user", "user:pleroma_chat"],
|
||||||
|
{user, %{cm_ref | chat: chat, object: object}}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|
|
||||||
|
meta =
|
||||||
|
meta
|
||||||
|
|> add_streamables(streamables)
|
||||||
|
|
||||||
|
{:ok, object, meta}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_object_creation(%{"type" => "Question"} = object, activity, meta) do
|
def handle_object_creation(%{"type" => "Question"} = object, activity, meta) do
|
||||||
|
@ -574,6 +614,13 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
meta
|
meta
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp add_streamables(meta, streamables) do
|
||||||
|
existing = Keyword.get(meta, :streamables, [])
|
||||||
|
|
||||||
|
meta
|
||||||
|
|> Keyword.put(:streamables, streamables ++ existing)
|
||||||
|
end
|
||||||
|
|
||||||
defp add_notifications(meta, notifications) do
|
defp add_notifications(meta, notifications) do
|
||||||
existing = Keyword.get(meta, :notifications, [])
|
existing = Keyword.get(meta, :notifications, [])
|
||||||
|
|
||||||
|
|
|
@ -447,7 +447,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
%{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
|
%{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
when objtype in ~w{Question Answer Audio Video Event Article Note Page} do
|
when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note Page} do
|
||||||
fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
|
fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
|
||||||
|
|
||||||
object =
|
object =
|
||||||
|
@ -927,6 +927,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
Map.put(object, "attributedTo", attributed_to)
|
Map.put(object, "attributedTo", attributed_to)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: Revisit this
|
||||||
|
def prepare_attachments(%{"type" => "ChatMessage"} = object), do: object
|
||||||
|
|
||||||
def prepare_attachments(object) do
|
def prepare_attachments(object) do
|
||||||
attachments =
|
attachments =
|
||||||
object
|
object
|
||||||
|
|
|
@ -81,7 +81,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
|
|
||||||
fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue"))
|
fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue"))
|
||||||
|
|
||||||
capabilities = %{}
|
capabilities =
|
||||||
|
if is_boolean(user.accepts_chat_messages) do
|
||||||
|
%{
|
||||||
|
"acceptsChatMessages" => user.accepts_chat_messages
|
||||||
|
}
|
||||||
|
else
|
||||||
|
%{}
|
||||||
|
end
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"id" => user.ap_id,
|
"id" => user.ap_id,
|
||||||
|
|
|
@ -52,6 +52,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
when action in [:list_user_statuses]
|
when action in [:list_user_statuses]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
plug(
|
||||||
|
OAuthScopesPlug,
|
||||||
|
%{scopes: ["admin:read:chats"]}
|
||||||
|
when action in [:list_user_chats]
|
||||||
|
)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["admin:read"]}
|
%{scopes: ["admin:read"]}
|
||||||
|
@ -100,6 +106,20 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = _params) do
|
||||||
|
with %User{id: user_id} <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
|
||||||
|
chats =
|
||||||
|
Pleroma.Chat.for_user_query(user_id)
|
||||||
|
|> Pleroma.Repo.all()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_view(AdminAPI.ChatView)
|
||||||
|
|> render("index.json", chats: chats)
|
||||||
|
else
|
||||||
|
_ -> {:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
|
def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
|
||||||
with {:ok, _} <- User.tag(nicknames, tags) do
|
with {:ok, _} <- User.tag(nicknames, tags) do
|
||||||
ModerationLog.insert_log(%{
|
ModerationLog.insert_log(%{
|
||||||
|
|
85
lib/pleroma/web/admin_api/controllers/chat_controller.ex
Normal file
85
lib/pleroma/web/admin_api/controllers/chat_controller.ex
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-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
|
30
lib/pleroma/web/admin_api/views/chat_view.ex
Normal file
30
lib/pleroma/web/admin_api/views/chat_view.ex
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-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
|
|
@ -84,6 +84,7 @@ defmodule Pleroma.Web.ApiSpec do
|
||||||
%{
|
%{
|
||||||
"name" => "Administration",
|
"name" => "Administration",
|
||||||
"tags" => [
|
"tags" => [
|
||||||
|
"Chat administration",
|
||||||
"Emoji pack administration",
|
"Emoji pack administration",
|
||||||
"Frontend managment",
|
"Frontend managment",
|
||||||
"Instance configuration",
|
"Instance configuration",
|
||||||
|
@ -113,6 +114,7 @@ defmodule Pleroma.Web.ApiSpec do
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
%{"name" => "Instance", "tags" => ["Custom emojis"]},
|
%{"name" => "Instance", "tags" => ["Custom emojis"]},
|
||||||
|
%{"name" => "Messaging", "tags" => ["Chats", "Conversations"]},
|
||||||
%{
|
%{
|
||||||
"name" => "Statuses",
|
"name" => "Statuses",
|
||||||
"tags" => [
|
"tags" => [
|
||||||
|
|
|
@ -613,6 +613,11 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description: "Whether manual approval of follow requests is required."
|
description: "Whether manual approval of follow requests is required."
|
||||||
},
|
},
|
||||||
|
accepts_chat_messages: %Schema{
|
||||||
|
allOf: [BooleanLike],
|
||||||
|
nullable: true,
|
||||||
|
description: "Whether the user accepts receiving chat messages."
|
||||||
|
},
|
||||||
fields_attributes: %Schema{
|
fields_attributes: %Schema{
|
||||||
nullable: true,
|
nullable: true,
|
||||||
oneOf: [
|
oneOf: [
|
||||||
|
|
96
lib/pleroma/web/api_spec/operations/admin/chat_operation.ex
Normal file
96
lib/pleroma/web/api_spec/operations/admin/chat_operation.ex
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-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
|
361
lib/pleroma/web/api_spec/operations/chat_operation.ex
Normal file
361
lib/pleroma/web/api_spec/operations/chat_operation.ex
Normal file
|
@ -0,0 +1,361 @@
|
||||||
|
# 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
|
|
@ -174,6 +174,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
|
||||||
"reblog",
|
"reblog",
|
||||||
"mention",
|
"mention",
|
||||||
"pleroma:emoji_reaction",
|
"pleroma:emoji_reaction",
|
||||||
|
"pleroma:chat_mention",
|
||||||
"pleroma:report",
|
"pleroma:report",
|
||||||
"move",
|
"move",
|
||||||
"follow_request",
|
"follow_request",
|
||||||
|
@ -189,6 +190,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
|
||||||
- `poll` - A poll you have voted in or created has ended
|
- `poll` - A poll you have voted in or created has ended
|
||||||
- `move` - Someone moved their account
|
- `move` - Someone moved their account
|
||||||
- `pleroma:emoji_reaction` - Someone reacted with emoji to your status
|
- `pleroma:emoji_reaction` - Someone reacted with emoji to your status
|
||||||
|
- `pleroma:chat_mention` - Someone mentioned you in a chat message
|
||||||
- `pleroma:report` - Someone was reported
|
- `pleroma:report` - Someone was reported
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,6 +142,11 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation do
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description: "Receive poll notifications?"
|
description: "Receive poll notifications?"
|
||||||
},
|
},
|
||||||
|
"pleroma:chat_mention": %Schema{
|
||||||
|
allOf: [BooleanLike],
|
||||||
|
nullable: true,
|
||||||
|
description: "Receive chat notifications?"
|
||||||
|
},
|
||||||
"pleroma:emoji_reaction": %Schema{
|
"pleroma:emoji_reaction": %Schema{
|
||||||
allOf: [BooleanLike],
|
allOf: [BooleanLike],
|
||||||
nullable: true,
|
nullable: true,
|
||||||
|
@ -211,6 +216,11 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation do
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description: "Receive poll notifications?"
|
description: "Receive poll notifications?"
|
||||||
},
|
},
|
||||||
|
"pleroma:chat_mention": %Schema{
|
||||||
|
allOf: [BooleanLike],
|
||||||
|
nullable: true,
|
||||||
|
description: "Receive chat notifications?"
|
||||||
|
},
|
||||||
"pleroma:emoji_reaction": %Schema{
|
"pleroma:emoji_reaction": %Schema{
|
||||||
allOf: [BooleanLike],
|
allOf: [BooleanLike],
|
||||||
nullable: true,
|
nullable: true,
|
||||||
|
|
|
@ -43,7 +43,7 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation do
|
||||||
tags: ["Timelines"],
|
tags: ["Timelines"],
|
||||||
summary: "Direct timeline",
|
summary: "Direct timeline",
|
||||||
description:
|
description:
|
||||||
"View statuses with a “direct” scope addressed to the account. Using this endpoint is discouraged, please use [conversations](#tag/Conversations).",
|
"View statuses with a “direct” scope addressed to the account. Using this endpoint is discouraged, please use [conversations](#tag/Conversations) or [chats](#tag/Chats).",
|
||||||
parameters: [with_muted_param() | pagination_params()],
|
parameters: [with_muted_param() | pagination_params()],
|
||||||
security: [%{"oAuth" => ["read:statuses"]}],
|
security: [%{"oAuth" => ["read:statuses"]}],
|
||||||
operationId: "TimelineController.direct",
|
operationId: "TimelineController.direct",
|
||||||
|
|
|
@ -47,6 +47,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
description: "whether the user allows automatically follow moved following accounts"
|
description: "whether the user allows automatically follow moved following accounts"
|
||||||
},
|
},
|
||||||
background_image: %Schema{type: :string, nullable: true, format: :uri},
|
background_image: %Schema{type: :string, nullable: true, format: :uri},
|
||||||
|
chat_token: %Schema{type: :string},
|
||||||
is_confirmed: %Schema{
|
is_confirmed: %Schema{
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description:
|
description:
|
||||||
|
@ -101,6 +102,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
description:
|
description:
|
||||||
"A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`"
|
"A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`"
|
||||||
},
|
},
|
||||||
|
accepts_chat_messages: %Schema{type: :boolean, nullable: true},
|
||||||
favicon: %Schema{
|
favicon: %Schema{
|
||||||
type: :string,
|
type: :string,
|
||||||
format: :uri,
|
format: :uri,
|
||||||
|
@ -179,6 +181,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
"is_admin" => false,
|
"is_admin" => false,
|
||||||
"is_moderator" => false,
|
"is_moderator" => false,
|
||||||
"skip_thread_containment" => false,
|
"skip_thread_containment" => false,
|
||||||
|
"accepts_chat_messages" => true,
|
||||||
|
"chat_token" =>
|
||||||
|
"SFMyNTY.g3QAAAACZAAEZGF0YW0AAAASOXRLaTNlc2JHN09RZ1oyOTIwZAAGc2lnbmVkbgYARNplS3EB.Mb_Iaqew2bN1I1o79B_iP7encmVCpTKC4OtHZRxdjKc",
|
||||||
"unread_conversation_count" => 0,
|
"unread_conversation_count" => 0,
|
||||||
"tags" => [],
|
"tags" => [],
|
||||||
"notification_settings" => %{
|
"notification_settings" => %{
|
||||||
|
|
75
lib/pleroma/web/api_spec/schemas/chat.ex
Normal file
75
lib/pleroma/web/api_spec/schemas/chat.ex
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
# 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
|
77
lib/pleroma/web/api_spec/schemas/chat_message.ex
Normal file
77
lib/pleroma/web/api_spec/schemas/chat_message.ex
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
# 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
|
45
lib/pleroma/web/channels/user_socket.ex
Normal file
45
lib/pleroma/web/channels/user_socket.ex
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
# 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
|
|
@ -5,6 +5,7 @@
|
||||||
defmodule Pleroma.Web.CommonAPI do
|
defmodule Pleroma.Web.CommonAPI do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
|
alias Pleroma.Formatter
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.ThreadMute
|
alias Pleroma.ThreadMute
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
@ -29,6 +30,57 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
|
||||||
|
with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
|
||||||
|
:ok <- validate_chat_content_length(content, !!maybe_attachment),
|
||||||
|
{_, {:ok, chat_message_data, _meta}} <-
|
||||||
|
{:build_object,
|
||||||
|
Builder.chat_message(
|
||||||
|
user,
|
||||||
|
recipient.ap_id,
|
||||||
|
content |> format_chat_content,
|
||||||
|
attachment: maybe_attachment
|
||||||
|
)},
|
||||||
|
{_, {:ok, create_activity_data, _meta}} <-
|
||||||
|
{:build_create_activity, Builder.create(user, chat_message_data, [recipient.ap_id])},
|
||||||
|
{_, {:ok, %Activity{} = activity, _meta}} <-
|
||||||
|
{:common_pipeline,
|
||||||
|
Pipeline.common_pipeline(create_activity_data,
|
||||||
|
local: true,
|
||||||
|
idempotency_key: opts[:idempotency_key]
|
||||||
|
)} do
|
||||||
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
{:common_pipeline, {:reject, _} = e} -> e
|
||||||
|
e -> e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_chat_content(nil), do: nil
|
||||||
|
|
||||||
|
defp format_chat_content(content) do
|
||||||
|
{text, _, _} =
|
||||||
|
content
|
||||||
|
|> Formatter.html_escape("text/plain")
|
||||||
|
|> Formatter.linkify()
|
||||||
|
|> (fn {text, mentions, tags} ->
|
||||||
|
{String.replace(text, ~r/\r?\n/, "<br>"), mentions, tags}
|
||||||
|
end).()
|
||||||
|
|
||||||
|
text
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_chat_content_length(_, true), do: :ok
|
||||||
|
defp validate_chat_content_length(nil, false), do: {:error, :no_content}
|
||||||
|
|
||||||
|
defp validate_chat_content_length(content, _) do
|
||||||
|
if String.length(content) <= Pleroma.Config.get([:instance, :chat_limit]) do
|
||||||
|
:ok
|
||||||
|
else
|
||||||
|
{:error, :content_too_long}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def unblock(blocker, blocked) do
|
def unblock(blocker, blocked) do
|
||||||
with {_, %Activity{} = block} <- {:fetch_block, Utils.fetch_latest_block(blocker, blocked)},
|
with {_, %Activity{} = block} <- {:fetch_block, Utils.fetch_latest_block(blocker, blocked)},
|
||||||
{:ok, unblock_data, _} <- Builder.undo(blocker, block),
|
{:ok, unblock_data, _} <- Builder.undo(blocker, block),
|
||||||
|
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.Endpoint do
|
||||||
|
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
|
|
||||||
|
socket("/socket", Pleroma.Web.UserSocket)
|
||||||
socket("/live", Phoenix.LiveView.Socket)
|
socket("/live", Phoenix.LiveView.Socket)
|
||||||
|
|
||||||
plug(Pleroma.Web.Plugs.SetLocalePlug)
|
plug(Pleroma.Web.Plugs.SetLocalePlug)
|
||||||
|
|
|
@ -154,10 +154,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||||
|
|
||||||
@doc "GET /api/v1/accounts/verify_credentials"
|
@doc "GET /api/v1/accounts/verify_credentials"
|
||||||
def verify_credentials(%{assigns: %{user: user}} = conn, _) do
|
def verify_credentials(%{assigns: %{user: user}} = conn, _) do
|
||||||
|
chat_token = Phoenix.Token.sign(conn, "user socket", user.id)
|
||||||
|
|
||||||
render(conn, "show.json",
|
render(conn, "show.json",
|
||||||
user: user,
|
user: user,
|
||||||
for: user,
|
for: user,
|
||||||
with_pleroma_settings: true
|
with_pleroma_settings: true,
|
||||||
|
with_chat_token: chat_token
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -191,7 +194,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||||
:show_role,
|
:show_role,
|
||||||
:skip_thread_containment,
|
:skip_thread_containment,
|
||||||
:allow_following_move,
|
:allow_following_move,
|
||||||
:also_known_as
|
:also_known_as,
|
||||||
|
:accepts_chat_messages
|
||||||
]
|
]
|
||||||
|> Enum.reduce(%{}, fn key, acc ->
|
|> Enum.reduce(%{}, fn key, acc ->
|
||||||
Maps.put_if_present(acc, key, params[key], &{:ok, Params.truthy_param?(&1)})
|
Maps.put_if_present(acc, key, params[key], &{:ok, Params.truthy_param?(&1)})
|
||||||
|
|
|
@ -307,6 +307,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
||||||
relationship: relationship,
|
relationship: relationship,
|
||||||
skip_thread_containment: user.skip_thread_containment,
|
skip_thread_containment: user.skip_thread_containment,
|
||||||
background_image: image_url(user.background) |> MediaProxy.url(),
|
background_image: image_url(user.background) |> MediaProxy.url(),
|
||||||
|
accepts_chat_messages: user.accepts_chat_messages,
|
||||||
favicon: favicon
|
favicon: favicon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -314,6 +315,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
||||||
|> maybe_put_settings(user, opts[:for], opts)
|
|> maybe_put_settings(user, opts[:for], opts)
|
||||||
|> maybe_put_notification_settings(user, opts[:for])
|
|> maybe_put_notification_settings(user, opts[:for])
|
||||||
|> maybe_put_settings_store(user, opts[:for], opts)
|
|> maybe_put_settings_store(user, opts[:for], opts)
|
||||||
|
|> maybe_put_chat_token(user, opts[:for], opts)
|
||||||
|> maybe_put_activation_status(user, opts[:for])
|
|> maybe_put_activation_status(user, opts[:for])
|
||||||
|> maybe_put_follow_requests_count(user, opts[:for])
|
|> maybe_put_follow_requests_count(user, opts[:for])
|
||||||
|> maybe_put_allow_following_move(user, opts[:for])
|
|> maybe_put_allow_following_move(user, opts[:for])
|
||||||
|
@ -367,6 +369,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
||||||
|
|
||||||
defp maybe_put_settings_store(data, _, _, _), do: data
|
defp maybe_put_settings_store(data, _, _, _), do: data
|
||||||
|
|
||||||
|
defp maybe_put_chat_token(data, %User{id: id}, %User{id: id}, %{
|
||||||
|
with_chat_token: token
|
||||||
|
}) do
|
||||||
|
data
|
||||||
|
|> Kernel.put_in([:pleroma, :chat_token], token)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_put_chat_token(data, _, _, _), do: data
|
||||||
|
|
||||||
defp maybe_put_role(data, %User{show_role: true} = user, _) do
|
defp maybe_put_role(data, %User{show_role: true} = user, _) do
|
||||||
data
|
data
|
||||||
|> Kernel.put_in([:pleroma, :is_admin], user.is_admin)
|
|> Kernel.put_in([:pleroma, :is_admin], user.is_admin)
|
||||||
|
|
|
@ -37,6 +37,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
||||||
background_upload_limit: Keyword.get(instance, :background_upload_limit),
|
background_upload_limit: Keyword.get(instance, :background_upload_limit),
|
||||||
banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
|
banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
|
||||||
background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image),
|
background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image),
|
||||||
|
shout_limit: Config.get([:shout, :limit]),
|
||||||
description_limit: Keyword.get(instance, :description_limit),
|
description_limit: Keyword.get(instance, :description_limit),
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
metadata: %{
|
metadata: %{
|
||||||
|
|
|
@ -6,7 +6,9 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Chat.MessageReference
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.UserRelationship
|
alias Pleroma.UserRelationship
|
||||||
alias Pleroma.Web.AdminAPI.Report
|
alias Pleroma.Web.AdminAPI.Report
|
||||||
|
@ -16,6 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
alias Pleroma.Web.MastodonAPI.NotificationView
|
alias Pleroma.Web.MastodonAPI.NotificationView
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
|
||||||
|
|
||||||
defp object_id_for(%{data: %{"object" => %{"id" => id}}}) when is_binary(id), do: id
|
defp object_id_for(%{data: %{"object" => %{"id" => id}}}) when is_binary(id), do: id
|
||||||
|
|
||||||
|
@ -125,6 +128,9 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|
||||||
|> put_status(parent_activity_fn.(), reading_user, status_render_opts)
|
|> put_status(parent_activity_fn.(), reading_user, status_render_opts)
|
||||||
|> put_emoji(activity)
|
|> put_emoji(activity)
|
||||||
|
|
||||||
|
"pleroma:chat_mention" ->
|
||||||
|
put_chat_message(response, activity, reading_user, status_render_opts)
|
||||||
|
|
||||||
"pleroma:report" ->
|
"pleroma:report" ->
|
||||||
put_report(response, activity)
|
put_report(response, activity)
|
||||||
|
|
||||||
|
@ -145,6 +151,17 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|
||||||
|> Map.put(:emoji_url, MediaProxy.url(Pleroma.Emoji.emoji_url(activity.data)))
|
|> Map.put(:emoji_url, MediaProxy.url(Pleroma.Emoji.emoji_url(activity.data)))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp put_chat_message(response, activity, reading_user, opts) do
|
||||||
|
object = Object.normalize(activity, fetch: false)
|
||||||
|
author = User.get_cached_by_ap_id(object.data["actor"])
|
||||||
|
chat = Pleroma.Chat.get(reading_user.id, author.ap_id)
|
||||||
|
cm_ref = MessageReference.for_chat_and_object(chat, object)
|
||||||
|
render_opts = Map.merge(opts, %{for: reading_user, chat_message_reference: cm_ref})
|
||||||
|
chat_message_render = MessageReferenceView.render("show.json", render_opts)
|
||||||
|
|
||||||
|
Map.put(response, :chat_message, chat_message_render)
|
||||||
|
end
|
||||||
|
|
||||||
defp put_status(response, activity, reading_user, opts) do
|
defp put_status(response, activity, reading_user, opts) do
|
||||||
status_render_opts = Map.merge(opts, %{activity: activity, for: reading_user})
|
status_render_opts = Map.merge(opts, %{activity: activity, for: reading_user})
|
||||||
status_render = StatusView.render("show.json", status_render_opts)
|
status_render = StatusView.render("show.json", status_render_opts)
|
||||||
|
|
188
lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
Normal file
188
lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
# 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
|
|
@ -0,0 +1,63 @@
|
||||||
|
# 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
|
44
lib/pleroma/web/pleroma_api/views/chat_view.ex
Normal file
44
lib/pleroma/web/pleroma_api/views/chat_view.ex
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# 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
|
|
@ -124,6 +124,13 @@ defmodule Pleroma.Web.Push.Impl do
|
||||||
|
|
||||||
def format_body(activity, actor, object, mastodon_type \\ nil)
|
def format_body(activity, actor, object, mastodon_type \\ nil)
|
||||||
|
|
||||||
|
def format_body(_activity, actor, %{data: %{"type" => "ChatMessage"} = data}, _) do
|
||||||
|
case data["content"] do
|
||||||
|
nil -> "@#{actor.nickname}: (Attachment)"
|
||||||
|
content -> "@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def format_body(
|
def format_body(
|
||||||
%{activity: %{data: %{"type" => "Create"}}},
|
%{activity: %{data: %{"type" => "Create"}}},
|
||||||
actor,
|
actor,
|
||||||
|
@ -190,6 +197,7 @@ defmodule Pleroma.Web.Push.Impl do
|
||||||
"reblog" -> "New Repeat"
|
"reblog" -> "New Repeat"
|
||||||
"favourite" -> "New Favorite"
|
"favourite" -> "New Favorite"
|
||||||
"update" -> "New Update"
|
"update" -> "New Update"
|
||||||
|
"pleroma:chat_mention" -> "New Chat Message"
|
||||||
"pleroma:emoji_reaction" -> "New Reaction"
|
"pleroma:emoji_reaction" -> "New Reaction"
|
||||||
type -> "New #{String.capitalize(type || "event")}"
|
type -> "New #{String.capitalize(type || "event")}"
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,7 +26,7 @@ defmodule Pleroma.Web.Push.Subscription do
|
||||||
end
|
end
|
||||||
|
|
||||||
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
|
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
|
||||||
@supported_alert_types ~w[follow favourite mention reblog poll pleroma:emoji_reaction]a
|
@supported_alert_types ~w[follow favourite mention reblog poll pleroma:chat_mention pleroma:emoji_reaction]a
|
||||||
|
|
||||||
defp alerts(%{data: %{alerts: alerts}}) do
|
defp alerts(%{data: %{alerts: alerts}}) do
|
||||||
alerts = Map.take(alerts, @supported_alert_types)
|
alerts = Map.take(alerts, @supported_alert_types)
|
||||||
|
|
|
@ -256,8 +256,12 @@ defmodule Pleroma.Web.Router do
|
||||||
patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials)
|
patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials)
|
||||||
|
|
||||||
get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
|
get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
|
||||||
|
get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
|
||||||
|
|
||||||
get("/statuses", StatusController, :index)
|
get("/statuses", StatusController, :index)
|
||||||
|
|
||||||
|
get("/chats/:id", ChatController, :show)
|
||||||
|
get("/chats/:id/messages", ChatController, :messages)
|
||||||
end
|
end
|
||||||
|
|
||||||
# AdminAPI: admins and mods (staff) can perform these actions
|
# AdminAPI: admins and mods (staff) can perform these actions
|
||||||
|
@ -297,6 +301,8 @@ defmodule Pleroma.Web.Router do
|
||||||
|
|
||||||
post("/reload_emoji", AdminAPIController, :reload_emoji)
|
post("/reload_emoji", AdminAPIController, :reload_emoji)
|
||||||
get("/stats", AdminAPIController, :stats)
|
get("/stats", AdminAPIController, :stats)
|
||||||
|
|
||||||
|
delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/v1/pleroma/emoji", Pleroma.Web.PleromaAPI do
|
scope "/api/v1/pleroma/emoji", Pleroma.Web.PleromaAPI do
|
||||||
|
@ -425,6 +431,14 @@ defmodule Pleroma.Web.Router do
|
||||||
scope [] do
|
scope [] do
|
||||||
pipe_through(:authenticated_api)
|
pipe_through(:authenticated_api)
|
||||||
|
|
||||||
|
post("/chats/by-account-id/:id", ChatController, :create)
|
||||||
|
get("/chats/:id", ChatController, :show)
|
||||||
|
get("/chats/:id/messages", ChatController, :messages)
|
||||||
|
post("/chats/:id/messages", ChatController, :post_chat_message)
|
||||||
|
delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
|
||||||
|
post("/chats/:id/read", ChatController, :mark_as_read)
|
||||||
|
post("/chats/:id/messages/:message_id/read", ChatController, :mark_message_as_read)
|
||||||
|
|
||||||
get("/conversations/:id/statuses", ConversationController, :statuses)
|
get("/conversations/:id/statuses", ConversationController, :statuses)
|
||||||
get("/conversations/:id", ConversationController, :show)
|
get("/conversations/:id", ConversationController, :show)
|
||||||
post("/conversations/read", ConversationController, :mark_as_read)
|
post("/conversations/read", ConversationController, :mark_as_read)
|
||||||
|
@ -490,6 +504,11 @@ defmodule Pleroma.Web.Router do
|
||||||
FrontendSettingsController,
|
FrontendSettingsController,
|
||||||
:delete_profile
|
:delete_profile
|
||||||
)
|
)
|
||||||
|
scope "/api/v2/pleroma", Pleroma.Web.PleromaAPI do
|
||||||
|
scope [] do
|
||||||
|
pipe_through(:authenticated_api)
|
||||||
|
get("/chats", ChatController, :index2)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
||||||
|
|
59
lib/pleroma/web/shout_channel.ex
Normal file
59
lib/pleroma/web/shout_channel.ex
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
# 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
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.Streamer do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Chat.MessageReference
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
|
@ -25,7 +26,7 @@ defmodule Pleroma.Web.Streamer do
|
||||||
def registry, do: @registry
|
def registry, do: @registry
|
||||||
|
|
||||||
@public_streams ["public", "public:local", "public:media", "public:local:media"]
|
@public_streams ["public", "public:local", "public:media", "public:local:media"]
|
||||||
@user_streams ["user", "user:notification", "direct"]
|
@user_streams ["user", "user:notification", "direct", "user:pleroma_chat"]
|
||||||
|
|
||||||
@doc "Expands and authorizes a stream, and registers the process for streaming."
|
@doc "Expands and authorizes a stream, and registers the process for streaming."
|
||||||
@spec get_topic_and_add_socket(
|
@spec get_topic_and_add_socket(
|
||||||
|
@ -246,6 +247,19 @@ defmodule Pleroma.Web.Streamer do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp do_stream(topic, {user, %MessageReference{} = cm_ref})
|
||||||
|
when topic in ["user", "user:pleroma_chat"] do
|
||||||
|
topic = "#{topic}:#{user.id}"
|
||||||
|
|
||||||
|
text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref})
|
||||||
|
|
||||||
|
Registry.dispatch(@registry, topic, fn list ->
|
||||||
|
Enum.each(list, fn {pid, _auth} ->
|
||||||
|
send(pid, {:text, text})
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
defp do_stream("user", item) do
|
defp do_stream("user", item) do
|
||||||
Logger.debug("Trying to push to users")
|
Logger.debug("Trying to push to users")
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,32 @@ defmodule Pleroma.Web.StreamerView do
|
||||||
|
|
||||||
def render("status_update.json", %Activity{} = activity, topic) do
|
def render("status_update.json", %Activity{} = activity, topic) do
|
||||||
activity = Activity.get_create_by_object_ap_id_with_object(activity.object.data["id"])
|
activity = Activity.get_create_by_object_ap_id_with_object(activity.object.data["id"])
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("chat_update.json", %{chat_message_reference: cm_ref}) do
|
||||||
|
# Explicitly giving the cmr for the object here, so we don't accidentally
|
||||||
|
# send a later 'last_message' that was inserted between inserting this and
|
||||||
|
# streaming it out
|
||||||
|
#
|
||||||
|
# It also contains the chat with a cache of the correct unread count
|
||||||
|
Logger.debug("Trying to stream out #{inspect(cm_ref)}")
|
||||||
|
|
||||||
|
representation =
|
||||||
|
Pleroma.Web.PleromaAPI.ChatView.render(
|
||||||
|
"show.json",
|
||||||
|
%{last_message: cm_ref, chat: cm_ref.chat}
|
||||||
|
)
|
||||||
|
|
||||||
|
%{
|
||||||
|
event: "pleroma:chat_update",
|
||||||
|
payload:
|
||||||
|
representation
|
||||||
|
|> Jason.encode!()
|
||||||
|
}
|
||||||
|
|> Jason.encode!()
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("follow_relationships_update.json", item) do
|
||||||
%{
|
%{
|
||||||
stream: [topic],
|
stream: [topic],
|
||||||
event: "status.update",
|
event: "status.update",
|
||||||
|
|
1
test/fixtures/mewmew_no_name.json
vendored
1
test/fixtures/mewmew_no_name.json
vendored
|
@ -8,6 +8,7 @@
|
||||||
],
|
],
|
||||||
"attachment" : [],
|
"attachment" : [],
|
||||||
"capabilities" : {
|
"capabilities" : {
|
||||||
|
"acceptsChatMessages" : true
|
||||||
},
|
},
|
||||||
"discoverable" : false,
|
"discoverable" : false,
|
||||||
"endpoints" : {
|
"endpoints" : {
|
||||||
|
|
|
@ -124,6 +124,7 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
|
||||||
notify_email: "noreply@example.com",
|
notify_email: "noreply@example.com",
|
||||||
description: "A Pleroma instance, an alternative fediverse server",
|
description: "A Pleroma instance, an alternative fediverse server",
|
||||||
limit: 5_000,
|
limit: 5_000,
|
||||||
|
chat_limit: 5_000,
|
||||||
remote_limit: 100_000,
|
remote_limit: 100_000,
|
||||||
upload_limit: 16_000_000,
|
upload_limit: 16_000_000,
|
||||||
avatar_upload_limit: 2_000_000,
|
avatar_upload_limit: 2_000_000,
|
||||||
|
@ -183,7 +184,7 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
|
||||||
{:ok, file} = File.read(temp_file)
|
{:ok, file} = File.read(temp_file)
|
||||||
|
|
||||||
assert file ==
|
assert file ==
|
||||||
"import Config\n\nconfig :pleroma, :instance,\n name: \"Pleroma\",\n email: \"example@example.com\",\n notify_email: \"noreply@example.com\",\n description: \"A Pleroma instance, an alternative fediverse server\",\n limit: 5000,\n remote_limit: 100_000,\n upload_limit: 16_000_000,\n avatar_upload_limit: 2_000_000,\n background_upload_limit: 4_000_000,\n banner_upload_limit: 4_000_000,\n poll_limits: %{\n max_expiration: 31_536_000,\n max_option_chars: 200,\n max_options: 20,\n min_expiration: 0\n },\n registrations_open: true,\n federating: true,\n federation_incoming_replies_max_depth: 100,\n federation_reachability_timeout_days: 7,\n federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],\n allow_relay: true,\n public: true,\n quarantined_instances: [],\n managed_config: true,\n static_dir: \"instance/static/\",\n allowed_post_formats: [\"text/plain\", \"text/html\", \"text/markdown\", \"text/bbcode\"],\n autofollowed_nicknames: [],\n max_pinned_statuses: 1,\n attachment_links: false,\n max_report_comment_size: 1000,\n safe_dm_mentions: false,\n healthcheck: false,\n remote_post_retention_days: 90,\n skip_thread_containment: true,\n limit_to_local_content: :unauthenticated,\n user_bio_length: 5000,\n user_name_length: 100,\n max_account_fields: 10,\n max_remote_account_fields: 20,\n account_field_name_length: 512,\n account_field_value_length: 2048,\n external_user_synchronization: true,\n extended_nickname_format: true,\n multi_factor_authentication: [\n totp: [digits: 6, period: 30],\n backup_codes: [number: 2, length: 6]\n ]\n"
|
"import Config\n\nconfig :pleroma, :instance,\n name: \"Pleroma\",\n email: \"example@example.com\",\n notify_email: \"noreply@example.com\",\n description: \"A Pleroma instance, an alternative fediverse server\",\n limit: 5000,\n chat_limit: 5000,\n remote_limit: 100_000,\n upload_limit: 16_000_000,\n avatar_upload_limit: 2_000_000,\n background_upload_limit: 4_000_000,\n banner_upload_limit: 4_000_000,\n poll_limits: %{\n max_expiration: 31_536_000,\n max_option_chars: 200,\n max_options: 20,\n min_expiration: 0\n },\n registrations_open: true,\n federating: true,\n federation_incoming_replies_max_depth: 100,\n federation_reachability_timeout_days: 7,\n federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],\n allow_relay: true,\n public: true,\n quarantined_instances: [],\n managed_config: true,\n static_dir: \"instance/static/\",\n allowed_post_formats: [\"text/plain\", \"text/html\", \"text/markdown\", \"text/bbcode\"],\n autofollowed_nicknames: [],\n max_pinned_statuses: 1,\n attachment_links: false,\n max_report_comment_size: 1000,\n safe_dm_mentions: false,\n healthcheck: false,\n remote_post_retention_days: 90,\n skip_thread_containment: true,\n limit_to_local_content: :unauthenticated,\n user_bio_length: 5000,\n user_name_length: 100,\n max_account_fields: 10,\n max_remote_account_fields: 20,\n account_field_name_length: 512,\n account_field_value_length: 2048,\n external_user_synchronization: true,\n extended_nickname_format: true,\n multi_factor_authentication: [\n totp: [digits: 6, period: 30],\n backup_codes: [number: 2, length: 6]\n ]\n"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
29
test/pleroma/chat/message_reference_test.exs
Normal file
29
test/pleroma/chat/message_reference_test.exs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# 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
|
84
test/pleroma/chat_test.exs
Normal file
84
test/pleroma/chat_test.exs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
# 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
|
|
@ -289,4 +289,14 @@ defmodule Pleroma.Config.DeprecationWarningsTest do
|
||||||
|
|
||||||
Application.put_env(:tesla, :adapter, Tesla.Mock)
|
Application.put_env(:tesla, :adapter, Tesla.Mock)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "check_old_chat_shoutbox/0" do
|
||||||
|
clear_config([:instance, :chat_limit], 1_000)
|
||||||
|
clear_config([:chat, :enabled], true)
|
||||||
|
|
||||||
|
assert capture_log(fn ->
|
||||||
|
DeprecationWarnings.check_old_chat_shoutbox()
|
||||||
|
end) =~
|
||||||
|
"Your config is using the old namespace for the Shoutbox configuration."
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -140,6 +140,9 @@ defmodule Pleroma.Config.TransferTaskTest do
|
||||||
# That way we are sure that the previous call has finished before we continue.
|
# That way we are sure that the previous call has finished before we continue.
|
||||||
Restarter.Pleroma.rebooted?()
|
Restarter.Pleroma.rebooted?()
|
||||||
end) =~ "pleroma restarted"
|
end) =~ "pleroma restarted"
|
||||||
|
clear_config(:shout)
|
||||||
|
insert(:config, key: :shout, value: [enabled: false])
|
||||||
|
assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "on reboot time subkey" do
|
test "on reboot time subkey" do
|
||||||
|
@ -164,6 +167,10 @@ defmodule Pleroma.Config.TransferTaskTest do
|
||||||
clear_config(Pleroma.Captcha)
|
clear_config(Pleroma.Captcha)
|
||||||
|
|
||||||
insert(:config, key: :rate_limit, value: [enabled: false])
|
insert(:config, key: :rate_limit, value: [enabled: false])
|
||||||
|
clear_config(:shout)
|
||||||
|
clear_config(Pleroma.Captcha)
|
||||||
|
|
||||||
|
insert(:config, key: :shout, value: [enabled: false])
|
||||||
insert(:config, key: Pleroma.Captcha, value: [seconds_valid: 60])
|
insert(:config, key: Pleroma.Captcha, value: [seconds_valid: 60])
|
||||||
|
|
||||||
refute String.contains?(
|
refute String.contains?(
|
||||||
|
|
|
@ -19,6 +19,7 @@ defmodule Pleroma.MigrationHelper.NotificationBackfillTest do
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
||||||
{:ok, post} = CommonAPI.post(user, %{status: "yeah, @#{other_user.nickname}"})
|
{:ok, post} = CommonAPI.post(user, %{status: "yeah, @#{other_user.nickname}"})
|
||||||
|
{:ok, chat} = CommonAPI.post_chat_message(user, other_user, "yo")
|
||||||
{:ok, react} = CommonAPI.react_with_emoji(post.id, other_user, "☕")
|
{:ok, react} = CommonAPI.react_with_emoji(post.id, other_user, "☕")
|
||||||
{:ok, like} = CommonAPI.favorite(other_user, post.id)
|
{:ok, like} = CommonAPI.favorite(other_user, post.id)
|
||||||
{:ok, react_2} = CommonAPI.react_with_emoji(post.id, other_user, "☕")
|
{:ok, react_2} = CommonAPI.react_with_emoji(post.id, other_user, "☕")
|
||||||
|
@ -32,7 +33,7 @@ defmodule Pleroma.MigrationHelper.NotificationBackfillTest do
|
||||||
|> Activity.change(%{data: data})
|
|> Activity.change(%{data: data})
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
|
|
||||||
assert {4, nil} = Repo.update_all(Notification, set: [type: nil])
|
assert {5, nil} = Repo.update_all(Notification, set: [type: nil])
|
||||||
|
|
||||||
NotificationBackfill.fill_in_notification_types()
|
NotificationBackfill.fill_in_notification_types()
|
||||||
|
|
||||||
|
@ -47,6 +48,9 @@ defmodule Pleroma.MigrationHelper.NotificationBackfillTest do
|
||||||
|
|
||||||
assert %{type: "pleroma:emoji_reaction"} =
|
assert %{type: "pleroma:emoji_reaction"} =
|
||||||
Repo.get_by(Notification, user_id: user.id, activity_id: react_2.id)
|
Repo.get_by(Notification, user_id: user.id, activity_id: react_2.id)
|
||||||
|
|
||||||
|
assert %{type: "pleroma:chat_mention"} =
|
||||||
|
Repo.get_by(Notification, user_id: other_user.id, activity_id: chat.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
52
test/pleroma/repo/migrations/rename_instance_chat_test.exs
Normal file
52
test/pleroma/repo/migrations/rename_instance_chat_test.exs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
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
|
36
test/pleroma/user/welcome_chat_message_test.exs
Normal file
36
test/pleroma/user/welcome_chat_message_test.exs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# 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
|
|
@ -443,6 +443,22 @@ defmodule Pleroma.UserTest do
|
||||||
assert activity.actor == welcome_user.ap_id
|
assert activity.actor == welcome_user.ap_id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it sends a welcome chat message if it is set" do
|
||||||
|
welcome_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, this is a chat message")
|
||||||
|
|
||||||
|
cng = User.register_changeset(%User{}, @full_user_data)
|
||||||
|
{:ok, registered_user} = User.register(cng)
|
||||||
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
|
activity = Repo.one(Pleroma.Activity)
|
||||||
|
assert registered_user.ap_id in activity.recipients
|
||||||
|
assert Object.normalize(activity, fetch: false).data["content"] =~ "chat message"
|
||||||
|
assert activity.actor == welcome_user.ap_id
|
||||||
|
end
|
||||||
|
|
||||||
setup do:
|
setup do:
|
||||||
clear_config(
|
clear_config(
|
||||||
[:mrf_simple],
|
[:mrf_simple],
|
||||||
|
@ -467,6 +483,24 @@ defmodule Pleroma.UserTest do
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test "it sends a welcome chat message when Simple policy applied to local instance" do
|
||||||
|
clear_config([:mrf_simple, :media_nsfw], [{"localhost", ""}])
|
||||||
|
|
||||||
|
welcome_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, this is a chat message")
|
||||||
|
|
||||||
|
cng = User.register_changeset(%User{}, @full_user_data)
|
||||||
|
{:ok, registered_user} = User.register(cng)
|
||||||
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
|
activity = Repo.one(Pleroma.Activity)
|
||||||
|
assert registered_user.ap_id in activity.recipients
|
||||||
|
assert Object.normalize(activity, fetch: false).data["content"] =~ "chat message"
|
||||||
|
assert activity.actor == welcome_user.ap_id
|
||||||
|
end
|
||||||
|
|
||||||
test "it sends a welcome email message if it is set" do
|
test "it sends a welcome email message if it is set" do
|
||||||
welcome_user = insert(:user)
|
welcome_user = insert(:user)
|
||||||
clear_config([:welcome, :email, :enabled], true)
|
clear_config([:welcome, :email, :enabled], true)
|
||||||
|
@ -632,6 +666,15 @@ defmodule Pleroma.UserTest do
|
||||||
assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
|
assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it sets the 'accepts_chat_messages' set to true" do
|
||||||
|
changeset = User.register_changeset(%User{}, @full_user_data)
|
||||||
|
assert changeset.valid?
|
||||||
|
|
||||||
|
{:ok, user} = Repo.insert(changeset)
|
||||||
|
|
||||||
|
assert user.accepts_chat_messages
|
||||||
|
end
|
||||||
|
|
||||||
test "it creates a confirmed user" do
|
test "it creates a confirmed user" do
|
||||||
changeset = User.register_changeset(%User{}, @full_user_data)
|
changeset = User.register_changeset(%User{}, @full_user_data)
|
||||||
assert changeset.valid?
|
assert changeset.valid?
|
||||||
|
|
|
@ -187,6 +187,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
|
||||||
assert User.invisible?(user)
|
assert User.invisible?(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it returns a user that accepts chat messages" do
|
||||||
|
user_id = "http://mastodon.example.org/users/admin"
|
||||||
|
{:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
|
||||||
|
|
||||||
|
assert user.accepts_chat_messages
|
||||||
|
end
|
||||||
|
|
||||||
test "works for guppe actors" do
|
test "works for guppe actors" do
|
||||||
user_id = "https://gup.pe/u/bernie2020"
|
user_id = "https://gup.pe/u/bernie2020"
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicyTest do
|
||||||
|
|
||||||
import Pleroma.Web.ActivityPub.MRF.HellthreadPolicy
|
import Pleroma.Web.ActivityPub.MRF.HellthreadPolicy
|
||||||
|
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
@ -46,6 +48,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicyTest do
|
||||||
|
|
||||||
setup do: clear_config(:mrf_hellthread)
|
setup do: clear_config(:mrf_hellthread)
|
||||||
|
|
||||||
|
test "doesn't die on chat messages" do
|
||||||
|
clear_config([:mrf_hellthread], %{delist_threshold: 2, reject_threshold: 0})
|
||||||
|
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post_chat_message(user, other_user, "moin")
|
||||||
|
|
||||||
|
assert {:ok, _} = filter(activity.data)
|
||||||
|
end
|
||||||
|
|
||||||
describe "reject" do
|
describe "reject" do
|
||||||
test "rejects the message if the recipient count is above reject_threshold", %{
|
test "rejects the message if the recipient count is above reject_threshold", %{
|
||||||
message: message,
|
message: message,
|
||||||
|
|
|
@ -0,0 +1,212 @@
|
||||||
|
# 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
|
|
@ -7,6 +7,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Chat
|
||||||
|
alias Pleroma.Chat.MessageReference
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
@ -15,7 +17,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Builder
|
alias Pleroma.Web.ActivityPub.Builder
|
||||||
alias Pleroma.Web.ActivityPub.SideEffects
|
alias Pleroma.Web.ActivityPub.SideEffects
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
import Mock
|
import Mock
|
||||||
|
@ -55,22 +56,15 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
|
||||||
author = insert(:user, local: true)
|
author = insert(:user, local: true)
|
||||||
recipient = insert(:user, local: true)
|
recipient = insert(:user, local: true)
|
||||||
|
|
||||||
{:ok, note_data, _meta} =
|
{:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
|
||||||
Builder.note(%Pleroma.Web.CommonAPI.ActivityDraft{
|
|
||||||
user: author,
|
|
||||||
to: [recipient.ap_id],
|
|
||||||
mentions: [recipient],
|
|
||||||
content_html: "hey",
|
|
||||||
extra: %{"id" => Utils.generate_object_id()}
|
|
||||||
})
|
|
||||||
|
|
||||||
{:ok, create_activity_data, _meta} =
|
{:ok, create_activity_data, _meta} =
|
||||||
Builder.create(author, note_data["id"], [recipient.ap_id])
|
Builder.create(author, chat_message_data["id"], [recipient.ap_id])
|
||||||
|
|
||||||
{:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
|
{:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
|
||||||
|
|
||||||
{:ok, _create_activity, meta} =
|
{:ok, _create_activity, meta} =
|
||||||
SideEffects.handle(create_activity, local: false, object_data: note_data)
|
SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
|
||||||
|
|
||||||
assert [notification] = meta[:notifications]
|
assert [notification] = meta[:notifications]
|
||||||
|
|
||||||
|
@ -93,6 +87,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
|
||||||
SideEffects.handle_after_transaction(meta)
|
SideEffects.handle_after_transaction(meta)
|
||||||
|
|
||||||
assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
|
assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
|
||||||
|
assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
|
||||||
assert called(Pleroma.Web.Push.send(notification))
|
assert called(Pleroma.Web.Push.send(notification))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -665,6 +660,147 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "creation of ChatMessages" do
|
||||||
|
test "notifies the recipient" do
|
||||||
|
author = insert(:user, local: false)
|
||||||
|
recipient = insert(:user, local: true)
|
||||||
|
|
||||||
|
{:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
|
||||||
|
|
||||||
|
{:ok, create_activity_data, _meta} =
|
||||||
|
Builder.create(author, chat_message_data["id"], [recipient.ap_id])
|
||||||
|
|
||||||
|
{:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
|
||||||
|
|
||||||
|
{:ok, _create_activity, _meta} =
|
||||||
|
SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
|
||||||
|
|
||||||
|
assert Repo.get_by(Notification, user_id: recipient.id, activity_id: create_activity.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it streams the created ChatMessage" do
|
||||||
|
author = insert(:user, local: true)
|
||||||
|
recipient = insert(:user, local: true)
|
||||||
|
|
||||||
|
{:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
|
||||||
|
|
||||||
|
{:ok, create_activity_data, _meta} =
|
||||||
|
Builder.create(author, chat_message_data["id"], [recipient.ap_id])
|
||||||
|
|
||||||
|
{:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
|
||||||
|
|
||||||
|
{:ok, _create_activity, meta} =
|
||||||
|
SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
|
||||||
|
|
||||||
|
assert [_, _] = meta[:streamables]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it creates a Chat and MessageReferences for the local users and bumps the unread count, except for the author" do
|
||||||
|
author = insert(:user, local: true)
|
||||||
|
recipient = insert(:user, local: true)
|
||||||
|
|
||||||
|
{:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
|
||||||
|
|
||||||
|
{:ok, create_activity_data, _meta} =
|
||||||
|
Builder.create(author, chat_message_data["id"], [recipient.ap_id])
|
||||||
|
|
||||||
|
{:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
|
||||||
|
|
||||||
|
with_mocks([
|
||||||
|
{
|
||||||
|
Pleroma.Web.Streamer,
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
stream: fn _, _ -> nil end
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pleroma.Web.Push,
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
send: fn _ -> nil end
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]) do
|
||||||
|
{:ok, _create_activity, meta} =
|
||||||
|
SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
|
||||||
|
|
||||||
|
# The notification gets created
|
||||||
|
assert [notification] = meta[:notifications]
|
||||||
|
assert notification.activity_id == create_activity.id
|
||||||
|
|
||||||
|
# But it is not sent out
|
||||||
|
refute called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
|
||||||
|
refute called(Pleroma.Web.Push.send(notification))
|
||||||
|
|
||||||
|
# Same for the user chat stream
|
||||||
|
assert [{topics, _}, _] = meta[:streamables]
|
||||||
|
assert topics == ["user", "user:pleroma_chat"]
|
||||||
|
refute called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
|
||||||
|
|
||||||
|
chat = Chat.get(author.id, recipient.ap_id)
|
||||||
|
|
||||||
|
[cm_ref] = MessageReference.for_chat_query(chat) |> Repo.all()
|
||||||
|
|
||||||
|
assert cm_ref.object.data["content"] == "hey"
|
||||||
|
assert cm_ref.unread == false
|
||||||
|
|
||||||
|
chat = Chat.get(recipient.id, author.ap_id)
|
||||||
|
|
||||||
|
[cm_ref] = MessageReference.for_chat_query(chat) |> Repo.all()
|
||||||
|
|
||||||
|
assert cm_ref.object.data["content"] == "hey"
|
||||||
|
assert cm_ref.unread == true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it creates a Chat for the local users and bumps the unread count" do
|
||||||
|
author = insert(:user, local: false)
|
||||||
|
recipient = insert(:user, local: true)
|
||||||
|
|
||||||
|
{:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
|
||||||
|
|
||||||
|
{:ok, create_activity_data, _meta} =
|
||||||
|
Builder.create(author, chat_message_data["id"], [recipient.ap_id])
|
||||||
|
|
||||||
|
{:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
|
||||||
|
|
||||||
|
{:ok, _create_activity, _meta} =
|
||||||
|
SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
|
||||||
|
|
||||||
|
# An object is created
|
||||||
|
assert Object.get_by_ap_id(chat_message_data["id"])
|
||||||
|
|
||||||
|
# The remote user won't get a chat
|
||||||
|
chat = Chat.get(author.id, recipient.ap_id)
|
||||||
|
refute chat
|
||||||
|
|
||||||
|
# The local user will get a chat
|
||||||
|
chat = Chat.get(recipient.id, author.ap_id)
|
||||||
|
assert chat
|
||||||
|
|
||||||
|
author = insert(:user, local: true)
|
||||||
|
recipient = insert(:user, local: true)
|
||||||
|
|
||||||
|
{:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
|
||||||
|
|
||||||
|
{:ok, create_activity_data, _meta} =
|
||||||
|
Builder.create(author, chat_message_data["id"], [recipient.ap_id])
|
||||||
|
|
||||||
|
{:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
|
||||||
|
|
||||||
|
{:ok, _create_activity, _meta} =
|
||||||
|
SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
|
||||||
|
|
||||||
|
# Both users are local and get the chat
|
||||||
|
chat = Chat.get(author.id, recipient.ap_id)
|
||||||
|
assert chat
|
||||||
|
|
||||||
|
chat = Chat.get(recipient.id, author.ap_id)
|
||||||
|
assert chat
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "announce objects" do
|
describe "announce objects" do
|
||||||
setup do
|
setup do
|
||||||
poster = insert(:user)
|
poster = insert(:user)
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
# 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
|
|
@ -157,4 +157,23 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
|
||||||
assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user})
|
assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "acceptsChatMessages" do
|
||||||
|
test "it returns this value if it is set" do
|
||||||
|
true_user = insert(:user, accepts_chat_messages: true)
|
||||||
|
false_user = insert(:user, accepts_chat_messages: false)
|
||||||
|
nil_user = insert(:user, accepts_chat_messages: nil)
|
||||||
|
|
||||||
|
assert %{"capabilities" => %{"acceptsChatMessages" => true}} =
|
||||||
|
UserView.render("user.json", user: true_user)
|
||||||
|
|
||||||
|
assert %{"capabilities" => %{"acceptsChatMessages" => false}} =
|
||||||
|
UserView.render("user.json", user: false_user)
|
||||||
|
|
||||||
|
refute Map.has_key?(
|
||||||
|
UserView.render("user.json", user: nil_user)["capabilities"],
|
||||||
|
"acceptsChatMessages"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -420,6 +420,56 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "GET /api/pleroma/admin/users/:nickname/chats" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
recipients = insert_list(3, :user)
|
||||||
|
|
||||||
|
Enum.each(recipients, fn recipient ->
|
||||||
|
CommonAPI.post_chat_message(user, recipient, "yo")
|
||||||
|
end)
|
||||||
|
|
||||||
|
%{user: user}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders user's chats", %{conn: conn, user: user} do
|
||||||
|
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/chats")
|
||||||
|
|
||||||
|
assert json_response(conn, 200) |> length() == 3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "GET /api/pleroma/admin/users/:nickname/chats unauthorized" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
recipient = insert(:user)
|
||||||
|
CommonAPI.post_chat_message(user, recipient, "yo")
|
||||||
|
%{conn: conn} = oauth_access(["read:chats"])
|
||||||
|
%{conn: conn, user: user}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns 403", %{conn: conn, user: user} do
|
||||||
|
conn
|
||||||
|
|> get("/api/pleroma/admin/users/#{user.nickname}/chats")
|
||||||
|
|> json_response(403)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "GET /api/pleroma/admin/users/:nickname/chats unauthenticated" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
recipient = insert(:user)
|
||||||
|
CommonAPI.post_chat_message(user, recipient, "yo")
|
||||||
|
%{conn: build_conn(), user: user}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns 403", %{conn: conn, user: user} do
|
||||||
|
conn
|
||||||
|
|> get("/api/pleroma/admin/users/#{user.nickname}/chats")
|
||||||
|
|> json_response(403)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "GET /api/v1/pleroma/admin/moderation_log" do
|
describe "GET /api/v1/pleroma/admin/moderation_log" do
|
||||||
setup do
|
setup do
|
||||||
moderator = insert(:user, is_moderator: true)
|
moderator = insert(:user, is_moderator: true)
|
||||||
|
|
218
test/pleroma/web/admin_api/controllers/chat_controller_test.exs
Normal file
218
test/pleroma/web/admin_api/controllers/chat_controller_test.exs
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
# 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
|
1525
test/pleroma/web/admin_api/controllers/config_controller_test.exs
Normal file
1525
test/pleroma/web/admin_api/controllers/config_controller_test.exs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -7,11 +7,13 @@ defmodule Pleroma.Web.CommonAPITest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Chat
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Web.AdminAPI.AccountView
|
alias Pleroma.Web.AdminAPI.AccountView
|
||||||
|
@ -115,6 +117,173 @@ defmodule Pleroma.Web.CommonAPITest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "posting chat messages" do
|
||||||
|
setup do: clear_config([:instance, :chat_limit])
|
||||||
|
|
||||||
|
test "it posts a self-chat" do
|
||||||
|
author = insert(:user)
|
||||||
|
recipient = author
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post_chat_message(
|
||||||
|
author,
|
||||||
|
recipient,
|
||||||
|
"remember to buy milk when milk truk arive"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert activity.data["type"] == "Create"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it posts a chat message without content but with an attachment" do
|
||||||
|
author = 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: author.ap_id)
|
||||||
|
|
||||||
|
with_mocks([
|
||||||
|
{
|
||||||
|
Pleroma.Web.Streamer,
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
stream: fn _, _ ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pleroma.Web.Push,
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
send: fn _ -> nil end
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]) do
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post_chat_message(
|
||||||
|
author,
|
||||||
|
recipient,
|
||||||
|
nil,
|
||||||
|
media_id: upload.id
|
||||||
|
)
|
||||||
|
|
||||||
|
notification =
|
||||||
|
Notification.for_user_and_activity(recipient, activity)
|
||||||
|
|> Repo.preload(:activity)
|
||||||
|
|
||||||
|
assert called(Pleroma.Web.Push.send(notification))
|
||||||
|
assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
|
||||||
|
assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
|
||||||
|
|
||||||
|
assert activity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it adds html newlines" do
|
||||||
|
author = insert(:user)
|
||||||
|
recipient = insert(:user)
|
||||||
|
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post_chat_message(
|
||||||
|
author,
|
||||||
|
recipient,
|
||||||
|
"uguu\nuguuu"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert other_user.ap_id not in activity.recipients
|
||||||
|
|
||||||
|
object = Object.normalize(activity, fetch: false)
|
||||||
|
|
||||||
|
assert object.data["content"] == "uguu<br/>uguuu"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it linkifies" do
|
||||||
|
author = insert(:user)
|
||||||
|
recipient = insert(:user)
|
||||||
|
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post_chat_message(
|
||||||
|
author,
|
||||||
|
recipient,
|
||||||
|
"https://example.org is the site of @#{other_user.nickname} #2hu"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert other_user.ap_id not in activity.recipients
|
||||||
|
|
||||||
|
object = Object.normalize(activity, fetch: false)
|
||||||
|
|
||||||
|
assert object.data["content"] ==
|
||||||
|
"<a href=\"https://example.org\" rel=\"ugc\">https://example.org</a> is the site of <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{other_user.id}\" href=\"#{other_user.ap_id}\" rel=\"ugc\">@<span>#{other_user.nickname}</span></a></span> <a class=\"hashtag\" data-tag=\"2hu\" href=\"http://localhost:4001/tag/2hu\">#2hu</a>"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it posts a chat message" do
|
||||||
|
author = insert(:user)
|
||||||
|
recipient = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post_chat_message(
|
||||||
|
author,
|
||||||
|
recipient,
|
||||||
|
"a test message <script>alert('uuu')</script> :firefox:"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert activity.data["type"] == "Create"
|
||||||
|
assert activity.local
|
||||||
|
object = Object.normalize(activity, fetch: false)
|
||||||
|
|
||||||
|
assert object.data["type"] == "ChatMessage"
|
||||||
|
assert object.data["to"] == [recipient.ap_id]
|
||||||
|
|
||||||
|
assert object.data["content"] ==
|
||||||
|
"a test message <script>alert('uuu')</script> :firefox:"
|
||||||
|
|
||||||
|
assert object.data["emoji"] == %{
|
||||||
|
"firefox" => "http://localhost:4001/emoji/Firefox.gif"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert Chat.get(author.id, recipient.ap_id)
|
||||||
|
assert Chat.get(recipient.id, author.ap_id)
|
||||||
|
|
||||||
|
assert :ok == Pleroma.Web.Federator.perform(:publish, activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it reject messages over the local limit" do
|
||||||
|
clear_config([:instance, :chat_limit], 2)
|
||||||
|
|
||||||
|
author = insert(:user)
|
||||||
|
recipient = insert(:user)
|
||||||
|
|
||||||
|
{:error, message} =
|
||||||
|
CommonAPI.post_chat_message(
|
||||||
|
author,
|
||||||
|
recipient,
|
||||||
|
"123"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert message == :content_too_long
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it reject messages via MRF" do
|
||||||
|
clear_config([:mrf_keyword, :reject], ["GNO"])
|
||||||
|
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
|
||||||
|
|
||||||
|
author = insert(:user)
|
||||||
|
recipient = insert(:user)
|
||||||
|
|
||||||
|
assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} ==
|
||||||
|
CommonAPI.post_chat_message(author, recipient, "GNO/Linux")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "unblocking" do
|
describe "unblocking" do
|
||||||
test "it works even without an existing block activity" do
|
test "it works even without an existing block activity" do
|
||||||
blocked = insert(:user)
|
blocked = insert(:user)
|
||||||
|
|
|
@ -1711,6 +1711,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||||
response = json_response_and_validate_schema(conn, 200)
|
response = json_response_and_validate_schema(conn, 200)
|
||||||
|
|
||||||
assert %{"id" => id, "source" => %{"privacy" => "public"}} = response
|
assert %{"id" => id, "source" => %{"privacy" => "public"}} = response
|
||||||
|
assert response["pleroma"]["chat_token"]
|
||||||
assert response["pleroma"]["unread_notifications_count"] == 6
|
assert response["pleroma"]["unread_notifications_count"] == 6
|
||||||
assert id == to_string(user.id)
|
assert id == to_string(user.id)
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,6 +39,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do
|
||||||
"background_upload_limit" => _,
|
"background_upload_limit" => _,
|
||||||
"banner_upload_limit" => _,
|
"banner_upload_limit" => _,
|
||||||
"background_image" => from_config_background,
|
"background_image" => from_config_background,
|
||||||
|
"shout_limit" => _,
|
||||||
"description_limit" => _
|
"description_limit" => _
|
||||||
} = result
|
} = result
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,27 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
|
||||||
assert response == expected_response
|
assert response == expected_response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "by default, does not contain pleroma:chat_mention" do
|
||||||
|
%{user: user, conn: conn} = oauth_access(["read:notifications"])
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _activity} = CommonAPI.post_chat_message(other_user, user, "hey")
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/notifications")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert [] == result
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/notifications?include_types[]=pleroma:chat_mention")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert [_] = result
|
||||||
|
end
|
||||||
|
|
||||||
test "by default, does not contain pleroma:report" do
|
test "by default, does not contain pleroma:report" do
|
||||||
%{user: user, conn: conn} = oauth_access(["read:notifications"])
|
%{user: user, conn: conn} = oauth_access(["read:notifications"])
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
|
@ -113,6 +113,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do
|
||||||
"favourite" => true,
|
"favourite" => true,
|
||||||
"follow" => true,
|
"follow" => true,
|
||||||
"reblog" => true,
|
"reblog" => true,
|
||||||
|
"pleroma:chat_mention" => true,
|
||||||
"pleroma:emoji_reaction" => true
|
"pleroma:emoji_reaction" => true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -128,6 +129,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do
|
||||||
"favourite" => true,
|
"favourite" => true,
|
||||||
"follow" => true,
|
"follow" => true,
|
||||||
"reblog" => true,
|
"reblog" => true,
|
||||||
|
"pleroma:chat_mention" => true,
|
||||||
"pleroma:emoji_reaction" => true
|
"pleroma:emoji_reaction" => true
|
||||||
},
|
},
|
||||||
"endpoint" => subscription.endpoint,
|
"endpoint" => subscription.endpoint,
|
||||||
|
@ -183,6 +185,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do
|
||||||
"favourite" => true,
|
"favourite" => true,
|
||||||
"follow" => true,
|
"follow" => true,
|
||||||
"reblog" => true,
|
"reblog" => true,
|
||||||
|
"pleroma:chat_mention" => true,
|
||||||
"pleroma:emoji_reaction" => true
|
"pleroma:emoji_reaction" => true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,6 +204,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do
|
||||||
"favourite" => false,
|
"favourite" => false,
|
||||||
"follow" => false,
|
"follow" => false,
|
||||||
"reblog" => false,
|
"reblog" => false,
|
||||||
|
"pleroma:chat_mention" => false,
|
||||||
"pleroma:emoji_reaction" => false
|
"pleroma:emoji_reaction" => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,6 +217,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do
|
||||||
"favourite" => false,
|
"favourite" => false,
|
||||||
"follow" => false,
|
"follow" => false,
|
||||||
"reblog" => false,
|
"reblog" => false,
|
||||||
|
"pleroma:chat_mention" => false,
|
||||||
"pleroma:emoji_reaction" => false
|
"pleroma:emoji_reaction" => false
|
||||||
},
|
},
|
||||||
"endpoint" => "https://example.com/example/1234",
|
"endpoint" => "https://example.com/example/1234",
|
||||||
|
|
|
@ -104,6 +104,13 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do
|
||||||
assert user_data["locked"] == true
|
assert user_data["locked"] == true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "updates the user's chat acceptance status", %{conn: conn} do
|
||||||
|
conn = patch(conn, "/api/v1/accounts/update_credentials", %{accepts_chat_messages: "false"})
|
||||||
|
|
||||||
|
assert user_data = json_response_and_validate_schema(conn, 200)
|
||||||
|
assert user_data["pleroma"]["accepts_chat_messages"] == false
|
||||||
|
end
|
||||||
|
|
||||||
test "updates the user's allow_following_move", %{user: user, conn: conn} do
|
test "updates the user's allow_following_move", %{user: user, conn: conn} do
|
||||||
assert user.allow_following_move == true
|
assert user.allow_following_move == true
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
||||||
hide_followers_count: false,
|
hide_followers_count: false,
|
||||||
hide_follows_count: false,
|
hide_follows_count: false,
|
||||||
relationship: %{},
|
relationship: %{},
|
||||||
skip_thread_containment: false
|
skip_thread_containment: false,
|
||||||
|
accepts_chat_messages: nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,7 +265,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
||||||
hide_followers_count: false,
|
hide_followers_count: false,
|
||||||
hide_follows_count: false,
|
hide_follows_count: false,
|
||||||
relationship: %{},
|
relationship: %{},
|
||||||
skip_thread_containment: false
|
skip_thread_containment: false,
|
||||||
|
accepts_chat_messages: nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Chat
|
||||||
|
alias Pleroma.Chat.MessageReference
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
@ -20,6 +22,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
|
||||||
alias Pleroma.Web.MastodonAPI.NotificationView
|
alias Pleroma.Web.MastodonAPI.NotificationView
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
|
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
defp test_notifications_rendering(notifications, user, expected_result) do
|
defp test_notifications_rendering(notifications, user, expected_result) do
|
||||||
|
@ -37,6 +40,30 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
|
||||||
assert expected_result == result
|
assert expected_result == result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "ChatMessage notification" do
|
||||||
|
user = insert(:user)
|
||||||
|
recipient = insert(:user)
|
||||||
|
{:ok, activity} = CommonAPI.post_chat_message(user, recipient, "what's up my dude")
|
||||||
|
|
||||||
|
{:ok, [notification]} = Notification.create_notifications(activity)
|
||||||
|
|
||||||
|
object = Object.normalize(activity, fetch: false)
|
||||||
|
chat = Chat.get(recipient.id, user.ap_id)
|
||||||
|
|
||||||
|
cm_ref = MessageReference.for_chat_and_object(chat, object)
|
||||||
|
|
||||||
|
expected = %{
|
||||||
|
id: to_string(notification.id),
|
||||||
|
pleroma: %{is_seen: false, is_muted: false},
|
||||||
|
type: "pleroma:chat_mention",
|
||||||
|
account: AccountView.render("show.json", %{user: user, for: recipient}),
|
||||||
|
chat_message: MessageReferenceView.render("show.json", %{chat_message_reference: cm_ref}),
|
||||||
|
created_at: Utils.to_masto_date(notification.inserted_at)
|
||||||
|
}
|
||||||
|
|
||||||
|
test_notifications_rendering([notification], recipient, [expected])
|
||||||
|
end
|
||||||
|
|
||||||
test "Mention notification" do
|
test "Mention notification" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
mentioned_user = insert(:user)
|
mentioned_user = insert(:user)
|
||||||
|
|
|
@ -0,0 +1,453 @@
|
||||||
|
# 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
|
|
@ -0,0 +1,75 @@
|
||||||
|
# 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
|
49
test/pleroma/web/pleroma_api/views/chat_view_test.exs
Normal file
49
test/pleroma/web/pleroma_api/views/chat_view_test.exs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# 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
|
|
@ -7,8 +7,10 @@ defmodule Pleroma.Web.Push.ImplTest do
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.Push.Impl
|
alias Pleroma.Web.Push.Impl
|
||||||
alias Pleroma.Web.Push.Subscription
|
alias Pleroma.Web.Push.Subscription
|
||||||
|
@ -229,6 +231,46 @@ defmodule Pleroma.Web.Push.ImplTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "build_content/3" do
|
describe "build_content/3" do
|
||||||
|
test "builds content for chat messages" do
|
||||||
|
user = insert(:user)
|
||||||
|
recipient = insert(:user)
|
||||||
|
|
||||||
|
{:ok, chat} = CommonAPI.post_chat_message(user, recipient, "hey")
|
||||||
|
object = Object.normalize(chat, fetch: false)
|
||||||
|
[notification] = Notification.for_user(recipient)
|
||||||
|
|
||||||
|
res = Impl.build_content(notification, user, object)
|
||||||
|
|
||||||
|
assert res == %{
|
||||||
|
body: "@#{user.nickname}: hey",
|
||||||
|
title: "New Chat Message"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "builds content for chat messages with no content" 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, chat} = CommonAPI.post_chat_message(user, recipient, nil, media_id: upload.id)
|
||||||
|
object = Object.normalize(chat, fetch: false)
|
||||||
|
[notification] = Notification.for_user(recipient)
|
||||||
|
|
||||||
|
res = Impl.build_content(notification, user, object)
|
||||||
|
|
||||||
|
assert res == %{
|
||||||
|
body: "@#{user.nickname}: (Attachment)",
|
||||||
|
title: "New Chat Message"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
test "hides contents of notifications when option enabled" do
|
test "hides contents of notifications when option enabled" do
|
||||||
user = insert(:user, nickname: "Bob")
|
user = insert(:user, nickname: "Bob")
|
||||||
|
|
||||||
|
|
41
test/pleroma/web/shout_channel_test.exs
Normal file
41
test/pleroma/web/shout_channel_test.exs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# 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
|
|
@ -7,11 +7,15 @@ defmodule Pleroma.Web.StreamerTest do
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
alias Pleroma.Chat
|
||||||
|
alias Pleroma.Chat.MessageReference
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
alias Pleroma.List
|
alias Pleroma.List
|
||||||
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.Streamer
|
alias Pleroma.Web.Streamer
|
||||||
|
alias Pleroma.Web.StreamerView
|
||||||
|
|
||||||
@moduletag needs_streamer: true, capture_log: true
|
@moduletag needs_streamer: true, capture_log: true
|
||||||
|
|
||||||
|
@ -76,16 +80,20 @@ defmodule Pleroma.Web.StreamerTest do
|
||||||
expected_user_topic = "user:#{user.id}"
|
expected_user_topic = "user:#{user.id}"
|
||||||
expected_notification_topic = "user:notification:#{user.id}"
|
expected_notification_topic = "user:notification:#{user.id}"
|
||||||
expected_direct_topic = "direct:#{user.id}"
|
expected_direct_topic = "direct:#{user.id}"
|
||||||
|
expected_pleroma_chat_topic = "user:pleroma_chat:#{user.id}"
|
||||||
|
|
||||||
for valid_user_token <- [read_oauth_token, read_statuses_token] do
|
for valid_user_token <- [read_oauth_token, read_statuses_token] do
|
||||||
assert {:ok, ^expected_user_topic} = Streamer.get_topic("user", user, valid_user_token)
|
assert {:ok, ^expected_user_topic} = Streamer.get_topic("user", user, valid_user_token)
|
||||||
|
|
||||||
assert {:ok, ^expected_direct_topic} =
|
assert {:ok, ^expected_direct_topic} =
|
||||||
Streamer.get_topic("direct", user, valid_user_token)
|
Streamer.get_topic("direct", user, valid_user_token)
|
||||||
|
|
||||||
|
assert {:ok, ^expected_pleroma_chat_topic} =
|
||||||
|
Streamer.get_topic("user:pleroma_chat", user, valid_user_token)
|
||||||
end
|
end
|
||||||
|
|
||||||
for invalid_user_token <- [read_notifications_token, badly_scoped_token],
|
for invalid_user_token <- [read_notifications_token, badly_scoped_token],
|
||||||
user_topic <- ["user", "direct"] do
|
user_topic <- ["user", "direct", "user:pleroma_chat"] do
|
||||||
assert {:error, :unauthorized} = Streamer.get_topic(user_topic, user, invalid_user_token)
|
assert {:error, :unauthorized} = Streamer.get_topic(user_topic, user, invalid_user_token)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -258,6 +266,66 @@ defmodule Pleroma.Web.StreamerTest do
|
||||||
refute Streamer.filtered_by_user?(user, notify)
|
refute Streamer.filtered_by_user?(user, notify)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it sends chat messages to the 'user:pleroma_chat' stream", %{
|
||||||
|
user: user,
|
||||||
|
token: oauth_token
|
||||||
|
} do
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, create_activity} =
|
||||||
|
CommonAPI.post_chat_message(other_user, user, "hey cirno", idempotency_key: "123")
|
||||||
|
|
||||||
|
object = Object.normalize(create_activity, fetch: false)
|
||||||
|
chat = Chat.get(user.id, other_user.ap_id)
|
||||||
|
cm_ref = MessageReference.for_chat_and_object(chat, object)
|
||||||
|
cm_ref = %{cm_ref | chat: chat, object: object}
|
||||||
|
|
||||||
|
Streamer.get_topic_and_add_socket("user:pleroma_chat", user, oauth_token)
|
||||||
|
Streamer.stream("user:pleroma_chat", {user, cm_ref})
|
||||||
|
|
||||||
|
text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref})
|
||||||
|
|
||||||
|
assert text =~ "hey cirno"
|
||||||
|
assert_receive {:text, ^text}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it sends chat messages to the 'user' stream", %{user: user, token: oauth_token} do
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey cirno")
|
||||||
|
object = Object.normalize(create_activity, fetch: false)
|
||||||
|
chat = Chat.get(user.id, other_user.ap_id)
|
||||||
|
cm_ref = MessageReference.for_chat_and_object(chat, object)
|
||||||
|
cm_ref = %{cm_ref | chat: chat, object: object}
|
||||||
|
|
||||||
|
Streamer.get_topic_and_add_socket("user", user, oauth_token)
|
||||||
|
Streamer.stream("user", {user, cm_ref})
|
||||||
|
|
||||||
|
text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref})
|
||||||
|
|
||||||
|
assert text =~ "hey cirno"
|
||||||
|
assert_receive {:text, ^text}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it sends chat message notifications to the 'user:notification' stream", %{
|
||||||
|
user: user,
|
||||||
|
token: oauth_token
|
||||||
|
} do
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey")
|
||||||
|
|
||||||
|
notify =
|
||||||
|
Repo.get_by(Pleroma.Notification, user_id: user.id, activity_id: create_activity.id)
|
||||||
|
|> Repo.preload(:activity)
|
||||||
|
|
||||||
|
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
|
||||||
|
Streamer.stream("user:notification", notify)
|
||||||
|
|
||||||
|
assert_receive {:render_with_user, _, _, ^notify}
|
||||||
|
refute Streamer.filtered_by_user?(user, notify)
|
||||||
|
end
|
||||||
|
|
||||||
test "it doesn't send notify to the 'user:notification' stream when a user is blocked", %{
|
test "it doesn't send notify to the 'user:notification' stream when a user is blocked", %{
|
||||||
user: user,
|
user: user,
|
||||||
token: oauth_token
|
token: oauth_token
|
||||||
|
|
Loading…
Reference in a new issue