API compatibility with fedibird, frontend config (#163)

Reviewed-on: AkkomaGang/akkoma#163
This commit is contained in:
floatingghost 2022-08-17 00:22:59 +00:00
parent 89ffc01c23
commit 11ec9daa5b
19 changed files with 256 additions and 60 deletions

View file

@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Added
- support for fedibird-fe, and non-breaking API parity for it to function
### Fixed ### Fixed
- Compatibility with latest meilisearch - Compatibility with latest meilisearch
- Resolution of nested mix tasks (i.e search.meilisearch) in OTP releases - Resolution of nested mix tasks (i.e search.meilisearch) in OTP releases

View file

@ -734,6 +734,14 @@
"build_dir" => "distribution", "build_dir" => "distribution",
"ref" => "akkoma" "ref" => "akkoma"
}, },
"fedibird-fe" => %{
"name" => "fedibird-fe",
"git" => "https://akkoma.dev/AkkomaGang/fedibird-fe",
"build_url" =>
"https://akkoma-updates.s3-website.fr-par.scw.cloud/frontend/${ref}/fedibird-fe.zip",
"build_dir" => "distribution",
"ref" => "akkoma"
},
"admin-fe" => %{ "admin-fe" => %{
"name" => "admin-fe", "name" => "admin-fe",
"git" => "https://akkoma.dev/AkkomaGang/admin-fe", "git" => "https://akkoma.dev/AkkomaGang/admin-fe",

View file

@ -19,6 +19,10 @@ config :pleroma, :frontends,
admin: %{ admin: %{
"name" => "admin-fe", "name" => "admin-fe",
"ref" => "stable" "ref" => "stable"
},
mastodon: %{
"name" => "mastodon-fe",
"ref" => "akkoma"
} }
``` ```
@ -26,12 +30,18 @@ This would serve the frontend from the the folder at `$instance_static/frontends
Refer to [the frontend CLI task](../../administration/CLI_tasks/frontend) for how to install the frontend's files Refer to [the frontend CLI task](../../administration/CLI_tasks/frontend) for how to install the frontend's files
If you wish masto-fe to also be enabled, you will also need to run the install task for `mastodon-fe`. Not doing this will lead to the frontend not working.
If you choose not to install a frontend for whatever reason, it is recommended that you enable [`:static_fe`](#static_fe) to allow remote users to click "view remote source". Don't bother with this if you've got no unauthenticated access though. If you choose not to install a frontend for whatever reason, it is recommended that you enable [`:static_fe`](#static_fe) to allow remote users to click "view remote source". Don't bother with this if you've got no unauthenticated access though.
You can also replace the default "no frontend" page by placing an `index.html` file under your `instance/static/` directory. You can also replace the default "no frontend" page by placing an `index.html` file under your `instance/static/` directory.
## Mastodon-FE
Akkoma supports both [glitchsoc](https://github.com/glitch-soc/mastodon)'s more "vanilla" mastodon frontend,
as well as [fedibird](https://github.com/fedibird/mastodon)'s extended frontend which has near-feature-parity with akkoma (with quoting and reactions).
To enable either one, you must run the `frontend.install` task for either `mastodon-fe` or `fedibird-fe` (both `--ref akkoma`), then make sure
`:pleroma, :frontends, :mastodon` references the one you want.
## Swagger (openAPI) documentation viewer ## Swagger (openAPI) documentation viewer
If you're a developer and you'd like a human-readable rendering of the If you're a developer and you'd like a human-readable rendering of the

View file

@ -27,9 +27,21 @@ defmodule Pleroma.Web.MastoFEController do
def index(conn, _params) do def index(conn, _params) do
with %{assigns: %{user: %User{} = user, token: %Token{app_id: token_app_id} = token}} <- conn, with %{assigns: %{user: %User{} = user, token: %Token{app_id: token_app_id} = token}} <- conn,
{:ok, %{id: ^token_app_id}} <- AuthController.local_mastofe_app() do {:ok, %{id: ^token_app_id}} <- AuthController.local_mastofe_app() do
flavour =
[:frontends, :mastodon]
|> Pleroma.Config.get()
|> Map.get("name", "mastodon-fe")
index =
if flavour == "fedibird-fe" do
"fedibird.index.html"
else
"glitchsoc.index.html"
end
conn conn
|> put_layout(false) |> put_layout(false)
|> render("index.html", |> render(index,
token: token.token, token: token.token,
user: user, user: user,
custom_emojis: Pleroma.Emoji.get_all() custom_emojis: Pleroma.Emoji.get_all()

View file

@ -375,6 +375,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
emojis: build_emojis(object.data["emoji"]), emojis: build_emojis(object.data["emoji"]),
quote_id: if(quote, do: quote.id, else: nil), quote_id: if(quote, do: quote.id, else: nil),
quote: maybe_render_quote(quote, opts), quote: maybe_render_quote(quote, opts),
emoji_reactions: emoji_reactions,
pleroma: %{ pleroma: %{
local: activity.local, local: activity.local,
conversation_id: get_context_id(activity), conversation_id: get_context_id(activity),
@ -589,7 +590,8 @@ defp build_emoji_map(emoji, users, url, current_user) do
name: emoji, name: emoji,
count: length(users), count: length(users),
url: MediaProxy.url(url), url: MediaProxy.url(url),
me: !!(current_user && current_user.ap_id in users) me: !!(current_user && current_user.ap_id in users),
account_ids: Enum.map(users, fn user -> User.get_cached_by_ap_id(user).id end)
} }
end end

View file

@ -32,8 +32,15 @@ def init(%{qs: qs} = req, state) do
req req
end end
{:cowboy_websocket, req, %{user: user, topic: topic, count: 0, timer: nil}, {:cowboy_websocket, req,
%{idle_timeout: @timeout}} %{
user: user,
topic: topic,
count: 0,
timer: nil,
subscriptions: [],
oauth_token: oauth_token
}, %{idle_timeout: @timeout}}
else else
{:error, :bad_topic} -> {:error, :bad_topic} ->
Logger.debug("#{__MODULE__} bad topic #{inspect(req)}") Logger.debug("#{__MODULE__} bad topic #{inspect(req)}")
@ -65,21 +72,50 @@ def websocket_handle(:pong, state) do
# We only receive pings for now # We only receive pings for now
def websocket_handle(:ping, state), do: {:ok, state} def websocket_handle(:ping, state), do: {:ok, state}
def websocket_handle({:text, "ping"}, state) do def websocket_handle({:text, ping}, state) when ping in ~w[ping PING] do
if state.timer, do: Process.cancel_timer(state.timer) if state.timer, do: Process.cancel_timer(state.timer)
{:reply, {:text, "pong"}, %{state | timer: timer()}} {:reply, {:text, "pong"}, %{state | timer: timer()}}
end end
def websocket_handle({:text, text}, state) do
with {:ok, json} <- Jason.decode(text) do
websocket_handle({:json, json}, state)
else
_ ->
Logger.error("#{__MODULE__} received text frame: #{text}")
{:ok, state}
end
end
def websocket_handle(
{:json, %{"type" => "subscribe", "stream" => stream_name}},
%{user: user, oauth_token: token} = state
) do
with {:ok, topic} <- Streamer.get_topic(stream_name, user, token, %{}) do
new_subscriptions =
[topic | Map.get(state, :subscriptions, [])]
|> Enum.uniq()
{:ok, _topic} = Streamer.add_socket(topic, user)
{:ok, Map.put(state, :subscriptions, new_subscriptions)}
else
_ ->
Logger.error("#{__MODULE__} received invalid topic: #{stream_name}")
{:ok, state}
end
end
def websocket_handle(frame, state) do def websocket_handle(frame, state) do
Logger.error("#{__MODULE__} received frame: #{inspect(frame)}") Logger.error("#{__MODULE__} received frame: #{inspect(frame)}")
{:ok, state} {:ok, state}
end end
def websocket_info({:render_with_user, view, template, item}, state) do def websocket_info({:render_with_user, view, template, item, topic}, state) do
user = %User{} = User.get_cached_by_ap_id(state.user.ap_id) user = %User{} = User.get_cached_by_ap_id(state.user.ap_id)
unless Streamer.filtered_by_user?(user, item) do unless Streamer.filtered_by_user?(user, item) do
websocket_info({:text, view.render(template, item, user)}, %{state | user: user}) websocket_info({:text, view.render(template, item, user, topic)}, %{state | user: user})
else else
{:ok, state} {:ok, state}
end end

View file

@ -74,6 +74,8 @@ defp filter(reactions, %{emoji: emoji}) when is_binary(emoji) do
defp filter(reactions, _), do: reactions defp filter(reactions, _), do: reactions
def create(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do def create(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do
emoji = Pleroma.Emoji.maybe_quote(emoji)
with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji) do with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji) do
activity = Activity.get_by_id(activity_id) activity = Activity.get_by_id(activity_id)

View file

@ -457,6 +457,11 @@ defmodule Pleroma.Web.Router do
get("/federation_status", InstancesController, :show) get("/federation_status", InstancesController, :show)
end end
scope "/api/v1", Pleroma.Web.PleromaAPI do
pipe_through(:authenticated_api)
put("/statuses/:id/emoji_reactions/:emoji", EmojiReactionController, :create)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:authenticated_api) pipe_through(:authenticated_api)

View file

@ -114,6 +114,11 @@ def get_topic("list", _user, _oauth_token, _params) do
{:error, :unauthorized} {:error, :unauthorized}
end end
# mastodon multi-topic WS
def get_topic(nil, _user, _oauth_token, _params) do
{:ok, :multi}
end
def get_topic(_stream, _user, _oauth_token, _params) do def get_topic(_stream, _user, _oauth_token, _params) do
{:error, :bad_topic} {:error, :bad_topic}
end end
@ -186,8 +191,8 @@ defp do_stream("direct", item) do
end end
defp do_stream("follow_relationship", item) do defp do_stream("follow_relationship", item) do
text = StreamerView.render("follow_relationships_update.json", item)
user_topic = "user:#{item.follower.id}" user_topic = "user:#{item.follower.id}"
text = StreamerView.render("follow_relationships_update.json", item, user_topic)
Logger.debug("Trying to push follow relationship update to #{user_topic}\n\n") Logger.debug("Trying to push follow relationship update to #{user_topic}\n\n")
@ -235,7 +240,7 @@ defp do_stream(topic, %Notification{} = item)
when topic in ["user", "user:notification"] do when topic in ["user", "user:notification"] do
Registry.dispatch(@registry, "#{topic}:#{item.user_id}", fn list -> Registry.dispatch(@registry, "#{topic}:#{item.user_id}", fn list ->
Enum.each(list, fn {pid, _auth} -> Enum.each(list, fn {pid, _auth} ->
send(pid, {:render_with_user, StreamerView, "notification.json", item}) send(pid, {:render_with_user, StreamerView, "notification.json", item, topic})
end) end)
end) end)
end end
@ -259,7 +264,7 @@ defp do_stream(topic, item) do
end end
defp push_to_socket(topic, %Participation{} = participation) do defp push_to_socket(topic, %Participation{} = participation) do
rendered = StreamerView.render("conversation.json", participation) rendered = StreamerView.render("conversation.json", participation, topic)
Registry.dispatch(@registry, topic, fn list -> Registry.dispatch(@registry, topic, fn list ->
Enum.each(list, fn {pid, _} -> Enum.each(list, fn {pid, _} ->
@ -283,12 +288,12 @@ defp push_to_socket(topic, %Activity{
defp push_to_socket(_topic, %Activity{data: %{"type" => "Delete"}}), do: :noop defp push_to_socket(_topic, %Activity{data: %{"type" => "Delete"}}), do: :noop
defp push_to_socket(topic, item) do defp push_to_socket(topic, item) do
anon_render = StreamerView.render("update.json", item) anon_render = StreamerView.render("update.json", item, topic)
Registry.dispatch(@registry, topic, fn list -> Registry.dispatch(@registry, topic, fn list ->
Enum.each(list, fn {pid, auth?} -> Enum.each(list, fn {pid, auth?} ->
if auth? do if auth? do
send(pid, {:render_with_user, StreamerView, "update.json", item}) send(pid, {:render_with_user, StreamerView, "update.json", item, topic})
else else
send(pid, {:text, anon_render}) send(pid, {:text, anon_render})
end end

View file

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<meta content='width=device-width, initial-scale=1' name='viewport'>
<title>
<%= Config.get([:instance, :name]) %>
</title>
<link rel="icon" type="image/png" href="/favicon.png"/>
<link rel="manifest" type="applicaton/manifest+json" href="<%= Routes.masto_fe_path(Pleroma.Web.Endpoint, :manifest) %>" />
<meta name="theme-color" content="<%= Config.get([:manifest, :theme_color]) %>" />
<script id='initial-state' type='application/json'><%= initial_state(@token, @user, @custom_emojis) %></script>
<script crossorigin='anonymous' src="/packs/js/common.js"></script>
<script crossorigin='anonymous' src="/packs/js/locale_en.js"></script>
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/getting_started.js'>
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/compose.js'>
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/home_timeline.js'>
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/notifications.js'>
<script crossorigin='anonymous' src="/packs/js/application.js"></script>
<link rel="stylesheet" media="all" href="/packs/css/common.css" />
<link rel="stylesheet" media="all" href="/packs/css/default.css" />
</head>
<body class='app-body no-reduce-motion system-font'>
<div class='app-holder' data-props='{&quot;locale&quot;:&quot;en&quot;}' id='mastodon'>
</div>
</body>
</html>

View file

@ -14,6 +14,7 @@ def initial_state(token, user, custom_emojis) do
%{ %{
meta: %{ meta: %{
title: Config.get([:instance, :name]),
streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(), streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
access_token: token, access_token: token,
locale: "en", locale: "en",
@ -27,7 +28,11 @@ def initial_state(token, user, custom_emojis) do
display_sensitive_media: false, display_sensitive_media: false,
reduce_motion: false, reduce_motion: false,
max_toot_chars: limit, max_toot_chars: limit,
mascot: User.get_mascot(user)["url"] mascot: User.get_mascot(user)["url"],
show_quote_button: true,
enable_reaction: true,
compact_reaction: false,
advanced_layout: true
}, },
poll_limits: Config.get([:instance, :poll_limits]), poll_limits: Config.get([:instance, :poll_limits]),
rights: %{ rights: %{
@ -56,6 +61,7 @@ def initial_state(token, user, custom_emojis) do
"video\/mp4" "video\/mp4"
] ]
}, },
lists: [],
settings: user.mastofe_settings || %{}, settings: user.mastofe_settings || %{},
push_subscription: nil, push_subscription: nil,
accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)}, accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},

View file

@ -11,8 +11,9 @@ defmodule Pleroma.Web.StreamerView do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.MastodonAPI.NotificationView alias Pleroma.Web.MastodonAPI.NotificationView
def render("update.json", %Activity{} = activity, %User{} = user) do def render("update.json", %Activity{} = activity, %User{} = user, topic) do
%{ %{
stream: [topic],
event: "update", event: "update",
payload: payload:
Pleroma.Web.MastodonAPI.StatusView.render( Pleroma.Web.MastodonAPI.StatusView.render(
@ -25,8 +26,9 @@ def render("update.json", %Activity{} = activity, %User{} = user) do
|> Jason.encode!() |> Jason.encode!()
end end
def render("notification.json", %Notification{} = notify, %User{} = user) do def render("notification.json", %Notification{} = notify, %User{} = user, topic) do
%{ %{
stream: [topic],
event: "notification", event: "notification",
payload: payload:
NotificationView.render( NotificationView.render(
@ -38,8 +40,9 @@ def render("notification.json", %Notification{} = notify, %User{} = user) do
|> Jason.encode!() |> Jason.encode!()
end end
def render("update.json", %Activity{} = activity) do def render("update.json", %Activity{} = activity, topic) do
%{ %{
stream: [topic],
event: "update", event: "update",
payload: payload:
Pleroma.Web.MastodonAPI.StatusView.render( Pleroma.Web.MastodonAPI.StatusView.render(
@ -51,8 +54,9 @@ def render("update.json", %Activity{} = activity) do
|> Jason.encode!() |> Jason.encode!()
end end
def render("follow_relationships_update.json", item) do def render("follow_relationships_update.json", item, topic) do
%{ %{
stream: [topic],
event: "pleroma:follow_relationships_update", event: "pleroma:follow_relationships_update",
payload: payload:
%{ %{
@ -73,8 +77,9 @@ def render("follow_relationships_update.json", item) do
|> Jason.encode!() |> Jason.encode!()
end end
def render("conversation.json", %Participation{} = participation) do def render("conversation.json", %Participation{} = participation, topic) do
%{ %{
stream: [topic],
event: "conversation", event: "conversation",
payload: payload:
Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{ Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{

View file

@ -31,9 +31,9 @@ def start_socket(qs \\ nil, headers \\ []) do
WebsocketClient.start_link(self(), path, headers) WebsocketClient.start_link(self(), path, headers)
end end
test "refuses invalid requests" do test "allows multi-streams" do
capture_log(fn -> capture_log(fn ->
assert {:error, {404, _}} = start_socket() assert {:ok, _} = start_socket()
assert {:error, {404, _}} = start_socket("?stream=ncjdk") assert {:error, {404, _}} = start_socket("?stream=ncjdk")
Process.sleep(30) Process.sleep(30)
end) end)

View file

@ -224,7 +224,7 @@ test "it creates a notification for user and send to the 'user' and the 'user:no
task = task =
Task.async(fn -> Task.async(fn ->
{:ok, _topic} = Streamer.get_topic_and_add_socket("user", user, oauth_token) {:ok, _topic} = Streamer.get_topic_and_add_socket("user", user, oauth_token)
assert_receive {:render_with_user, _, _, _}, 4_000 assert_receive {:render_with_user, _, _, _, "user"}, 4_000
end) end)
task_user_notification = task_user_notification =
@ -232,7 +232,7 @@ test "it creates a notification for user and send to the 'user' and the 'user:no
{:ok, _topic} = {:ok, _topic} =
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
assert_receive {:render_with_user, _, _, _}, 4_000 assert_receive {:render_with_user, _, _, _, "user:notification"}, 4_000
end) end)
activity = insert(:note_activity) activity = insert(:note_activity)

View file

@ -0,0 +1,38 @@
defmodule Pleroma.Web.MastoFEControllerTest do
use Pleroma.Web.ConnCase, async: true
alias Pleroma.Web.MastodonAPI.AuthController
describe "index/2 (main page)" do
test "GET /web/ (glitch-soc)" do
clear_config([:frontends, :mastodon], %{"name" => "mastodon-fe"})
{:ok, masto_app} = AuthController.local_mastofe_app()
user = Pleroma.Factory.insert(:user)
token = Pleroma.Factory.insert(:oauth_token, app: masto_app, user: user)
%{conn: conn} = oauth_access(["read", "write"], oauth_token: token, user: user)
resp =
conn
|> get("/web/getting-started")
|> html_response(200)
assert resp =~ "glitch"
end
test "GET /web/ (fedibird)" do
clear_config([:frontends, :mastodon], %{"name" => "fedibird-fe"})
{:ok, masto_app} = AuthController.local_mastofe_app()
user = Pleroma.Factory.insert(:user)
token = Pleroma.Factory.insert(:oauth_token, app: masto_app, user: user)
%{conn: conn} = oauth_access(["read", "write"], oauth_token: token, user: user)
resp =
conn
|> get("/web/getting-started")
|> html_response(200)
refute resp =~ "glitch"
end
end
end

View file

@ -44,14 +44,15 @@ test "has an emoji reaction list" do
assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec()) assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
assert status[:pleroma][:emoji_reactions] == [ assert status[:pleroma][:emoji_reactions] == [
%{name: "", count: 2, me: false, url: nil}, %{name: "", count: 2, me: false, url: nil, account_ids: [other_user.id, user.id]},
%{ %{
count: 2, count: 2,
me: false, me: false,
name: "dinosaur", name: "dinosaur",
url: "http://localhost:4001/emoji/dino walking.gif" url: "http://localhost:4001/emoji/dino walking.gif",
account_ids: [other_user.id, user.id]
}, },
%{name: "🍵", count: 1, me: false, url: nil} %{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
] ]
status = StatusView.render("show.json", activity: activity, for: user) status = StatusView.render("show.json", activity: activity, for: user)
@ -59,14 +60,15 @@ test "has an emoji reaction list" do
assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec()) assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
assert status[:pleroma][:emoji_reactions] == [ assert status[:pleroma][:emoji_reactions] == [
%{name: "", count: 2, me: true, url: nil}, %{name: "", count: 2, me: true, url: nil, account_ids: [other_user.id, user.id]},
%{ %{
count: 2, count: 2,
me: true, me: true,
name: "dinosaur", name: "dinosaur",
url: "http://localhost:4001/emoji/dino walking.gif" url: "http://localhost:4001/emoji/dino walking.gif",
account_ids: [other_user.id, user.id]
}, },
%{name: "🍵", count: 1, me: false, url: nil} %{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
] ]
end end
@ -82,7 +84,7 @@ test "works correctly with badly formatted emojis" do
status = StatusView.render("show.json", activity: activity, for: user) status = StatusView.render("show.json", activity: activity, for: user)
assert status[:pleroma][:emoji_reactions] == [ assert status[:pleroma][:emoji_reactions] == [
%{name: "", count: 1, me: true, url: nil} %{name: "", count: 1, me: true, url: nil, account_ids: [user.id]}
] ]
end end
@ -102,7 +104,7 @@ test "doesn't show reactions from muted and blocked users" do
status = StatusView.render("show.json", activity: activity) status = StatusView.render("show.json", activity: activity)
assert status[:pleroma][:emoji_reactions] == [ assert status[:pleroma][:emoji_reactions] == [
%{name: "", count: 1, me: false, url: nil} %{name: "", count: 1, me: false, url: nil, account_ids: [other_user.id]}
] ]
status = StatusView.render("show.json", activity: activity, for: user) status = StatusView.render("show.json", activity: activity, for: user)
@ -114,19 +116,25 @@ test "doesn't show reactions from muted and blocked users" do
status = StatusView.render("show.json", activity: activity) status = StatusView.render("show.json", activity: activity)
assert status[:pleroma][:emoji_reactions] == [ assert status[:pleroma][:emoji_reactions] == [
%{name: "", count: 2, me: false, url: nil} %{
name: "",
count: 2,
me: false,
url: nil,
account_ids: [third_user.id, other_user.id]
}
] ]
status = StatusView.render("show.json", activity: activity, for: user) status = StatusView.render("show.json", activity: activity, for: user)
assert status[:pleroma][:emoji_reactions] == [ assert status[:pleroma][:emoji_reactions] == [
%{name: "", count: 1, me: false, url: nil} %{name: "", count: 1, me: false, url: nil, account_ids: [third_user.id]}
] ]
status = StatusView.render("show.json", activity: activity, for: other_user) status = StatusView.render("show.json", activity: activity, for: other_user)
assert status[:pleroma][:emoji_reactions] == [ assert status[:pleroma][:emoji_reactions] == [
%{name: "", count: 1, me: true, url: nil} %{name: "", count: 1, me: true, url: nil, account_ids: [other_user.id]}
] ]
end end
@ -272,6 +280,7 @@ test "a note activity" do
spoiler_text: HTML.filter_tags(object_data["summary"]), spoiler_text: HTML.filter_tags(object_data["summary"]),
visibility: "public", visibility: "public",
media_attachments: [], media_attachments: [],
emoji_reactions: [],
mentions: [], mentions: [],
tags: [ tags: [
%{ %{

View file

@ -31,7 +31,13 @@ test "PUT /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
assert to_string(activity.id) == id assert to_string(activity.id) == id
assert result["pleroma"]["emoji_reactions"] == [ assert result["pleroma"]["emoji_reactions"] == [
%{"name" => "", "count" => 1, "me" => true, "url" => nil} %{
"name" => "",
"count" => 1,
"me" => true,
"url" => nil,
"account_ids" => [other_user.id]
}
] ]
{:ok, activity} = CommonAPI.post(user, %{status: "#cofe"}) {:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
@ -54,7 +60,8 @@ test "PUT /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
"name" => "dinosaur", "name" => "dinosaur",
"count" => 1, "count" => 1,
"me" => true, "me" => true,
"url" => "http://localhost:4001/emoji/dino walking.gif" "url" => "http://localhost:4001/emoji/dino walking.gif",
"account_ids" => [other_user.id]
} }
] ]

View file

@ -157,7 +157,8 @@ test "it streams the user's post in the 'user' stream", %{user: user, token: oau
Streamer.get_topic_and_add_socket("user", user, oauth_token) Streamer.get_topic_and_add_socket("user", user, oauth_token)
{:ok, activity} = CommonAPI.post(user, %{status: "hey"}) {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
assert_receive {:render_with_user, _, _, ^activity} stream_name = "user:#{user.id}"
assert_receive {:render_with_user, _, _, ^activity, ^stream_name}
refute Streamer.filtered_by_user?(user, activity) refute Streamer.filtered_by_user?(user, activity)
end end
@ -168,7 +169,11 @@ test "it streams boosts of the user in the 'user' stream", %{user: user, token:
{:ok, activity} = CommonAPI.post(other_user, %{status: "hey"}) {:ok, activity} = CommonAPI.post(other_user, %{status: "hey"})
{:ok, announce} = CommonAPI.repeat(activity.id, user) {:ok, announce} = CommonAPI.repeat(activity.id, user)
assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce} stream_name = "user:#{user.id}"
assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce,
^stream_name}
refute Streamer.filtered_by_user?(user, announce) refute Streamer.filtered_by_user?(user, announce)
end end
@ -221,7 +226,11 @@ test "it streams boosts of mastodon user in the 'user' stream", %{
{:ok, %Pleroma.Activity{data: _data, local: false} = announce} = {:ok, %Pleroma.Activity{data: _data, local: false} = announce} =
Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(data) Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(data)
assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce} stream_name = "user:#{user.id}"
assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce,
^stream_name}
refute Streamer.filtered_by_user?(user, announce) refute Streamer.filtered_by_user?(user, announce)
end end
@ -233,7 +242,7 @@ test "it sends notify to in the 'user' stream", %{
Streamer.get_topic_and_add_socket("user", user, oauth_token) Streamer.get_topic_and_add_socket("user", user, oauth_token)
Streamer.stream("user", notify) Streamer.stream("user", notify)
assert_receive {:render_with_user, _, _, ^notify} assert_receive {:render_with_user, _, _, ^notify, "user"}
refute Streamer.filtered_by_user?(user, notify) refute Streamer.filtered_by_user?(user, notify)
end end
@ -245,7 +254,7 @@ test "it sends notify to in the 'user:notification' stream", %{
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
Streamer.stream("user:notification", notify) Streamer.stream("user:notification", notify)
assert_receive {:render_with_user, _, _, ^notify} assert_receive {:render_with_user, _, _, ^notify, "user:notification"}
refute Streamer.filtered_by_user?(user, notify) refute Streamer.filtered_by_user?(user, notify)
end end
@ -291,7 +300,7 @@ test "it sends favorite to 'user:notification' stream'", %{
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
{:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id) {:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id)
assert_receive {:render_with_user, _, "notification.json", notif} assert_receive {:render_with_user, _, "notification.json", notif, "user:notification"}
assert notif.activity.id == favorite_activity.id assert notif.activity.id == favorite_activity.id
refute Streamer.filtered_by_user?(user, notif) refute Streamer.filtered_by_user?(user, notif)
end end
@ -320,7 +329,7 @@ test "it sends follow activities to the 'user:notification' stream", %{
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
{:ok, _follower, _followed, follow_activity} = CommonAPI.follow(user2, user) {:ok, _follower, _followed, follow_activity} = CommonAPI.follow(user2, user)
assert_receive {:render_with_user, _, "notification.json", notif} assert_receive {:render_with_user, _, "notification.json", notif, "user:notification"}
assert notif.activity.id == follow_activity.id assert notif.activity.id == follow_activity.id
refute Streamer.filtered_by_user?(user, notif) refute Streamer.filtered_by_user?(user, notif)
end end
@ -384,7 +393,7 @@ test "it sends to public (authenticated)" do
Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.get_topic_and_add_socket("public", user, oauth_token)
{:ok, activity} = CommonAPI.post(other_user, %{status: "Test"}) {:ok, activity} = CommonAPI.post(other_user, %{status: "Test"})
assert_receive {:render_with_user, _, _, ^activity} assert_receive {:render_with_user, _, _, ^activity, "public"}
refute Streamer.filtered_by_user?(other_user, activity) refute Streamer.filtered_by_user?(other_user, activity)
end end
@ -436,7 +445,7 @@ test "it filters to user if recipients invalid and thread containment is enabled
Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.get_topic_and_add_socket("public", user, oauth_token)
Streamer.stream("public", activity) Streamer.stream("public", activity)
assert_receive {:render_with_user, _, _, ^activity} assert_receive {:render_with_user, _, _, ^activity, "public"}
assert Streamer.filtered_by_user?(user, activity) assert Streamer.filtered_by_user?(user, activity)
end end
@ -458,7 +467,7 @@ test "it sends message if recipients invalid and thread containment is disabled"
Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.get_topic_and_add_socket("public", user, oauth_token)
Streamer.stream("public", activity) Streamer.stream("public", activity)
assert_receive {:render_with_user, _, _, ^activity} assert_receive {:render_with_user, _, _, ^activity, "public"}
refute Streamer.filtered_by_user?(user, activity) refute Streamer.filtered_by_user?(user, activity)
end end
@ -481,7 +490,7 @@ test "it sends message if recipients invalid and thread containment is enabled b
Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.get_topic_and_add_socket("public", user, oauth_token)
Streamer.stream("public", activity) Streamer.stream("public", activity)
assert_receive {:render_with_user, _, _, ^activity} assert_receive {:render_with_user, _, _, ^activity, "public"}
refute Streamer.filtered_by_user?(user, activity) refute Streamer.filtered_by_user?(user, activity)
end end
end end
@ -495,7 +504,7 @@ test "it filters messages involving blocked users", %{user: user, token: oauth_t
Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.get_topic_and_add_socket("public", user, oauth_token)
{:ok, activity} = CommonAPI.post(blocked_user, %{status: "Test"}) {:ok, activity} = CommonAPI.post(blocked_user, %{status: "Test"})
assert_receive {:render_with_user, _, _, ^activity} assert_receive {:render_with_user, _, _, ^activity, "public"}
assert Streamer.filtered_by_user?(user, activity) assert Streamer.filtered_by_user?(user, activity)
end end
@ -512,17 +521,17 @@ test "it filters messages transitively involving blocked users", %{
{:ok, activity_one} = CommonAPI.post(friend, %{status: "hey! @#{blockee.nickname}"}) {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey! @#{blockee.nickname}"})
assert_receive {:render_with_user, _, _, ^activity_one} assert_receive {:render_with_user, _, _, ^activity_one, "public"}
assert Streamer.filtered_by_user?(blocker, activity_one) assert Streamer.filtered_by_user?(blocker, activity_one)
{:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"}) {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
assert_receive {:render_with_user, _, _, ^activity_two} assert_receive {:render_with_user, _, _, ^activity_two, "public"}
assert Streamer.filtered_by_user?(blocker, activity_two) assert Streamer.filtered_by_user?(blocker, activity_two)
{:ok, activity_three} = CommonAPI.post(blockee, %{status: "hey! @#{blocker.nickname}"}) {:ok, activity_three} = CommonAPI.post(blockee, %{status: "hey! @#{blocker.nickname}"})
assert_receive {:render_with_user, _, _, ^activity_three} assert_receive {:render_with_user, _, _, ^activity_three, "public"}
assert Streamer.filtered_by_user?(blocker, activity_three) assert Streamer.filtered_by_user?(blocker, activity_three)
end end
end end
@ -583,7 +592,8 @@ test "it sends wanted private posts to list", %{user: user_a, token: user_a_toke
visibility: "private" visibility: "private"
}) })
assert_receive {:render_with_user, _, _, ^activity} stream_name = "list:#{list.id}"
assert_receive {:render_with_user, _, _, ^activity, ^stream_name}
refute Streamer.filtered_by_user?(user_a, activity) refute Streamer.filtered_by_user?(user_a, activity)
end end
end end
@ -601,7 +611,8 @@ test "it filters muted reblogs", %{user: user1, token: user1_token} do
Streamer.get_topic_and_add_socket("user", user1, user1_token) Streamer.get_topic_and_add_socket("user", user1, user1_token)
{:ok, announce_activity} = CommonAPI.repeat(create_activity.id, user2) {:ok, announce_activity} = CommonAPI.repeat(create_activity.id, user2)
assert_receive {:render_with_user, _, _, ^announce_activity} stream_name = "user:#{user1.id}"
assert_receive {:render_with_user, _, _, ^announce_activity, ^stream_name}
assert Streamer.filtered_by_user?(user1, announce_activity) assert Streamer.filtered_by_user?(user1, announce_activity)
end end
@ -617,7 +628,7 @@ test "it filters reblog notification for reblog-muted actors", %{
Streamer.get_topic_and_add_socket("user", user1, user1_token) Streamer.get_topic_and_add_socket("user", user1, user1_token)
{:ok, _announce_activity} = CommonAPI.repeat(create_activity.id, user2) {:ok, _announce_activity} = CommonAPI.repeat(create_activity.id, user2)
assert_receive {:render_with_user, _, "notification.json", notif} assert_receive {:render_with_user, _, "notification.json", notif, "user"}
assert Streamer.filtered_by_user?(user1, notif) assert Streamer.filtered_by_user?(user1, notif)
end end
@ -633,7 +644,7 @@ test "it send non-reblog notification for reblog-muted actors", %{
Streamer.get_topic_and_add_socket("user", user1, user1_token) Streamer.get_topic_and_add_socket("user", user1, user1_token)
{:ok, _favorite_activity} = CommonAPI.favorite(user2, create_activity.id) {:ok, _favorite_activity} = CommonAPI.favorite(user2, create_activity.id)
assert_receive {:render_with_user, _, "notification.json", notif} assert_receive {:render_with_user, _, "notification.json", notif, "user"}
refute Streamer.filtered_by_user?(user1, notif) refute Streamer.filtered_by_user?(user1, notif)
end end
end end
@ -648,7 +659,8 @@ test "it filters posts from muted threads" do
{:ok, activity} = CommonAPI.post(user, %{status: "super hot take"}) {:ok, activity} = CommonAPI.post(user, %{status: "super hot take"})
{:ok, _} = CommonAPI.add_mute(user2, activity) {:ok, _} = CommonAPI.add_mute(user2, activity)
assert_receive {:render_with_user, _, _, ^activity} stream_name = "user:#{user2.id}"
assert_receive {:render_with_user, _, _, ^activity, ^stream_name}
assert Streamer.filtered_by_user?(user2, activity) assert Streamer.filtered_by_user?(user2, activity)
end end
end end
@ -690,7 +702,8 @@ test "it doesn't send conversation update to the 'direct' stream when the last m
}) })
create_activity_id = create_activity.id create_activity_id = create_activity.id
assert_receive {:render_with_user, _, _, ^create_activity} stream_name = "direct:#{user.id}"
assert_receive {:render_with_user, _, _, ^create_activity, ^stream_name}
assert_receive {:text, received_conversation1} assert_receive {:text, received_conversation1}
assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1) assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1)
@ -725,8 +738,9 @@ test "it sends conversation update to the 'direct' stream when a message is dele
visibility: "direct" visibility: "direct"
}) })
assert_receive {:render_with_user, _, _, ^create_activity} stream_name = "direct:#{user.id}"
assert_receive {:render_with_user, _, _, ^create_activity2} assert_receive {:render_with_user, _, _, ^create_activity, ^stream_name}
assert_receive {:render_with_user, _, _, ^create_activity2, ^stream_name}
assert_receive {:text, received_conversation1} assert_receive {:text, received_conversation1}
assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1) assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1)
assert_receive {:text, received_conversation1} assert_receive {:text, received_conversation1}