Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into remake-remodel-dms
This commit is contained in:
commit
578ed3a37f
25 changed files with 1021 additions and 582 deletions
|
@ -25,6 +25,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Mix task to create trusted OAuth App.
|
- Mix task to create trusted OAuth App.
|
||||||
- Notifications: Added `follow_request` notification type.
|
- Notifications: Added `follow_request` notification type.
|
||||||
- Added `:reject_deletes` group to SimplePolicy
|
- Added `:reject_deletes` group to SimplePolicy
|
||||||
|
- MRF (`EmojiStealPolicy`): New MRF Policy which allows to automatically download emojis from remote instances
|
||||||
<details>
|
<details>
|
||||||
<summary>API Changes</summary>
|
<summary>API Changes</summary>
|
||||||
- Mastodon API: Extended `/api/v1/instance`.
|
- Mastodon API: Extended `/api/v1/instance`.
|
||||||
|
|
|
@ -358,7 +358,7 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
|
||||||
* `recipients`: A list of ids of users that should receive posts to this conversation. This will replace the current list of recipients, so submit the full list. The owner of owner of the conversation will always be part of the set of recipients, though.
|
* `recipients`: A list of ids of users that should receive posts to this conversation. This will replace the current list of recipients, so submit the full list. The owner of owner of the conversation will always be part of the set of recipients, though.
|
||||||
* Response: JSON, statuses (200 - healthy, 503 unhealthy)
|
* Response: JSON, statuses (200 - healthy, 503 unhealthy)
|
||||||
|
|
||||||
## `GET /api/v1/pleroma/conversations/read`
|
## `POST /api/v1/pleroma/conversations/read`
|
||||||
### Marks all user's conversations as read.
|
### Marks all user's conversations as read.
|
||||||
* Method `POST`
|
* Method `POST`
|
||||||
* Authentication: required
|
* Authentication: required
|
||||||
|
@ -536,7 +536,7 @@ Emoji reactions work a lot like favourites do. They make it possible to react to
|
||||||
```
|
```
|
||||||
|
|
||||||
## `GET /api/v1/pleroma/statuses/:id/reactions/:emoji`
|
## `GET /api/v1/pleroma/statuses/:id/reactions/:emoji`
|
||||||
### Get an object of emoji to account mappings with accounts that reacted to the post for a specific emoji`
|
### Get an object of emoji to account mappings with accounts that reacted to the post for a specific emoji
|
||||||
* Method: `GET`
|
* Method: `GET`
|
||||||
* Authentication: optional
|
* Authentication: optional
|
||||||
* Params: None
|
* Params: None
|
||||||
|
|
|
@ -95,33 +95,33 @@ mix pleroma.user sign_out <nickname>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Deactivate or activate a user
|
## Deactivate or activate a user
|
||||||
```sh tab="OTP"
|
```sh tab="OTP"
|
||||||
./bin/pleroma_ctl user toggle_activated <nickname>
|
./bin/pleroma_ctl user toggle_activated <nickname>
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh tab="From Source"
|
```sh tab="From Source"
|
||||||
mix pleroma.user toggle_activated <nickname>
|
mix pleroma.user toggle_activated <nickname>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Unsubscribe local users from a user and deactivate the user
|
## Deactivate a user and unsubscribes local users from the user
|
||||||
```sh tab="OTP"
|
```sh tab="OTP"
|
||||||
./bin/pleroma_ctl user unsubscribe NICKNAME
|
./bin/pleroma_ctl user deactivate NICKNAME
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh tab="From Source"
|
```sh tab="From Source"
|
||||||
mix pleroma.user unsubscribe NICKNAME
|
mix pleroma.user deactivate NICKNAME
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Unsubscribe local users from an instance and deactivate all accounts on it
|
## Deactivate all accounts from an instance and unsubscribe local users on it
|
||||||
```sh tab="OTP"
|
```sh tab="OTP"
|
||||||
./bin/pleroma_ctl user unsubscribe_all_from_instance <instance>
|
./bin/pleroma_ctl user deactivate_all_from_instance <instance>
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh tab="From Source"
|
```sh tab="From Source"
|
||||||
mix pleroma.user unsubscribe_all_from_instance <instance>
|
mix pleroma.user deactivate_all_from_instance <instance>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -177,4 +177,3 @@ mix pleroma.user untag <nickname> <tags>
|
||||||
```sh tab="From Source"
|
```sh tab="From Source"
|
||||||
mix pleroma.user toggle_confirmed <nickname>
|
mix pleroma.user toggle_confirmed <nickname>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -149,6 +149,11 @@ config :pleroma, :mrf_user_allowlist,
|
||||||
* `:strip_followers` removes followers from the ActivityPub recipient list, ensuring they won't be delivered to home timelines
|
* `:strip_followers` removes followers from the ActivityPub recipient list, ensuring they won't be delivered to home timelines
|
||||||
* `:reject` rejects the message entirely
|
* `:reject` rejects the message entirely
|
||||||
|
|
||||||
|
#### mrf_steal_emoji
|
||||||
|
* `hosts`: List of hosts to steal emojis from
|
||||||
|
* `rejected_shortcodes`: Regex-list of shortcodes to reject
|
||||||
|
* `size_limit`: File size limit (in bytes), checked before an emoji is saved to the disk
|
||||||
|
|
||||||
### :activitypub
|
### :activitypub
|
||||||
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
|
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
|
||||||
* `outgoing_blocks`: Whether to federate blocks to other instances
|
* `outgoing_blocks`: Whether to federate blocks to other instances
|
||||||
|
|
|
@ -144,28 +144,18 @@ def run(["reset_password", nickname]) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(["unsubscribe", nickname]) do
|
def run(["deactivate", nickname]) do
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
|
||||||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
shell_info("Deactivating #{user.nickname}")
|
shell_info("Deactivating #{user.nickname}")
|
||||||
User.deactivate(user)
|
User.deactivate(user)
|
||||||
|
|
||||||
user
|
|
||||||
|> User.get_friends()
|
|
||||||
|> Enum.each(fn friend ->
|
|
||||||
user = User.get_cached_by_id(user.id)
|
|
||||||
|
|
||||||
shell_info("Unsubscribing #{friend.nickname} from #{user.nickname}")
|
|
||||||
User.unfollow(user, friend)
|
|
||||||
end)
|
|
||||||
|
|
||||||
:timer.sleep(500)
|
:timer.sleep(500)
|
||||||
|
|
||||||
user = User.get_cached_by_id(user.id)
|
user = User.get_cached_by_id(user.id)
|
||||||
|
|
||||||
if Enum.empty?(User.get_friends(user)) do
|
if Enum.empty?(Enum.filter(User.get_friends(user), & &1.local)) do
|
||||||
shell_info("Successfully unsubscribed all followers from #{user.nickname}")
|
shell_info("Successfully unsubscribed all local followers from #{user.nickname}")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -173,7 +163,7 @@ def run(["unsubscribe", nickname]) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(["unsubscribe_all_from_instance", instance]) do
|
def run(["deactivate_all_from_instance", instance]) do
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
|
||||||
Pleroma.User.Query.build(%{nickname: "@#{instance}"})
|
Pleroma.User.Query.build(%{nickname: "@#{instance}"})
|
||||||
|
@ -181,7 +171,7 @@ def run(["unsubscribe_all_from_instance", instance]) do
|
||||||
|> Stream.each(fn users ->
|
|> Stream.each(fn users ->
|
||||||
users
|
users
|
||||||
|> Enum.each(fn user ->
|
|> Enum.each(fn user ->
|
||||||
run(["unsubscribe", user.nickname])
|
run(["deactivate", user.nickname])
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|> Stream.run()
|
|> Stream.run()
|
||||||
|
|
|
@ -749,7 +749,19 @@ def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
|
||||||
{:error, "Not subscribed!"}
|
{:error, "Not subscribed!"}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
|
||||||
def unfollow(%User{} = follower, %User{} = followed) do
|
def unfollow(%User{} = follower, %User{} = followed) do
|
||||||
|
case do_unfollow(follower, followed) do
|
||||||
|
{:ok, follower, followed} ->
|
||||||
|
{:ok, follower, Utils.fetch_latest_follow(follower, followed)}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
|
||||||
|
defp do_unfollow(%User{} = follower, %User{} = followed) do
|
||||||
case get_follow_state(follower, followed) do
|
case get_follow_state(follower, followed) do
|
||||||
state when state in [:follow_pending, :follow_accept] ->
|
state when state in [:follow_pending, :follow_accept] ->
|
||||||
FollowingRelationship.unfollow(follower, followed)
|
FollowingRelationship.unfollow(follower, followed)
|
||||||
|
@ -760,7 +772,7 @@ def unfollow(%User{} = follower, %User{} = followed) do
|
||||||
|> update_following_count()
|
|> update_following_count()
|
||||||
|> set_cache()
|
|> set_cache()
|
||||||
|
|
||||||
{:ok, follower, Utils.fetch_latest_follow(follower, followed)}
|
{:ok, follower, followed}
|
||||||
|
|
||||||
nil ->
|
nil ->
|
||||||
{:error, "Not subscribed!"}
|
{:error, "Not subscribed!"}
|
||||||
|
@ -1402,15 +1414,13 @@ def deactivate(%User{} = user, status) do
|
||||||
user
|
user
|
||||||
|> get_followers()
|
|> get_followers()
|
||||||
|> Enum.filter(& &1.local)
|
|> Enum.filter(& &1.local)
|
||||||
|> Enum.each(fn follower ->
|
|> Enum.each(&set_cache(update_following_count(&1)))
|
||||||
follower |> update_following_count() |> set_cache()
|
|
||||||
end)
|
|
||||||
|
|
||||||
# Only update local user counts, remote will be update during the next pull.
|
# Only update local user counts, remote will be update during the next pull.
|
||||||
user
|
user
|
||||||
|> get_friends()
|
|> get_friends()
|
||||||
|> Enum.filter(& &1.local)
|
|> Enum.filter(& &1.local)
|
||||||
|> Enum.each(&update_follower_count/1)
|
|> Enum.each(&do_unfollow(user, &1))
|
||||||
|
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
end
|
end
|
||||||
|
|
97
lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
Normal file
97
lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
|
||||||
|
@moduledoc "Detect new emojis by their shortcode and steals them"
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
|
defp remote_host?(host), do: host != Config.get([Pleroma.Web.Endpoint, :url, :host])
|
||||||
|
|
||||||
|
defp accept_host?(host), do: host in Config.get([:mrf_steal_emoji, :hosts], [])
|
||||||
|
|
||||||
|
defp steal_emoji({shortcode, url}) do
|
||||||
|
url = Pleroma.Web.MediaProxy.url(url)
|
||||||
|
{:ok, response} = Pleroma.HTTP.get(url)
|
||||||
|
size_limit = Config.get([:mrf_steal_emoji, :size_limit], 50_000)
|
||||||
|
|
||||||
|
if byte_size(response.body) <= size_limit do
|
||||||
|
emoji_dir_path =
|
||||||
|
Config.get(
|
||||||
|
[:mrf_steal_emoji, :path],
|
||||||
|
Path.join(Config.get([:instance, :static_dir]), "emoji/stolen")
|
||||||
|
)
|
||||||
|
|
||||||
|
extension =
|
||||||
|
url
|
||||||
|
|> URI.parse()
|
||||||
|
|> Map.get(:path)
|
||||||
|
|> Path.basename()
|
||||||
|
|> Path.extname()
|
||||||
|
|
||||||
|
file_path = Path.join([emoji_dir_path, shortcode <> (extension || ".png")])
|
||||||
|
|
||||||
|
try do
|
||||||
|
:ok = File.write(file_path, response.body)
|
||||||
|
|
||||||
|
shortcode
|
||||||
|
rescue
|
||||||
|
e ->
|
||||||
|
Logger.warn("MRF.StealEmojiPolicy: Failed to write to #{file_path}: #{inspect(e)}")
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
else
|
||||||
|
Logger.debug(
|
||||||
|
"MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{
|
||||||
|
size_limit
|
||||||
|
} B)"
|
||||||
|
)
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
e ->
|
||||||
|
Logger.warn("MRF.StealEmojiPolicy: Failed to fetch #{url}: #{inspect(e)}")
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = message) do
|
||||||
|
host = URI.parse(actor).host
|
||||||
|
|
||||||
|
if remote_host?(host) and accept_host?(host) do
|
||||||
|
installed_emoji = Pleroma.Emoji.get_all() |> Enum.map(fn {k, _} -> k end)
|
||||||
|
|
||||||
|
new_emojis =
|
||||||
|
foreign_emojis
|
||||||
|
|> Enum.filter(fn {shortcode, _url} -> shortcode not in installed_emoji end)
|
||||||
|
|> Enum.filter(fn {shortcode, _url} ->
|
||||||
|
reject_emoji? =
|
||||||
|
Config.get([:mrf_steal_emoji, :rejected_shortcodes], [])
|
||||||
|
|> Enum.find(false, fn regex -> String.match?(shortcode, regex) end)
|
||||||
|
|
||||||
|
!reject_emoji?
|
||||||
|
end)
|
||||||
|
|> Enum.map(&steal_emoji(&1))
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|
|
||||||
|
if !Enum.empty?(new_emojis) do
|
||||||
|
Logger.info("Stole new emojis: #{inspect(new_emojis)}")
|
||||||
|
Pleroma.Emoji.reload()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, message}
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe do
|
||||||
|
{:ok, %{}}
|
||||||
|
end
|
||||||
|
end
|
102
lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex
Normal file
102
lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do
|
||||||
|
alias OpenApiSpex.Operation
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.Account
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.Status
|
||||||
|
|
||||||
|
def open_api_operation(action) do
|
||||||
|
operation = String.to_existing_atom("#{action}_operation")
|
||||||
|
apply(__MODULE__, operation, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def index_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Emoji Reactions"],
|
||||||
|
summary:
|
||||||
|
"Get an object of emoji to account mappings with accounts that reacted to the post",
|
||||||
|
parameters: [
|
||||||
|
Operation.parameter(:id, :path, FlakeID, "Status ID", required: true),
|
||||||
|
Operation.parameter(:emoji, :path, :string, "Filter by a single unicode emoji",
|
||||||
|
required: false
|
||||||
|
)
|
||||||
|
],
|
||||||
|
security: [%{"oAuth" => ["read:statuses"]}],
|
||||||
|
operationId: "EmojiReactionController.index",
|
||||||
|
responses: %{
|
||||||
|
200 => array_of_reactions_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Emoji Reactions"],
|
||||||
|
summary: "React to a post with a unicode emoji",
|
||||||
|
parameters: [
|
||||||
|
Operation.parameter(:id, :path, FlakeID, "Status ID", required: true),
|
||||||
|
Operation.parameter(:emoji, :path, :string, "A single character unicode emoji",
|
||||||
|
required: true
|
||||||
|
)
|
||||||
|
],
|
||||||
|
security: [%{"oAuth" => ["write:statuses"]}],
|
||||||
|
operationId: "EmojiReactionController.create",
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Status", "application/json", Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Emoji Reactions"],
|
||||||
|
summary: "Remove a reaction to a post with a unicode emoji",
|
||||||
|
parameters: [
|
||||||
|
Operation.parameter(:id, :path, FlakeID, "Status ID", required: true),
|
||||||
|
Operation.parameter(:emoji, :path, :string, "A single character unicode emoji",
|
||||||
|
required: true
|
||||||
|
)
|
||||||
|
],
|
||||||
|
security: [%{"oAuth" => ["write:statuses"]}],
|
||||||
|
operationId: "EmojiReactionController.delete",
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Status", "application/json", Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp array_of_reactions_response do
|
||||||
|
Operation.response("Array of Emoji Reactions", "application/json", %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: emoji_reaction(),
|
||||||
|
example: [emoji_reaction().example]
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp emoji_reaction do
|
||||||
|
%Schema{
|
||||||
|
title: "EmojiReaction",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
name: %Schema{type: :string, description: "Emoji"},
|
||||||
|
count: %Schema{type: :integer, description: "Count of reactions with this emoji"},
|
||||||
|
me: %Schema{type: :boolean, description: "Did I react with this emoji?"},
|
||||||
|
accounts: %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: Account,
|
||||||
|
description: "Array of accounts reacted with this emoji"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
example: %{
|
||||||
|
"name" => "😱",
|
||||||
|
"count" => 1,
|
||||||
|
"me" => false,
|
||||||
|
"accounts" => [Account.schema().example]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -145,7 +145,7 @@ def destroy_multiple_operation do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp notification do
|
def notification do
|
||||||
%Schema{
|
%Schema{
|
||||||
title: "Notification",
|
title: "Notification",
|
||||||
description: "Response schema for a notification",
|
description: "Response schema for a notification",
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ApiSpec.PleromaConversationOperation do
|
||||||
|
alias OpenApiSpex.Operation
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.Conversation
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
||||||
|
alias Pleroma.Web.ApiSpec.StatusOperation
|
||||||
|
|
||||||
|
import Pleroma.Web.ApiSpec.Helpers
|
||||||
|
|
||||||
|
def open_api_operation(action) do
|
||||||
|
operation = String.to_existing_atom("#{action}_operation")
|
||||||
|
apply(__MODULE__, operation, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def show_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Conversations"],
|
||||||
|
summary: "The conversation with the given ID",
|
||||||
|
parameters: [
|
||||||
|
Operation.parameter(:id, :path, :string, "Conversation ID",
|
||||||
|
example: "123",
|
||||||
|
required: true
|
||||||
|
)
|
||||||
|
],
|
||||||
|
security: [%{"oAuth" => ["read:statuses"]}],
|
||||||
|
operationId: "PleromaAPI.ConversationController.show",
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Conversation", "application/json", Conversation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def statuses_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Conversations"],
|
||||||
|
summary: "Timeline for a given conversation",
|
||||||
|
parameters: [
|
||||||
|
Operation.parameter(:id, :path, :string, "Conversation ID",
|
||||||
|
example: "123",
|
||||||
|
required: true
|
||||||
|
)
|
||||||
|
| pagination_params()
|
||||||
|
],
|
||||||
|
security: [%{"oAuth" => ["read:statuses"]}],
|
||||||
|
operationId: "PleromaAPI.ConversationController.statuses",
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response(
|
||||||
|
"Array of Statuses",
|
||||||
|
"application/json",
|
||||||
|
StatusOperation.array_of_statuses()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Conversations"],
|
||||||
|
summary: "Update a conversation. Used to change the set of recipients.",
|
||||||
|
parameters: [
|
||||||
|
Operation.parameter(:id, :path, :string, "Conversation ID",
|
||||||
|
example: "123",
|
||||||
|
required: true
|
||||||
|
),
|
||||||
|
Operation.parameter(
|
||||||
|
:recipients,
|
||||||
|
:query,
|
||||||
|
%Schema{type: :array, items: FlakeID},
|
||||||
|
"A list of ids of users that should receive posts to this conversation. This will replace the current list of recipients, so submit the full list. The owner of owner of the conversation will always be part of the set of recipients, though.",
|
||||||
|
required: true
|
||||||
|
)
|
||||||
|
],
|
||||||
|
security: [%{"oAuth" => ["write:conversations"]}],
|
||||||
|
operationId: "PleromaAPI.ConversationController.update",
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Conversation", "application/json", Conversation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_as_read_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Conversations"],
|
||||||
|
summary: "Marks all user's conversations as read",
|
||||||
|
security: [%{"oAuth" => ["write:conversations"]}],
|
||||||
|
operationId: "PleromaAPI.ConversationController.mark_as_read",
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response(
|
||||||
|
"Array of Conversations that were marked as read",
|
||||||
|
"application/json",
|
||||||
|
%Schema{
|
||||||
|
type: :array,
|
||||||
|
items: Conversation,
|
||||||
|
example: [Conversation.schema().example]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ApiSpec.PleromaNotificationOperation do
|
||||||
|
alias OpenApiSpex.Operation
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
alias Pleroma.Web.ApiSpec.NotificationOperation
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||||
|
|
||||||
|
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: ["Notifications"],
|
||||||
|
summary: "Mark notifications as read. Query parameters are mutually exclusive.",
|
||||||
|
parameters: [
|
||||||
|
Operation.parameter(:id, :query, :string, "A single notification ID to read"),
|
||||||
|
Operation.parameter(:max_id, :query, :string, "Read all notifications up to this id")
|
||||||
|
],
|
||||||
|
security: [%{"oAuth" => ["write:notifications"]}],
|
||||||
|
operationId: "PleromaAPI.NotificationController.mark_as_read",
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response(
|
||||||
|
"A Notification or array of Motifications",
|
||||||
|
"application/json",
|
||||||
|
%Schema{
|
||||||
|
anyOf: [
|
||||||
|
%Schema{type: :array, items: NotificationOperation.notification()},
|
||||||
|
NotificationOperation.notification()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
400 => Operation.response("Bad Request", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,95 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.ConversationController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
||||||
|
|
||||||
|
alias Pleroma.Conversation.Participation
|
||||||
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
|
||||||
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
plug(:put_view, Pleroma.Web.MastodonAPI.ConversationView)
|
||||||
|
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:show, :statuses])
|
||||||
|
|
||||||
|
plug(
|
||||||
|
OAuthScopesPlug,
|
||||||
|
%{scopes: ["write:conversations"]} when action in [:update, :mark_as_read]
|
||||||
|
)
|
||||||
|
|
||||||
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaConversationOperation
|
||||||
|
|
||||||
|
def show(%{assigns: %{user: %{id: user_id} = user}} = conn, %{id: participation_id}) do
|
||||||
|
with %Participation{user_id: ^user_id} = participation <- Participation.get(participation_id) do
|
||||||
|
render(conn, "participation.json", participation: participation, for: user)
|
||||||
|
else
|
||||||
|
_error ->
|
||||||
|
conn
|
||||||
|
|> put_status(:not_found)
|
||||||
|
|> json(%{"error" => "Unknown conversation id"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def statuses(
|
||||||
|
%{assigns: %{user: %{id: user_id} = user}} = conn,
|
||||||
|
%{id: participation_id} = params
|
||||||
|
) do
|
||||||
|
with %Participation{user_id: ^user_id} = participation <-
|
||||||
|
Participation.get(participation_id, preload: [:conversation]) do
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|
||||||
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("muting_user", user)
|
||||||
|
|> Map.put("user", user)
|
||||||
|
|
||||||
|
activities =
|
||||||
|
participation.conversation.ap_id
|
||||||
|
|> ActivityPub.fetch_activities_for_context_query(params)
|
||||||
|
|> Pleroma.Pagination.fetch_paginated(Map.put(params, "total", false))
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> add_link_headers(activities)
|
||||||
|
|> put_view(StatusView)
|
||||||
|
|> render("index.json", activities: activities, for: user, as: :activity)
|
||||||
|
else
|
||||||
|
_error ->
|
||||||
|
conn
|
||||||
|
|> put_status(:not_found)
|
||||||
|
|> json(%{"error" => "Unknown conversation id"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(
|
||||||
|
%{assigns: %{user: %{id: user_id} = user}} = conn,
|
||||||
|
%{id: participation_id, recipients: recipients}
|
||||||
|
) do
|
||||||
|
with %Participation{user_id: ^user_id} = participation <- Participation.get(participation_id),
|
||||||
|
{:ok, participation} <- Participation.set_recipients(participation, recipients) do
|
||||||
|
render(conn, "participation.json", participation: participation, for: user)
|
||||||
|
else
|
||||||
|
{:error, message} ->
|
||||||
|
conn
|
||||||
|
|> put_status(:bad_request)
|
||||||
|
|> json(%{"error" => message})
|
||||||
|
|
||||||
|
_error ->
|
||||||
|
conn
|
||||||
|
|> put_status(:not_found)
|
||||||
|
|> json(%{"error" => "Unknown conversation id"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_as_read(%{assigns: %{user: user}} = conn, _params) do
|
||||||
|
with {:ok, _, participations} <- Participation.mark_all_as_read(user) do
|
||||||
|
conn
|
||||||
|
|> add_link_headers(participations)
|
||||||
|
|> render("participations.json", participations: participations, for: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,61 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
|
||||||
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action in [:create, :delete])
|
||||||
|
|
||||||
|
plug(
|
||||||
|
OAuthScopesPlug,
|
||||||
|
%{scopes: ["read:statuses"], fallback: :proceed_unauthenticated}
|
||||||
|
when action == :index
|
||||||
|
)
|
||||||
|
|
||||||
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.EmojiReactionOperation
|
||||||
|
|
||||||
|
def index(%{assigns: %{user: user}} = conn, %{id: activity_id} = params) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
|
||||||
|
%Object{data: %{"reactions" => reactions}} when is_list(reactions) <-
|
||||||
|
Object.normalize(activity) do
|
||||||
|
reactions = filter(reactions, params)
|
||||||
|
render(conn, "index.json", emoji_reactions: reactions, user: user)
|
||||||
|
else
|
||||||
|
_e -> json(conn, [])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp filter(reactions, %{emoji: emoji}) when is_binary(emoji) do
|
||||||
|
Enum.filter(reactions, fn [e, _] -> e == emoji end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp filter(reactions, _), do: reactions
|
||||||
|
|
||||||
|
def create(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do
|
||||||
|
with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji) do
|
||||||
|
activity = Activity.get_by_id(activity_id)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_view(StatusView)
|
||||||
|
|> render("show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do
|
||||||
|
with {:ok, _activity} <- CommonAPI.unreact_with_emoji(activity_id, user, emoji) do
|
||||||
|
activity = Activity.get_by_id(activity_id)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_view(StatusView)
|
||||||
|
|> render("show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.NotificationController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.Notification
|
||||||
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
|
|
||||||
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :mark_as_read)
|
||||||
|
plug(:put_view, Pleroma.Web.MastodonAPI.NotificationView)
|
||||||
|
|
||||||
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaNotificationOperation
|
||||||
|
|
||||||
|
def mark_as_read(%{assigns: %{user: user}} = conn, %{id: notification_id}) do
|
||||||
|
with {:ok, notification} <- Notification.read_one(user, notification_id) do
|
||||||
|
render(conn, "show.json", notification: notification, for: user)
|
||||||
|
else
|
||||||
|
{:error, message} ->
|
||||||
|
conn
|
||||||
|
|> put_status(:bad_request)
|
||||||
|
|> json(%{"error" => message})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_as_read(%{assigns: %{user: user}} = conn, %{max_id: max_id}) do
|
||||||
|
notifications =
|
||||||
|
user
|
||||||
|
|> Notification.set_read_up_to(max_id)
|
||||||
|
|> Enum.take(80)
|
||||||
|
|
||||||
|
render(conn, "index.json", notifications: notifications, for: user)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,220 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
|
|
||||||
use Pleroma.Web, :controller
|
|
||||||
|
|
||||||
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
|
||||||
|
|
||||||
alias Pleroma.Activity
|
|
||||||
alias Pleroma.Conversation.Participation
|
|
||||||
alias Pleroma.Notification
|
|
||||||
alias Pleroma.Object
|
|
||||||
alias Pleroma.Plugs.OAuthScopesPlug
|
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
|
||||||
alias Pleroma.Web.CommonAPI
|
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
|
||||||
alias Pleroma.Web.MastodonAPI.ConversationView
|
|
||||||
alias Pleroma.Web.MastodonAPI.NotificationView
|
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
|
||||||
|
|
||||||
plug(
|
|
||||||
OAuthScopesPlug,
|
|
||||||
%{scopes: ["read:statuses"]}
|
|
||||||
when action in [:conversation, :conversation_statuses]
|
|
||||||
)
|
|
||||||
|
|
||||||
plug(
|
|
||||||
OAuthScopesPlug,
|
|
||||||
%{scopes: ["read:statuses"], fallback: :proceed_unauthenticated}
|
|
||||||
when action == :emoji_reactions_by
|
|
||||||
)
|
|
||||||
|
|
||||||
plug(
|
|
||||||
OAuthScopesPlug,
|
|
||||||
%{scopes: ["write:statuses"]}
|
|
||||||
when action in [:react_with_emoji, :unreact_with_emoji]
|
|
||||||
)
|
|
||||||
|
|
||||||
plug(
|
|
||||||
OAuthScopesPlug,
|
|
||||||
%{scopes: ["write:conversations"]}
|
|
||||||
when action in [:update_conversation, :mark_conversations_as_read]
|
|
||||||
)
|
|
||||||
|
|
||||||
plug(
|
|
||||||
OAuthScopesPlug,
|
|
||||||
%{scopes: ["write:notifications"]} when action == :mark_notifications_as_read
|
|
||||||
)
|
|
||||||
|
|
||||||
def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id} = params) do
|
|
||||||
with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
|
|
||||||
%Object{data: %{"reactions" => emoji_reactions}} when is_list(emoji_reactions) <-
|
|
||||||
Object.normalize(activity) do
|
|
||||||
reactions =
|
|
||||||
emoji_reactions
|
|
||||||
|> Enum.map(fn [emoji, user_ap_ids] ->
|
|
||||||
if params["emoji"] && params["emoji"] != emoji do
|
|
||||||
nil
|
|
||||||
else
|
|
||||||
users =
|
|
||||||
Enum.map(user_ap_ids, &User.get_cached_by_ap_id/1)
|
|
||||||
|> Enum.filter(fn
|
|
||||||
%{deactivated: false} -> true
|
|
||||||
_ -> false
|
|
||||||
end)
|
|
||||||
|
|
||||||
%{
|
|
||||||
name: emoji,
|
|
||||||
count: length(users),
|
|
||||||
accounts:
|
|
||||||
AccountView.render("index.json", %{
|
|
||||||
users: users,
|
|
||||||
for: user,
|
|
||||||
as: :user
|
|
||||||
}),
|
|
||||||
me: !!(user && user.ap_id in user_ap_ids)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|> Enum.filter(& &1)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> json(reactions)
|
|
||||||
else
|
|
||||||
_e ->
|
|
||||||
conn
|
|
||||||
|> json([])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def react_with_emoji(%{assigns: %{user: user}} = conn, %{"id" => activity_id, "emoji" => emoji}) do
|
|
||||||
with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji),
|
|
||||||
activity <- Activity.get_by_id(activity_id) do
|
|
||||||
conn
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> render("show.json", %{activity: activity, for: user, as: :activity})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def unreact_with_emoji(%{assigns: %{user: user}} = conn, %{
|
|
||||||
"id" => activity_id,
|
|
||||||
"emoji" => emoji
|
|
||||||
}) do
|
|
||||||
with {:ok, _activity} <-
|
|
||||||
CommonAPI.unreact_with_emoji(activity_id, user, emoji),
|
|
||||||
activity <- Activity.get_by_id(activity_id) do
|
|
||||||
conn
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> render("show.json", %{activity: activity, for: user, as: :activity})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do
|
|
||||||
with %Participation{} = participation <- Participation.get(participation_id),
|
|
||||||
true <- user.id == participation.user_id do
|
|
||||||
conn
|
|
||||||
|> put_view(ConversationView)
|
|
||||||
|> render("participation.json", %{participation: participation, for: user})
|
|
||||||
else
|
|
||||||
_error ->
|
|
||||||
conn
|
|
||||||
|> put_status(404)
|
|
||||||
|> json(%{"error" => "Unknown conversation id"})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def conversation_statuses(
|
|
||||||
%{assigns: %{user: %{id: user_id} = user}} = conn,
|
|
||||||
%{"id" => participation_id} = params
|
|
||||||
) do
|
|
||||||
with %Participation{user_id: ^user_id} = participation <-
|
|
||||||
Participation.get(participation_id, preload: [:conversation]) do
|
|
||||||
params =
|
|
||||||
params
|
|
||||||
|> Map.put("blocking_user", user)
|
|
||||||
|> Map.put("muting_user", user)
|
|
||||||
|> Map.put("user", user)
|
|
||||||
|
|
||||||
activities =
|
|
||||||
participation.conversation.ap_id
|
|
||||||
|> ActivityPub.fetch_activities_for_context_query(params)
|
|
||||||
|> Pleroma.Pagination.fetch_paginated(Map.put(params, "total", false))
|
|
||||||
|> Enum.reverse()
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> add_link_headers(activities)
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> render("index.json",
|
|
||||||
activities: activities,
|
|
||||||
for: user,
|
|
||||||
as: :activity
|
|
||||||
)
|
|
||||||
else
|
|
||||||
_error ->
|
|
||||||
conn
|
|
||||||
|> put_status(404)
|
|
||||||
|> json(%{"error" => "Unknown conversation id"})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_conversation(
|
|
||||||
%{assigns: %{user: user}} = conn,
|
|
||||||
%{"id" => participation_id, "recipients" => recipients}
|
|
||||||
) do
|
|
||||||
with %Participation{} = participation <- Participation.get(participation_id),
|
|
||||||
true <- user.id == participation.user_id,
|
|
||||||
{:ok, participation} <- Participation.set_recipients(participation, recipients) do
|
|
||||||
conn
|
|
||||||
|> put_view(ConversationView)
|
|
||||||
|> render("participation.json", %{participation: participation, for: user})
|
|
||||||
else
|
|
||||||
{:error, message} ->
|
|
||||||
conn
|
|
||||||
|> put_status(:bad_request)
|
|
||||||
|> json(%{"error" => message})
|
|
||||||
|
|
||||||
_error ->
|
|
||||||
conn
|
|
||||||
|> put_status(404)
|
|
||||||
|> json(%{"error" => "Unknown conversation id"})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def mark_conversations_as_read(%{assigns: %{user: user}} = conn, _params) do
|
|
||||||
with {:ok, _, participations} <- Participation.mark_all_as_read(user) do
|
|
||||||
conn
|
|
||||||
|> add_link_headers(participations)
|
|
||||||
|> put_view(ConversationView)
|
|
||||||
|> render("participations.json", participations: participations, for: user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
|
|
||||||
with {:ok, notification} <- Notification.read_one(user, notification_id) do
|
|
||||||
conn
|
|
||||||
|> put_view(NotificationView)
|
|
||||||
|> render("show.json", %{notification: notification, for: user})
|
|
||||||
else
|
|
||||||
{:error, message} ->
|
|
||||||
conn
|
|
||||||
|> put_status(:bad_request)
|
|
||||||
|> json(%{"error" => message})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do
|
|
||||||
with notifications <- Notification.set_read_up_to(user, max_id) do
|
|
||||||
notifications = Enum.take(notifications, 80)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_view(NotificationView)
|
|
||||||
|> render("index.json",
|
|
||||||
notifications: notifications,
|
|
||||||
for: user
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
33
lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex
Normal file
33
lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.EmojiReactionView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
|
|
||||||
|
def render("index.json", %{emoji_reactions: emoji_reactions} = opts) do
|
||||||
|
render_many(emoji_reactions, __MODULE__, "show.json", opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("show.json", %{emoji_reaction: [emoji, user_ap_ids], user: user}) do
|
||||||
|
users = fetch_users(user_ap_ids)
|
||||||
|
|
||||||
|
%{
|
||||||
|
name: emoji,
|
||||||
|
count: length(users),
|
||||||
|
accounts: render(AccountView, "index.json", users: users, for: user, as: :user),
|
||||||
|
me: !!(user && user.ap_id in user_ap_ids)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fetch_users(user_ap_ids) do
|
||||||
|
user_ap_ids
|
||||||
|
|> Enum.map(&Pleroma.User.get_cached_by_ap_id/1)
|
||||||
|
|> Enum.filter(fn
|
||||||
|
%{deactivated: false} -> true
|
||||||
|
_ -> false
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
|
@ -298,8 +298,8 @@ defmodule Pleroma.Web.Router do
|
||||||
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
|
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
|
||||||
pipe_through(:api)
|
pipe_through(:api)
|
||||||
|
|
||||||
get("/statuses/:id/reactions/:emoji", PleromaAPIController, :emoji_reactions_by)
|
get("/statuses/:id/reactions/:emoji", EmojiReactionController, :index)
|
||||||
get("/statuses/:id/reactions", PleromaAPIController, :emoji_reactions_by)
|
get("/statuses/:id/reactions", EmojiReactionController, :index)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
|
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
|
||||||
|
@ -313,23 +313,15 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/chats/:id/messages", ChatController, :post_chat_message)
|
post("/chats/:id/messages", ChatController, :post_chat_message)
|
||||||
delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
|
delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
|
||||||
post("/chats/:id/read", ChatController, :mark_as_read)
|
post("/chats/:id/read", ChatController, :mark_as_read)
|
||||||
end
|
|
||||||
|
|
||||||
scope [] do
|
get("/conversations/:id/statuses", ConversationController, :statuses)
|
||||||
pipe_through(:authenticated_api)
|
get("/conversations/:id", ConversationController, :show)
|
||||||
|
post("/conversations/read", ConversationController, :mark_as_read)
|
||||||
|
patch("/conversations/:id", ConversationController, :update)
|
||||||
|
|
||||||
get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses)
|
put("/statuses/:id/reactions/:emoji", EmojiReactionController, :create)
|
||||||
get("/conversations/:id", PleromaAPIController, :conversation)
|
delete("/statuses/:id/reactions/:emoji", EmojiReactionController, :delete)
|
||||||
post("/conversations/read", PleromaAPIController, :mark_conversations_as_read)
|
post("/notifications/read", NotificationController, :mark_as_read)
|
||||||
end
|
|
||||||
|
|
||||||
scope [] do
|
|
||||||
pipe_through(:authenticated_api)
|
|
||||||
|
|
||||||
patch("/conversations/:id", PleromaAPIController, :update_conversation)
|
|
||||||
put("/statuses/:id/reactions/:emoji", PleromaAPIController, :react_with_emoji)
|
|
||||||
delete("/statuses/:id/reactions/:emoji", PleromaAPIController, :unreact_with_emoji)
|
|
||||||
post("/notifications/read", PleromaAPIController, :mark_notifications_as_read)
|
|
||||||
|
|
||||||
patch("/accounts/update_avatar", AccountController, :update_avatar)
|
patch("/accounts/update_avatar", AccountController, :update_avatar)
|
||||||
patch("/accounts/update_banner", AccountController, :update_banner)
|
patch("/accounts/update_banner", AccountController, :update_banner)
|
||||||
|
|
|
@ -51,7 +51,7 @@ def api_operations do
|
||||||
|> Map.take([:delete, :get, :head, :options, :patch, :post, :put, :trace])
|
|> Map.take([:delete, :get, :head, :options, :patch, :post, :put, :trace])
|
||||||
|> Map.values()
|
|> Map.values()
|
||||||
|> Enum.reject(&is_nil/1)
|
|> Enum.reject(&is_nil/1)
|
||||||
|> Enum.uniq()
|
|
||||||
end)
|
end)
|
||||||
|
|> Enum.uniq()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1291,6 +1291,10 @@ def get("https://skippers-bin.com/notes/7x9tmrp97i", _, _, _) do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get("https://example.org/emoji/firedfox.png", _, _, _) do
|
||||||
|
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")}}
|
||||||
|
end
|
||||||
|
|
||||||
def get("https://skippers-bin.com/users/7v1w1r8ce6", _, _, _) do
|
def get("https://skippers-bin.com/users/7v1w1r8ce6", _, _, _) do
|
||||||
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/sjw.json")}}
|
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/sjw.json")}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -169,31 +169,31 @@ test "no user to toggle" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "running unsubscribe" do
|
describe "running deactivate" do
|
||||||
test "user is unsubscribed" do
|
test "user is unsubscribed" do
|
||||||
followed = insert(:user)
|
followed = insert(:user)
|
||||||
|
remote_followed = insert(:user, local: false)
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
User.follow(user, followed, :follow_accept)
|
|
||||||
|
|
||||||
Mix.Tasks.Pleroma.User.run(["unsubscribe", user.nickname])
|
User.follow(user, followed, :follow_accept)
|
||||||
|
User.follow(user, remote_followed, :follow_accept)
|
||||||
|
|
||||||
|
Mix.Tasks.Pleroma.User.run(["deactivate", user.nickname])
|
||||||
|
|
||||||
assert_received {:mix_shell, :info, [message]}
|
assert_received {:mix_shell, :info, [message]}
|
||||||
assert message =~ "Deactivating"
|
assert message =~ "Deactivating"
|
||||||
|
|
||||||
assert_received {:mix_shell, :info, [message]}
|
|
||||||
assert message =~ "Unsubscribing"
|
|
||||||
|
|
||||||
# Note that the task has delay :timer.sleep(500)
|
# Note that the task has delay :timer.sleep(500)
|
||||||
assert_received {:mix_shell, :info, [message]}
|
assert_received {:mix_shell, :info, [message]}
|
||||||
assert message =~ "Successfully unsubscribed"
|
assert message =~ "Successfully unsubscribed"
|
||||||
|
|
||||||
user = User.get_cached_by_nickname(user.nickname)
|
user = User.get_cached_by_nickname(user.nickname)
|
||||||
assert Enum.empty?(User.get_friends(user))
|
assert Enum.empty?(Enum.filter(User.get_friends(user), & &1.local))
|
||||||
assert user.deactivated
|
assert user.deactivated
|
||||||
end
|
end
|
||||||
|
|
||||||
test "no user to unsubscribe" do
|
test "no user to deactivate" do
|
||||||
Mix.Tasks.Pleroma.User.run(["unsubscribe", "nonexistent"])
|
Mix.Tasks.Pleroma.User.run(["deactivate", "nonexistent"])
|
||||||
|
|
||||||
assert_received {:mix_shell, :error, [message]}
|
assert_received {:mix_shell, :error, [message]}
|
||||||
assert message =~ "No user"
|
assert message =~ "No user"
|
||||||
|
|
64
test/web/activity_pub/mrf/steal_emoji_policy_test.exs
Normal file
64
test/web/activity_pub/mrf/steal_emoji_policy_test.exs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicyTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy
|
||||||
|
|
||||||
|
setup_all do
|
||||||
|
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
setup do
|
||||||
|
clear_config(:mrf_steal_emoji)
|
||||||
|
|
||||||
|
emoji_path = Path.join(Config.get([:instance, :static_dir]), "emoji/stolen")
|
||||||
|
File.rm_rf!(emoji_path)
|
||||||
|
File.mkdir!(emoji_path)
|
||||||
|
|
||||||
|
Pleroma.Emoji.reload()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does nothing by default" do
|
||||||
|
installed_emoji = Pleroma.Emoji.get_all() |> Enum.map(fn {k, _} -> k end)
|
||||||
|
refute "firedfox" in installed_emoji
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"emoji" => [{"firedfox", "https://example.org/emoji/firedfox.png"}],
|
||||||
|
"actor" => "https://example.org/users/admin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, message} == StealEmojiPolicy.filter(message)
|
||||||
|
|
||||||
|
installed_emoji = Pleroma.Emoji.get_all() |> Enum.map(fn {k, _} -> k end)
|
||||||
|
refute "firedfox" in installed_emoji
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Steals emoji on unknown shortcode from allowed remote host" do
|
||||||
|
installed_emoji = Pleroma.Emoji.get_all() |> Enum.map(fn {k, _} -> k end)
|
||||||
|
refute "firedfox" in installed_emoji
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"emoji" => [{"firedfox", "https://example.org/emoji/firedfox.png"}],
|
||||||
|
"actor" => "https://example.org/users/admin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Config.put([:mrf_steal_emoji, :hosts], ["example.org"])
|
||||||
|
Config.put([:mrf_steal_emoji, :size_limit], 284_468)
|
||||||
|
|
||||||
|
assert {:ok, message} == StealEmojiPolicy.filter(message)
|
||||||
|
|
||||||
|
installed_emoji = Pleroma.Emoji.get_all() |> Enum.map(fn {k, _} -> k end)
|
||||||
|
assert "firedfox" in installed_emoji
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,136 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.ConversationControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
|
alias Pleroma.Conversation.Participation
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "/api/v1/pleroma/conversations/:id" do
|
||||||
|
user = insert(:user)
|
||||||
|
%{user: other_user, conn: conn} = oauth_access(["read:statuses"])
|
||||||
|
|
||||||
|
{:ok, _activity} =
|
||||||
|
CommonAPI.post(user, %{status: "Hi @#{other_user.nickname}!", visibility: "direct"})
|
||||||
|
|
||||||
|
[participation] = Participation.for_user(other_user)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/pleroma/conversations/#{participation.id}")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert result["id"] == participation.id |> to_string()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "/api/v1/pleroma/conversations/:id/statuses" do
|
||||||
|
user = insert(:user)
|
||||||
|
%{user: other_user, conn: conn} = oauth_access(["read:statuses"])
|
||||||
|
third_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _activity} =
|
||||||
|
CommonAPI.post(user, %{status: "Hi @#{third_user.nickname}!", visibility: "direct"})
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(user, %{status: "Hi @#{other_user.nickname}!", visibility: "direct"})
|
||||||
|
|
||||||
|
[participation] = Participation.for_user(other_user)
|
||||||
|
|
||||||
|
{:ok, activity_two} =
|
||||||
|
CommonAPI.post(other_user, %{
|
||||||
|
status: "Hi!",
|
||||||
|
in_reply_to_status_id: activity.id,
|
||||||
|
in_reply_to_conversation_id: participation.id
|
||||||
|
})
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert length(result) == 2
|
||||||
|
|
||||||
|
id_one = activity.id
|
||||||
|
id_two = activity_two.id
|
||||||
|
assert [%{"id" => ^id_one}, %{"id" => ^id_two}] = result
|
||||||
|
|
||||||
|
{:ok, %{id: id_three}} =
|
||||||
|
CommonAPI.post(other_user, %{
|
||||||
|
status: "Bye!",
|
||||||
|
in_reply_to_status_id: activity.id,
|
||||||
|
in_reply_to_conversation_id: participation.id
|
||||||
|
})
|
||||||
|
|
||||||
|
assert [%{"id" => ^id_two}, %{"id" => ^id_three}] =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?limit=2")
|
||||||
|
|> json_response_and_validate_schema(:ok)
|
||||||
|
|
||||||
|
assert [%{"id" => ^id_three}] =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?min_id=#{id_two}")
|
||||||
|
|> json_response_and_validate_schema(:ok)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "PATCH /api/v1/pleroma/conversations/:id" do
|
||||||
|
%{user: user, conn: conn} = oauth_access(["write:conversations"])
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _activity} = CommonAPI.post(user, %{status: "Hi", visibility: "direct"})
|
||||||
|
|
||||||
|
[participation] = Participation.for_user(user)
|
||||||
|
|
||||||
|
participation = Repo.preload(participation, :recipients)
|
||||||
|
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
assert [user] == participation.recipients
|
||||||
|
assert other_user not in participation.recipients
|
||||||
|
|
||||||
|
query = "recipients[]=#{user.id}&recipients[]=#{other_user.id}"
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> patch("/api/v1/pleroma/conversations/#{participation.id}?#{query}")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert result["id"] == participation.id |> to_string
|
||||||
|
|
||||||
|
[participation] = Participation.for_user(user)
|
||||||
|
participation = Repo.preload(participation, :recipients)
|
||||||
|
|
||||||
|
assert user in participation.recipients
|
||||||
|
assert other_user in participation.recipients
|
||||||
|
end
|
||||||
|
|
||||||
|
test "POST /api/v1/pleroma/conversations/read" do
|
||||||
|
user = insert(:user)
|
||||||
|
%{user: other_user, conn: conn} = oauth_access(["write:conversations"])
|
||||||
|
|
||||||
|
{:ok, _activity} =
|
||||||
|
CommonAPI.post(user, %{status: "Hi @#{other_user.nickname}", visibility: "direct"})
|
||||||
|
|
||||||
|
{:ok, _activity} =
|
||||||
|
CommonAPI.post(user, %{status: "Hi @#{other_user.nickname}", visibility: "direct"})
|
||||||
|
|
||||||
|
[participation2, participation1] = Participation.for_user(other_user)
|
||||||
|
assert Participation.get(participation2.id).read == false
|
||||||
|
assert Participation.get(participation1.id).read == false
|
||||||
|
assert User.get_cached_by_id(other_user.id).unread_conversation_count == 2
|
||||||
|
|
||||||
|
[%{"unread" => false}, %{"unread" => false}] =
|
||||||
|
conn
|
||||||
|
|> post("/api/v1/pleroma/conversations/read", %{})
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
[participation2, participation1] = Participation.for_user(other_user)
|
||||||
|
assert Participation.get(participation2.id).read == true
|
||||||
|
assert Participation.get(participation1.id).read == true
|
||||||
|
assert User.get_cached_by_id(other_user.id).unread_conversation_count == 0
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,125 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do
|
||||||
|
use Oban.Testing, repo: Pleroma.Repo
|
||||||
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Tests.ObanHelpers
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "PUT /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> assign(:user, other_user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"]))
|
||||||
|
|> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/☕")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
# We return the status, but this our implementation detail.
|
||||||
|
assert %{"id" => id} = result
|
||||||
|
assert to_string(activity.id) == id
|
||||||
|
|
||||||
|
assert result["pleroma"]["emoji_reactions"] == [
|
||||||
|
%{"name" => "☕", "count" => 1, "me" => true}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
|
||||||
|
{:ok, _reaction_activity} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
|
||||||
|
|
||||||
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> assign(:user, other_user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"]))
|
||||||
|
|> delete("/api/v1/pleroma/statuses/#{activity.id}/reactions/☕")
|
||||||
|
|
||||||
|
assert %{"id" => id} = json_response_and_validate_schema(result, 200)
|
||||||
|
assert to_string(activity.id) == id
|
||||||
|
|
||||||
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
|
object = Object.get_by_ap_id(activity.data["object"])
|
||||||
|
|
||||||
|
assert object.data["reaction_count"] == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
test "GET /api/v1/pleroma/statuses/:id/reactions", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
doomed_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/pleroma/statuses/#{activity.id}/reactions")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert result == []
|
||||||
|
|
||||||
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
|
||||||
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, doomed_user, "🎅")
|
||||||
|
|
||||||
|
User.perform(:delete, doomed_user)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/pleroma/statuses/#{activity.id}/reactions")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
[%{"name" => "🎅", "count" => 1, "accounts" => [represented_user], "me" => false}] = result
|
||||||
|
|
||||||
|
assert represented_user["id"] == other_user.id
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> assign(:user, other_user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:statuses"]))
|
||||||
|
|> get("/api/v1/pleroma/statuses/#{activity.id}/reactions")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert [%{"name" => "🎅", "count" => 1, "accounts" => [_represented_user], "me" => true}] =
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
test "GET /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/pleroma/statuses/#{activity.id}/reactions/🎅")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert result == []
|
||||||
|
|
||||||
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
|
||||||
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
|
||||||
|
|
||||||
|
assert [%{"name" => "🎅", "count" => 1, "accounts" => [represented_user], "me" => false}] =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/pleroma/statuses/#{activity.id}/reactions/🎅")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert represented_user["id"] == other_user.id
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,63 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.NotificationControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
|
alias Pleroma.Notification
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "POST /api/v1/pleroma/notifications/read" do
|
||||||
|
setup do: oauth_access(["write:notifications"])
|
||||||
|
|
||||||
|
test "it marks a single notification as read", %{user: user1, conn: conn} do
|
||||||
|
user2 = insert(:user)
|
||||||
|
{:ok, activity1} = CommonAPI.post(user2, %{status: "hi @#{user1.nickname}"})
|
||||||
|
{:ok, activity2} = CommonAPI.post(user2, %{status: "hi @#{user1.nickname}"})
|
||||||
|
{:ok, [notification1]} = Notification.create_notifications(activity1)
|
||||||
|
{:ok, [notification2]} = Notification.create_notifications(activity2)
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> post("/api/v1/pleroma/notifications/read?id=#{notification1.id}")
|
||||||
|
|> json_response_and_validate_schema(:ok)
|
||||||
|
|
||||||
|
assert %{"pleroma" => %{"is_seen" => true}} = response
|
||||||
|
assert Repo.get(Notification, notification1.id).seen
|
||||||
|
refute Repo.get(Notification, notification2.id).seen
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it marks multiple notifications as read", %{user: user1, conn: conn} do
|
||||||
|
user2 = insert(:user)
|
||||||
|
{:ok, _activity1} = CommonAPI.post(user2, %{status: "hi @#{user1.nickname}"})
|
||||||
|
{:ok, _activity2} = CommonAPI.post(user2, %{status: "hi @#{user1.nickname}"})
|
||||||
|
{:ok, _activity3} = CommonAPI.post(user2, %{status: "HIE @#{user1.nickname}"})
|
||||||
|
|
||||||
|
[notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3})
|
||||||
|
|
||||||
|
[response1, response2] =
|
||||||
|
conn
|
||||||
|
|> post("/api/v1/pleroma/notifications/read?max_id=#{notification2.id}")
|
||||||
|
|> json_response_and_validate_schema(:ok)
|
||||||
|
|
||||||
|
assert %{"pleroma" => %{"is_seen" => true}} = response1
|
||||||
|
assert %{"pleroma" => %{"is_seen" => true}} = response2
|
||||||
|
assert Repo.get(Notification, notification1.id).seen
|
||||||
|
assert Repo.get(Notification, notification2.id).seen
|
||||||
|
refute Repo.get(Notification, notification3.id).seen
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns error when notification not found", %{conn: conn} do
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> post("/api/v1/pleroma/notifications/read?id=22222222222222")
|
||||||
|
|> json_response_and_validate_schema(:bad_request)
|
||||||
|
|
||||||
|
assert response == %{"error" => "Cannot get notification"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,302 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
|
|
||||||
use Oban.Testing, repo: Pleroma.Repo
|
|
||||||
use Pleroma.Web.ConnCase
|
|
||||||
|
|
||||||
alias Pleroma.Conversation.Participation
|
|
||||||
alias Pleroma.Notification
|
|
||||||
alias Pleroma.Object
|
|
||||||
alias Pleroma.Repo
|
|
||||||
alias Pleroma.Tests.ObanHelpers
|
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Web.CommonAPI
|
|
||||||
|
|
||||||
import Pleroma.Factory
|
|
||||||
|
|
||||||
test "PUT /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> assign(:user, other_user)
|
|
||||||
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"]))
|
|
||||||
|> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/☕")
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
# We return the status, but this our implementation detail.
|
|
||||||
assert %{"id" => id} = result
|
|
||||||
assert to_string(activity.id) == id
|
|
||||||
|
|
||||||
assert result["pleroma"]["emoji_reactions"] == [
|
|
||||||
%{"name" => "☕", "count" => 1, "me" => true}
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
|
|
||||||
{:ok, _reaction_activity} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
|
|
||||||
|
|
||||||
ObanHelpers.perform_all()
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> assign(:user, other_user)
|
|
||||||
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"]))
|
|
||||||
|> delete("/api/v1/pleroma/statuses/#{activity.id}/reactions/☕")
|
|
||||||
|
|
||||||
assert %{"id" => id} = json_response(result, 200)
|
|
||||||
assert to_string(activity.id) == id
|
|
||||||
|
|
||||||
ObanHelpers.perform_all()
|
|
||||||
|
|
||||||
object = Object.get_by_ap_id(activity.data["object"])
|
|
||||||
|
|
||||||
assert object.data["reaction_count"] == 0
|
|
||||||
end
|
|
||||||
|
|
||||||
test "GET /api/v1/pleroma/statuses/:id/reactions", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
other_user = insert(:user)
|
|
||||||
doomed_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/pleroma/statuses/#{activity.id}/reactions")
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
assert result == []
|
|
||||||
|
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
|
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, doomed_user, "🎅")
|
|
||||||
|
|
||||||
User.perform(:delete, doomed_user)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/pleroma/statuses/#{activity.id}/reactions")
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
[%{"name" => "🎅", "count" => 1, "accounts" => [represented_user], "me" => false}] = result
|
|
||||||
|
|
||||||
assert represented_user["id"] == other_user.id
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> assign(:user, other_user)
|
|
||||||
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:statuses"]))
|
|
||||||
|> get("/api/v1/pleroma/statuses/#{activity.id}/reactions")
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
assert [%{"name" => "🎅", "count" => 1, "accounts" => [_represented_user], "me" => true}] =
|
|
||||||
result
|
|
||||||
end
|
|
||||||
|
|
||||||
test "GET /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/pleroma/statuses/#{activity.id}/reactions/🎅")
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
assert result == []
|
|
||||||
|
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
|
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/pleroma/statuses/#{activity.id}/reactions/🎅")
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
[%{"name" => "🎅", "count" => 1, "accounts" => [represented_user], "me" => false}] = result
|
|
||||||
|
|
||||||
assert represented_user["id"] == other_user.id
|
|
||||||
end
|
|
||||||
|
|
||||||
test "/api/v1/pleroma/conversations/:id" do
|
|
||||||
user = insert(:user)
|
|
||||||
%{user: other_user, conn: conn} = oauth_access(["read:statuses"])
|
|
||||||
|
|
||||||
{:ok, _activity} =
|
|
||||||
CommonAPI.post(user, %{status: "Hi @#{other_user.nickname}!", visibility: "direct"})
|
|
||||||
|
|
||||||
[participation] = Participation.for_user(other_user)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/pleroma/conversations/#{participation.id}")
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
assert result["id"] == participation.id |> to_string()
|
|
||||||
end
|
|
||||||
|
|
||||||
test "/api/v1/pleroma/conversations/:id/statuses" do
|
|
||||||
user = insert(:user)
|
|
||||||
%{user: other_user, conn: conn} = oauth_access(["read:statuses"])
|
|
||||||
third_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, _activity} =
|
|
||||||
CommonAPI.post(user, %{status: "Hi @#{third_user.nickname}!", visibility: "direct"})
|
|
||||||
|
|
||||||
{:ok, activity} =
|
|
||||||
CommonAPI.post(user, %{status: "Hi @#{other_user.nickname}!", visibility: "direct"})
|
|
||||||
|
|
||||||
[participation] = Participation.for_user(other_user)
|
|
||||||
|
|
||||||
{:ok, activity_two} =
|
|
||||||
CommonAPI.post(other_user, %{
|
|
||||||
status: "Hi!",
|
|
||||||
in_reply_to_status_id: activity.id,
|
|
||||||
in_reply_to_conversation_id: participation.id
|
|
||||||
})
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses")
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
assert length(result) == 2
|
|
||||||
|
|
||||||
id_one = activity.id
|
|
||||||
id_two = activity_two.id
|
|
||||||
assert [%{"id" => ^id_one}, %{"id" => ^id_two}] = result
|
|
||||||
|
|
||||||
{:ok, %{id: id_three}} =
|
|
||||||
CommonAPI.post(other_user, %{
|
|
||||||
status: "Bye!",
|
|
||||||
in_reply_to_status_id: activity.id,
|
|
||||||
in_reply_to_conversation_id: participation.id
|
|
||||||
})
|
|
||||||
|
|
||||||
assert [%{"id" => ^id_two}, %{"id" => ^id_three}] =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?limit=2")
|
|
||||||
|> json_response(:ok)
|
|
||||||
|
|
||||||
assert [%{"id" => ^id_three}] =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?min_id=#{id_two}")
|
|
||||||
|> json_response(:ok)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "PATCH /api/v1/pleroma/conversations/:id" do
|
|
||||||
%{user: user, conn: conn} = oauth_access(["write:conversations"])
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, _activity} = CommonAPI.post(user, %{status: "Hi", visibility: "direct"})
|
|
||||||
|
|
||||||
[participation] = Participation.for_user(user)
|
|
||||||
|
|
||||||
participation = Repo.preload(participation, :recipients)
|
|
||||||
|
|
||||||
user = User.get_cached_by_id(user.id)
|
|
||||||
assert [user] == participation.recipients
|
|
||||||
assert other_user not in participation.recipients
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> patch("/api/v1/pleroma/conversations/#{participation.id}", %{
|
|
||||||
"recipients" => [user.id, other_user.id]
|
|
||||||
})
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
assert result["id"] == participation.id |> to_string
|
|
||||||
|
|
||||||
[participation] = Participation.for_user(user)
|
|
||||||
participation = Repo.preload(participation, :recipients)
|
|
||||||
|
|
||||||
assert user in participation.recipients
|
|
||||||
assert other_user in participation.recipients
|
|
||||||
end
|
|
||||||
|
|
||||||
test "POST /api/v1/pleroma/conversations/read" do
|
|
||||||
user = insert(:user)
|
|
||||||
%{user: other_user, conn: conn} = oauth_access(["write:conversations"])
|
|
||||||
|
|
||||||
{:ok, _activity} =
|
|
||||||
CommonAPI.post(user, %{status: "Hi @#{other_user.nickname}", visibility: "direct"})
|
|
||||||
|
|
||||||
{:ok, _activity} =
|
|
||||||
CommonAPI.post(user, %{status: "Hi @#{other_user.nickname}", visibility: "direct"})
|
|
||||||
|
|
||||||
[participation2, participation1] = Participation.for_user(other_user)
|
|
||||||
assert Participation.get(participation2.id).read == false
|
|
||||||
assert Participation.get(participation1.id).read == false
|
|
||||||
assert User.get_cached_by_id(other_user.id).unread_conversation_count == 2
|
|
||||||
|
|
||||||
[%{"unread" => false}, %{"unread" => false}] =
|
|
||||||
conn
|
|
||||||
|> post("/api/v1/pleroma/conversations/read", %{})
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
[participation2, participation1] = Participation.for_user(other_user)
|
|
||||||
assert Participation.get(participation2.id).read == true
|
|
||||||
assert Participation.get(participation1.id).read == true
|
|
||||||
assert User.get_cached_by_id(other_user.id).unread_conversation_count == 0
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "POST /api/v1/pleroma/notifications/read" do
|
|
||||||
setup do: oauth_access(["write:notifications"])
|
|
||||||
|
|
||||||
test "it marks a single notification as read", %{user: user1, conn: conn} do
|
|
||||||
user2 = insert(:user)
|
|
||||||
{:ok, activity1} = CommonAPI.post(user2, %{status: "hi @#{user1.nickname}"})
|
|
||||||
{:ok, activity2} = CommonAPI.post(user2, %{status: "hi @#{user1.nickname}"})
|
|
||||||
{:ok, [notification1]} = Notification.create_notifications(activity1)
|
|
||||||
{:ok, [notification2]} = Notification.create_notifications(activity2)
|
|
||||||
|
|
||||||
response =
|
|
||||||
conn
|
|
||||||
|> post("/api/v1/pleroma/notifications/read", %{"id" => "#{notification1.id}"})
|
|
||||||
|> json_response(:ok)
|
|
||||||
|
|
||||||
assert %{"pleroma" => %{"is_seen" => true}} = response
|
|
||||||
assert Repo.get(Notification, notification1.id).seen
|
|
||||||
refute Repo.get(Notification, notification2.id).seen
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it marks multiple notifications as read", %{user: user1, conn: conn} do
|
|
||||||
user2 = insert(:user)
|
|
||||||
{:ok, _activity1} = CommonAPI.post(user2, %{status: "hi @#{user1.nickname}"})
|
|
||||||
{:ok, _activity2} = CommonAPI.post(user2, %{status: "hi @#{user1.nickname}"})
|
|
||||||
{:ok, _activity3} = CommonAPI.post(user2, %{status: "HIE @#{user1.nickname}"})
|
|
||||||
|
|
||||||
[notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3})
|
|
||||||
|
|
||||||
[response1, response2] =
|
|
||||||
conn
|
|
||||||
|> post("/api/v1/pleroma/notifications/read", %{"max_id" => "#{notification2.id}"})
|
|
||||||
|> json_response(:ok)
|
|
||||||
|
|
||||||
assert %{"pleroma" => %{"is_seen" => true}} = response1
|
|
||||||
assert %{"pleroma" => %{"is_seen" => true}} = response2
|
|
||||||
assert Repo.get(Notification, notification1.id).seen
|
|
||||||
assert Repo.get(Notification, notification2.id).seen
|
|
||||||
refute Repo.get(Notification, notification3.id).seen
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it returns error when notification not found", %{conn: conn} do
|
|
||||||
response =
|
|
||||||
conn
|
|
||||||
|> post("/api/v1/pleroma/notifications/read", %{"id" => "22222222222222"})
|
|
||||||
|> json_response(:bad_request)
|
|
||||||
|
|
||||||
assert response == %{"error" => "Cannot get notification"}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
Loading…
Reference in a new issue