summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config/dev.exs3
-rw-r--r--docs/Clients.md13
-rw-r--r--lib/pleroma/notification.ex3
-rw-r--r--lib/pleroma/object.ex22
-rw-r--r--lib/pleroma/plugs/http_security_plug.ex23
-rw-r--r--lib/pleroma/thread_mute.ex45
-rw-r--r--lib/pleroma/uploaders/mdii.ex2
-rw-r--r--lib/pleroma/user.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/keyword_policy.ex28
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex10
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex75
-rw-r--r--lib/pleroma/web/common_api/common_api.ex24
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api_controller.ex25
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex3
-rw-r--r--lib/pleroma/web/rich_media/parser.ex9
-rw-r--r--lib/pleroma/web/router.ex4
-rw-r--r--lib/pleroma/web/templates/layout/app.html.eex26
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/show.html.eex4
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api.ex12
-rw-r--r--priv/repo/migrations/20190205114625_create_thread_mutes.exs12
-rw-r--r--priv/static/schemas/litepub-0.1.jsonld6
-rw-r--r--test/object_test.exs28
-rw-r--r--test/user_test.exs5
-rw-r--r--test/web/activity_pub/activity_pub_controller_test.exs4
-rw-r--r--test/web/activity_pub/mrf/keyword_policy_test.exs132
-rw-r--r--test/web/activity_pub/views/user_view_test.exs39
-rw-r--r--test/web/common_api/common_api_test.exs26
-rw-r--r--test/web/mastodon_api/mastodon_api_controller_test.exs32
28 files changed, 510 insertions, 109 deletions
diff --git a/config/dev.exs b/config/dev.exs
index 8f89aa03c..f77bb9976 100644
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -16,7 +16,8 @@ config :pleroma, Pleroma.Web.Endpoint,
debug_errors: true,
code_reloader: true,
check_origin: false,
- watchers: []
+ watchers: [],
+ secure_cookie_flag: false
config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Local
diff --git a/docs/Clients.md b/docs/Clients.md
index 057f12392..c3d776488 100644
--- a/docs/Clients.md
+++ b/docs/Clients.md
@@ -7,6 +7,7 @@ Feel free to contact us to be added to this list!
- Homepage: <http://www.pleroma.com/desktop-app/>
- Source Code: ???
- Platforms: Windows, Mac, (Linux?)
+- Features: Streaming Ready
### Social
- Source Code: <https://gitlab.gnome.org/BrainBlasted/Social>
@@ -19,6 +20,7 @@ Feel free to contact us to be added to this list!
- Source Code: <https://github.com/h3poteto/whalebird-desktop>
- Contact: [@h3poteto@pleroma.io](https://pleroma.io/users/h3poteto)
- Platforms: Windows, Mac, Linux
+- Features: Streaming Ready
## Handheld
### Amaroq
@@ -26,60 +28,71 @@ Feel free to contact us to be added to this list!
- Source Code: <https://github.com/ReticentJohn/Amaroq>
- Contact: [@eurasierboy@mastodon.social](https://mastodon.social/users/eurasierboy)
- Platforms: iOS
+- Features: No Streaming
### Nekonium
- Homepage: [F-Droid Repository](https://repo.gdgd.jp.net/), [Google Play](https://play.google.com/store/apps/details?id=com.apps.nekonium), [Amazon](https://www.amazon.co.jp/dp/B076FXPRBC/)
- Source: <https://git.gdgd.jp.net/lin/nekonium/>
- Contact: [@lin@pleroma.gdgd.jp.net](https://pleroma.gdgd.jp.net/users/lin)
- Platforms: Android
+- Features: Streaming Ready
### Mastalab
- Source Code: <https://gitlab.com/tom79/mastalab/>
- Contact: [@tom79@mastodon.social](https://mastodon.social/users/tom79)
- Platforms: Android
+- Features: Streaming Ready
### Roma
- Homepage: <http://www.pleroma.com/>
- Source Code: ???
- Platforms: iOS, Android
+- Features: No Streaming
### Tootdon
- Homepage: <http://tootdon.club/>, <http://blog.mastodon-tootdon.com/>
- Source Code: ???
- Contact: [@tootdon@mstdn.jp](https://mstdn.jp/users/tootdon)
- Platforms: Android, iOS
+- Features: No Streaming
### Tusky
- Homepage: <https://tuskyapp.github.io/>
- Source Code: <https://github.com/tuskyapp/Tusky>
- Contact: [@ConnyDuck@mastodon.social](https://mastodon.social/users/ConnyDuck)
- Platforms: Android
+- Features: No Streaming
### Twidere
- Homepage: <https://twidere.mariotaku.org/>
- Source Code: <https://github.com/TwidereProject/Twidere-Android/>, <https://github.com/TwidereProject/Twidere-iOS/>
- Contact: <me@mariotaku.org>
- Platform: Android, iOS
+- Features: No Streaming
## Alternative Web Interfaces
### Brutaldon
- Homepage: <https://jfm.carcosa.net/projects/software/brutaldon/>
- Source Code: <https://github.com/jfmcbrayer/brutaldon>
- Contact: [@gcupc@glitch.social](https://glitch.social/users/gcupc)
+- Features: No Streaming
### Feather
- Source Code: <https://github.com/kaniini/feather>
- Contact: [@kaniini@pleroma.site](https://pleroma.site/kaniini)
+- Features: No Streaming
### Halcyon
- Source Code: <https://notabug.org/halcyon-suite/halcyon>
- Contact: [@halcyon@social.csswg.org](https://social.csswg.org/users/halcyon)
+- Features: Streaming Ready
### Pinafore
- Homepage: <https://pinafore.social/>
- Source Code: <https://github.com/nolanlawson/pinafore>
- Contact: [@pinafore@mastodon.technology](https://mastodon.technology/users/pinafore)
- Note: Pleroma support is a secondary goal
+- Features: No Streaming
### Sengi
- Source Code: <https://github.com/NicolasConstant/sengi>
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index c7c925c89..c88512567 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -10,6 +10,7 @@ defmodule Pleroma.Notification do
alias Pleroma.Notification
alias Pleroma.Repo
alias Pleroma.Web.CommonAPI.Utils
+ alias Pleroma.Web.CommonAPI
import Ecto.Query
@@ -117,7 +118,7 @@ defmodule Pleroma.Notification do
# TODO move to sql, too.
def create_notification(%Activity{} = activity, %User{} = user) do
unless User.blocks?(user, %{ap_id: activity.data["actor"]}) or
- user.ap_id == activity.data["actor"] or
+ CommonAPI.thread_muted?(user, activity) or user.ap_id == activity.data["actor"] or
(activity.data["type"] == "Follow" and
Enum.any?(Notification.for_user(user), fn notif ->
notif.activity.data["type"] == "Follow" and
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index dabb49536..5f1fc801b 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -20,29 +20,9 @@ defmodule Pleroma.Object do
timestamps()
end
- def insert_or_get(cng) do
- {_, data} = fetch_field(cng, :data)
- id = data["id"] || data[:id]
- key = "object:#{id}"
-
- fetcher = fn _ ->
- with nil <- get_by_ap_id(id),
- {:ok, object} <- Repo.insert(cng) do
- {:commit, object}
- else
- %Object{} = object -> {:commit, object}
- e -> {:ignore, e}
- end
- end
-
- with {state, object} when state in [:commit, :ok] <- Cachex.fetch(:object_cache, key, fetcher) do
- {:ok, object}
- end
- end
-
def create(data) do
Object.change(%Object{}, %{data: data})
- |> insert_or_get()
+ |> Repo.insert()
end
def change(struct, params \\ %{}) do
diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex
index 2a266c407..057553e24 100644
--- a/lib/pleroma/plugs/http_security_plug.ex
+++ b/lib/pleroma/plugs/http_security_plug.ex
@@ -33,7 +33,22 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
end
defp csp_string do
- protocol = Config.get([Pleroma.Web.Endpoint, :protocol])
+ scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
+ websocket_url = String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws")
+
+ connect_src =
+ if Mix.env() == :dev do
+ "connect-src 'self' http://localhost:3035/ " <> websocket_url
+ else
+ "connect-src 'self' " <> websocket_url
+ end
+
+ script_src =
+ if Mix.env() == :dev do
+ "script-src 'self' 'unsafe-eval'"
+ else
+ "script-src 'self'"
+ end
[
"default-src 'none'",
@@ -43,10 +58,10 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
"media-src 'self' https:",
"style-src 'self' 'unsafe-inline'",
"font-src 'self'",
- "script-src 'self'",
- "connect-src 'self' " <> String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
"manifest-src 'self'",
- if protocol == "https" do
+ connect_src,
+ script_src,
+ if scheme == "https" do
"upgrade-insecure-requests"
end
]
diff --git a/lib/pleroma/thread_mute.ex b/lib/pleroma/thread_mute.ex
new file mode 100644
index 000000000..0b577113d
--- /dev/null
+++ b/lib/pleroma/thread_mute.ex
@@ -0,0 +1,45 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ThreadMute do
+ use Ecto.Schema
+ alias Pleroma.{Repo, User, ThreadMute}
+ require Ecto.Query
+
+ schema "thread_mutes" do
+ belongs_to(:user, User, type: Pleroma.FlakeId)
+ field(:context, :string)
+ end
+
+ def changeset(mute, params \\ %{}) do
+ mute
+ |> Ecto.Changeset.cast(params, [:user_id, :context])
+ |> Ecto.Changeset.foreign_key_constraint(:user_id)
+ |> Ecto.Changeset.unique_constraint(:user_id, name: :unique_index)
+ end
+
+ def query(user_id, context) do
+ user_id = Pleroma.FlakeId.from_string(user_id)
+
+ ThreadMute
+ |> Ecto.Query.where(user_id: ^user_id)
+ |> Ecto.Query.where(context: ^context)
+ end
+
+ def add_mute(user_id, context) do
+ %ThreadMute{}
+ |> changeset(%{user_id: user_id, context: context})
+ |> Repo.insert()
+ end
+
+ def remove_mute(user_id, context) do
+ query(user_id, context)
+ |> Repo.delete_all()
+ end
+
+ def check_muted(user_id, context) do
+ query(user_id, context)
+ |> Repo.all()
+ end
+end
diff --git a/lib/pleroma/uploaders/mdii.ex b/lib/pleroma/uploaders/mdii.ex
index 320b07abd..190ed9f3a 100644
--- a/lib/pleroma/uploaders/mdii.ex
+++ b/lib/pleroma/uploaders/mdii.ex
@@ -25,7 +25,7 @@ defmodule Pleroma.Uploaders.MDII do
query = "#{cgi}?#{extension}"
with {:ok, %{status: 200, body: body}} <-
- @httpoison.post(query, file_data, adapter: [pool: :default]) do
+ @httpoison.post(query, file_data, [], adapter: [pool: :default]) do
remote_file_name = String.split(body) |> List.first()
public_url = "#{files}/#{remote_file_name}.#{extension}"
{:ok, {:url, public_url}}
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 0060d966b..3232cb842 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -311,12 +311,12 @@ defmodule Pleroma.User do
end
end
- @doc "A mass follow for local users. Respects blocks but does not create activities."
+ @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
@spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
def follow_all(follower, followeds) do
followed_addresses =
followeds
- |> Enum.reject(fn %{ap_id: ap_id} -> ap_id in follower.info.blocks end)
+ |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
|> Enum.map(fn %{follower_address: fa} -> fa end)
q =
diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
index ce6d2e529..5fdc03414 100644
--- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
@@ -12,9 +12,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
String.match?(string, pattern)
end
- defp check_reject(%{"object" => %{"content" => content}} = message) do
+ defp check_reject(%{"object" => %{"content" => content, "summary" => summary}} = message) do
if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern ->
- string_matches?(content, pattern)
+ string_matches?(content, pattern) or string_matches?(summary, pattern)
end) do
{:reject, nil}
else
@@ -22,10 +22,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
end
end
- defp check_ftl_removal(%{"to" => to, "object" => %{"content" => content}} = message) do
+ defp check_ftl_removal(
+ %{"to" => to, "object" => %{"content" => content, "summary" => summary}} = message
+ ) do
if "https://www.w3.org/ns/activitystreams#Public" in to and
Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern ->
- string_matches?(content, pattern)
+ string_matches?(content, pattern) or string_matches?(summary, pattern)
end) do
to = List.delete(to, "https://www.w3.org/ns/activitystreams#Public")
cc = ["https://www.w3.org/ns/activitystreams#Public" | message["cc"] || []]
@@ -41,14 +43,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
end
end
- defp check_replace(%{"object" => %{"content" => content}} = message) do
- content =
- Enum.reduce(Pleroma.Config.get([:mrf_keyword, :replace]), content, fn {pattern, replacement},
- acc ->
- String.replace(acc, pattern, replacement)
+ defp check_replace(%{"object" => %{"content" => content, "summary" => summary}} = message) do
+ {content, summary} =
+ Enum.reduce(Pleroma.Config.get([:mrf_keyword, :replace]), {content, summary}, fn {pattern,
+ replacement},
+ {content_acc,
+ summary_acc} ->
+ {String.replace(content_acc, pattern, replacement),
+ String.replace(summary_acc, pattern, replacement)}
end)
- {:ok, put_in(message["object"]["content"], content)}
+ {:ok,
+ message
+ |> put_in(["object", "content"], content)
+ |> put_in(["object", "summary"], summary)}
end
@impl true
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index da6cca4dd..964e11c9d 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -142,8 +142,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do
context = context || generate_id("contexts")
changeset = Object.context_mapping(context)
- with {:ok, object} <- Object.insert_or_get(changeset) do
- object
+ case Repo.insert(changeset) do
+ {:ok, object} ->
+ object
+
+ # This should be solved by an upsert, but it seems ecto
+ # has problems accessing the constraint inside the jsonb.
+ {:error, _} ->
+ Object.get_cached_by_ap_id(context)
end
end
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 15e6c1f68..c8e154989 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -12,9 +12,26 @@ defmodule Pleroma.Web.ActivityPub.UserView do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.Router.Helpers
+ alias Pleroma.Web.Endpoint
import Ecto.Query
+ def render("endpoints.json", %{user: %User{nickname: nil, local: true} = _user}) do
+ %{"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)}
+ end
+
+ def render("endpoints.json", %{user: %User{local: true} = _user}) do
+ %{
+ "oauthAuthorizationEndpoint" => Helpers.o_auth_url(Endpoint, :authorize),
+ "oauthRegistrationEndpoint" => Helpers.mastodon_api_url(Endpoint, :create_app),
+ "oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange),
+ "sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)
+ }
+ end
+
+ def render("endpoints.json", _), do: %{}
+
# the instance itself is not a Person, but instead an Application
def render("user.json", %{user: %{nickname: nil} = user}) do
{:ok, user} = WebFinger.ensure_keys_present(user)
@@ -22,6 +39,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key])
+ endpoints = render("endpoints.json", %{user: user})
+
%{
"id" => user.ap_id,
"type" => "Application",
@@ -37,9 +56,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"owner" => user.ap_id,
"publicKeyPem" => public_key
},
- "endpoints" => %{
- "sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox"
- }
+ "endpoints" => endpoints
}
|> Map.merge(Utils.make_json_ld_header())
end
@@ -50,6 +67,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key])
+ endpoints = render("endpoints.json", %{user: user})
+
%{
"id" => user.ap_id,
"type" => "Person",
@@ -67,9 +86,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"owner" => user.ap_id,
"publicKeyPem" => public_key
},
- "endpoints" => %{
- "sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox"
- },
+ "endpoints" => endpoints,
"icon" => %{
"type" => "Image",
"url" => User.avatar_url(user)
@@ -88,7 +105,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do
query = from(user in query, select: [:ap_id])
following = Repo.all(query)
- collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows)
+ total =
+ if !user.info.hide_follows do
+ length(following)
+ else
+ 0
+ end
+
+ collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows, total)
|> Map.merge(Utils.make_json_ld_header())
end
@@ -97,10 +121,17 @@ defmodule Pleroma.Web.ActivityPub.UserView do
query = from(user in query, select: [:ap_id])
following = Repo.all(query)
+ total =
+ if !user.info.hide_follows do
+ length(following)
+ else
+ 0
+ end
+
%{
"id" => "#{user.ap_id}/following",
"type" => "OrderedCollection",
- "totalItems" => length(following),
+ "totalItems" => total,
"first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
}
|> Map.merge(Utils.make_json_ld_header())
@@ -111,7 +142,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do
query = from(user in query, select: [:ap_id])
followers = Repo.all(query)
- collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers)
+ total =
+ if !user.info.hide_followers do
+ length(followers)
+ else
+ 0
+ end
+
+ collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers, total)
|> Map.merge(Utils.make_json_ld_header())
end
@@ -120,19 +158,24 @@ defmodule Pleroma.Web.ActivityPub.UserView do
query = from(user in query, select: [:ap_id])
followers = Repo.all(query)
+ total =
+ if !user.info.hide_followers do
+ length(followers)
+ else
+ 0
+ end
+
%{
"id" => "#{user.ap_id}/followers",
"type" => "OrderedCollection",
- "totalItems" => length(followers),
- "first" => collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers)
+ "totalItems" => total,
+ "first" =>
+ collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers, total)
}
|> Map.merge(Utils.make_json_ld_header())
end
def render("outbox.json", %{user: user, max_id: max_qid}) do
- # XXX: technically note_count is wrong for this, but it's better than nothing
- info = User.user_info(user)
-
params = %{
"limit" => "10"
}
@@ -160,7 +203,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"id" => "#{iri}?max_id=#{max_id}",
"type" => "OrderedCollectionPage",
"partOf" => iri,
- "totalItems" => info.note_count,
"orderedItems" => collection,
"next" => "#{iri}?max_id=#{min_id}"
}
@@ -169,7 +211,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
%{
"id" => iri,
"type" => "OrderedCollection",
- "totalItems" => info.note_count,
"first" => page
}
|> Map.merge(Utils.make_json_ld_header())
@@ -207,7 +248,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"id" => "#{iri}?max_id=#{max_id}",
"type" => "OrderedCollectionPage",
"partOf" => iri,
- "totalItems" => -1,
"orderedItems" => collection,
"next" => "#{iri}?max_id=#{min_id}"
}
@@ -216,7 +256,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
%{
"id" => iri,
"type" => "OrderedCollection",
- "totalItems" => -1,
"first" => page
}
|> Map.merge(Utils.make_json_ld_header())
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index c0d6fb5c4..86f249c54 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Repo
alias Pleroma.Activity
alias Pleroma.Object
+ alias Pleroma.ThreadMute
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Formatter
@@ -219,4 +220,27 @@ defmodule Pleroma.Web.CommonAPI do
{:error, "Could not unpin"}
end
end
+
+ def add_mute(user, activity) do
+ with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do
+ {:ok, activity}
+ else
+ {:error, _} -> {:error, "conversation is already muted"}
+ end
+ end
+
+ def remove_mute(user, activity) do
+ ThreadMute.remove_mute(user.id, activity.data["context"])
+ {:ok, activity}
+ end
+
+ def thread_muted?(%{id: nil} = _user, _activity), do: false
+
+ def thread_muted?(user, activity) do
+ with [] <- ThreadMute.check_muted(user.id, activity.data["context"]) do
+ false
+ else
+ _ -> true
+ end
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 06f870393..dcaeccac6 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -456,6 +456,31 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
+ def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ activity = Activity.get_by_id(id)
+
+ with {:ok, activity} <- CommonAPI.add_mute(user, activity) do
+ conn
+ |> put_view(StatusView)
+ |> try_render("status.json", %{activity: activity, for: user, as: :activity})
+ else
+ {:error, reason} ->
+ conn
+ |> put_resp_content_type("application/json")
+ |> send_resp(:bad_request, Jason.encode!(%{"error" => reason}))
+ end
+ end
+
+ def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ activity = Activity.get_by_id(id)
+
+ with {:ok, activity} <- CommonAPI.remove_mute(user, activity) do
+ conn
+ |> put_view(StatusView)
+ |> try_render("status.json", %{activity: activity, for: user, as: :activity})
+ end
+ end
+
def notifications(%{assigns: %{user: user}} = conn, params) do
notifications = Notification.for_user(user, params)
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index f51a2ebb0..69f5f992c 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.HTML
alias Pleroma.Repo
alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView
@@ -160,7 +161,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
reblogged: present?(repeated),
favourited: present?(favorited),
bookmarked: present?(bookmarked),
- muted: false,
+ muted: CommonAPI.thread_muted?(user, activity),
pinned: pinned?(activity, user),
sensitive: sensitive,
spoiler_text: object["summary"] || "",
diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex
index 38f1cdeec..4341141df 100644
--- a/lib/pleroma/web/rich_media/parser.ex
+++ b/lib/pleroma/web/rich_media/parser.ex
@@ -9,6 +9,13 @@ defmodule Pleroma.Web.RichMedia.Parser do
Pleroma.Web.RichMedia.Parsers.OEmbed
]
+ @hackney_options [
+ pool: :media,
+ timeout: 2_000,
+ recv_timeout: 2_000,
+ max_body: 2_000_000
+ ]
+
def parse(nil), do: {:error, "No URL provided"}
if Mix.env() == :test do
@@ -28,7 +35,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
defp parse_url(url) do
try do
- {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: [pool: :media])
+ {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options)
html |> maybe_parse() |> clean_parsed_data() |> check_parsed_data()
rescue
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 7f606ac40..d66a1c2a1 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -198,6 +198,8 @@ defmodule Pleroma.Web.Router do
post("/statuses/:id/unpin", MastodonAPIController, :unpin_status)
post("/statuses/:id/bookmark", MastodonAPIController, :bookmark_status)
post("/statuses/:id/unbookmark", MastodonAPIController, :unbookmark_status)
+ post("/statuses/:id/mute", MastodonAPIController, :mute_conversation)
+ post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation)
post("/notifications/clear", MastodonAPIController, :clear_notifications)
post("/notifications/dismiss", MastodonAPIController, :dismiss_notification)
@@ -466,8 +468,8 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web.ActivityPub do
pipe_through(:activitypub)
- post("/users/:nickname/inbox", ActivityPubController, :inbox)
post("/inbox", ActivityPubController, :inbox)
+ post("/users/:nickname/inbox", ActivityPubController, :inbox)
end
scope "/.well-known", Pleroma.Web do
diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex
index 8dd3284d6..520e4b3d5 100644
--- a/lib/pleroma/web/templates/layout/app.html.eex
+++ b/lib/pleroma/web/templates/layout/app.html.eex
@@ -67,6 +67,32 @@
font-weight: 500;
font-size: 16px;
}
+
+ .alert-danger {
+ box-sizing: border-box;
+ width: 100%;
+ color: #D8000C;
+ background-color: #FFD2D2;
+ border-radius: 4px;
+ border: none;
+ padding: 10px;
+ margin-top: 20px;
+ font-weight: 500;
+ font-size: 16px;
+ }
+
+ .alert-info {
+ box-sizing: border-box;
+ width: 100%;
+ color: #00529B;
+ background-color: #BDE5F8;
+ border-radius: 4px;
+ border: none;
+ padding: 10px;
+ margin-top: 20px;
+ font-weight: 500;
+ font-size: 16px;
+ }
</style>
</head>
<body>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
index de2241ec9..32c458f0c 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
@@ -1,5 +1,9 @@
+<%= if get_flash(@conn, :info) do %>
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
+<% end %>
+<%= if get_flash(@conn, :error) do %>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
+<% end %>
<h2>OAuth Authorization</h2>
<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
<%= label f, :name, "Name or email" %>
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index 162beb9be..db521a3ad 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -310,8 +310,16 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
else
_e ->
changeset = Object.context_mapping(context)
- {:ok, object} = Object.insert_or_get(changeset)
- object.id
+
+ case Repo.insert(changeset) do
+ {:ok, %{id: id}} ->
+ id
+
+ # This should be solved by an upsert, but it seems ecto
+ # has problems accessing the constraint inside the jsonb.
+ {:error, _} ->
+ Object.get_cached_by_ap_id(context).id
+ end
end
end
diff --git a/priv/repo/migrations/20190205114625_create_thread_mutes.exs b/priv/repo/migrations/20190205114625_create_thread_mutes.exs
new file mode 100644
index 000000000..8e9eccbae
--- /dev/null
+++ b/priv/repo/migrations/20190205114625_create_thread_mutes.exs
@@ -0,0 +1,12 @@
+defmodule Pleroma.Repo.Migrations.CreateThreadMutes do
+ use Ecto.Migration
+
+ def change do
+ create table(:thread_mutes) do
+ add :user_id, references(:users, type: :uuid, on_delete: :delete_all)
+ add :context, :string
+ end
+
+ create unique_index(:thread_mutes, [:user_id, :context], name: :unique_index)
+ end
+end
diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld
index 15645646a..f36b231c5 100644
--- a/priv/static/schemas/litepub-0.1.jsonld
+++ b/priv/static/schemas/litepub-0.1.jsonld
@@ -19,7 +19,11 @@
"value": "schema:value",
"sensitive": "as:sensitive",
"litepub": "http://litepub.social/ns#",
- "directMessage": "litepub:directMessage"
+ "directMessage": "litepub:directMessage",
+ "oauthRegistrationEndpoint": {
+ "@id": "litepub:oauthRegistrationEndpoint",
+ "@type": "@id"
+ }
}
]
}
diff --git a/test/object_test.exs b/test/object_test.exs
index 9f5283d2d..a820a34ee 100644
--- a/test/object_test.exs
+++ b/test/object_test.exs
@@ -58,32 +58,4 @@ defmodule Pleroma.ObjectTest do
assert cached_object.data["type"] == "Tombstone"
end
end
-
- describe "insert_or_get" do
- test "inserting the same object twice (by id) just returns the original object" do
- data = %{data: %{"id" => Ecto.UUID.generate()}}
- cng = Object.change(%Object{}, data)
- {:ok, object} = Object.insert_or_get(cng)
- {:ok, second_object} = Object.insert_or_get(cng)
-
- Cachex.clear(:object_cache)
- {:ok, third_object} = Object.insert_or_get(cng)
-
- assert object == second_object
- assert object == third_object
- end
- end
-
- describe "create" do
- test "inserts an object for a given data set" do
- data = %{"id" => Ecto.UUID.generate()}
-
- {:ok, object} = Object.create(data)
- assert object.data["id"] == data["id"]
-
- # Works when doing it twice.
- {:ok, object} = Object.create(data)
- assert object.data["id"] == data["id"]
- end
- end
end
diff --git a/test/user_test.exs b/test/user_test.exs
index 1fd54b944..58587bd82 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -57,18 +57,21 @@ defmodule Pleroma.UserTest do
followed_two = insert(:user)
blocked = insert(:user)
not_followed = insert(:user)
+ reverse_blocked = insert(:user)
{:ok, user} = User.block(user, blocked)
+ {:ok, reverse_blocked} = User.block(reverse_blocked, user)
{:ok, user} = User.follow(user, followed_zero)
- {:ok, user} = User.follow_all(user, [followed_one, followed_two, blocked])
+ {:ok, user} = User.follow_all(user, [followed_one, followed_two, blocked, reverse_blocked])
assert User.following?(user, followed_one)
assert User.following?(user, followed_two)
assert User.following?(user, followed_zero)
refute User.following?(user, not_followed)
refute User.following?(user, blocked)
+ refute User.following?(user, reverse_blocked)
end
test "follow_all follows mutliple users without duplicating" do
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs
index acd295988..398bedf77 100644
--- a/test/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/web/activity_pub/activity_pub_controller_test.exs
@@ -402,7 +402,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|> json_response(200)
assert result["first"]["orderedItems"] == []
- assert result["totalItems"] == 1
+ assert result["totalItems"] == 0
end
test "it works for more than 10 users", %{conn: conn} do
@@ -457,7 +457,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|> json_response(200)
assert result["first"]["orderedItems"] == []
- assert result["totalItems"] == 1
+ assert result["totalItems"] == 0
end
test "it works for more than 10 users", %{conn: conn} do
diff --git a/test/web/activity_pub/mrf/keyword_policy_test.exs b/test/web/activity_pub/mrf/keyword_policy_test.exs
index 67a5858d7..602892a37 100644
--- a/test/web/activity_pub/mrf/keyword_policy_test.exs
+++ b/test/web/activity_pub/mrf/keyword_policy_test.exs
@@ -12,18 +12,35 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do
end
describe "rejecting based on keywords" do
- test "rejects if string matches" do
+ test "rejects if string matches in content" do
Pleroma.Config.put([:mrf_keyword, :reject], ["pun"])
message = %{
"type" => "Create",
- "object" => %{"content" => "just a daily reminder that compLAINer is a good pun"}
+ "object" => %{
+ "content" => "just a daily reminder that compLAINer is a good pun",
+ "summary" => ""
+ }
}
assert {:reject, nil} == KeywordPolicy.filter(message)
end
- test "rejects if regex matches" do
+ test "rejects if string matches in summary" do
+ Pleroma.Config.put([:mrf_keyword, :reject], ["pun"])
+
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "summary" => "just a daily reminder that compLAINer is a good pun",
+ "content" => ""
+ }
+ }
+
+ assert {:reject, nil} == KeywordPolicy.filter(message)
+ end
+
+ test "rejects if regex matches in content" do
Pleroma.Config.put([:mrf_keyword, :reject], [~r/comp[lL][aA][iI][nN]er/])
assert true ==
@@ -31,7 +48,25 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do
message = %{
"type" => "Create",
"object" => %{
- "content" => "just a daily reminder that #{content} is a good pun"
+ "content" => "just a daily reminder that #{content} is a good pun",
+ "summary" => ""
+ }
+ }
+
+ {:reject, nil} == KeywordPolicy.filter(message)
+ end)
+ end
+
+ test "rejects if regex matches in summary" do
+ Pleroma.Config.put([:mrf_keyword, :reject], [~r/comp[lL][aA][iI][nN]er/])
+
+ assert true ==
+ Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content ->
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "summary" => "just a daily reminder that #{content} is a good pun",
+ "content" => ""
}
}
@@ -41,13 +76,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do
end
describe "delisting from ftl based on keywords" do
- test "delists if string matches" do
+ test "delists if string matches in content" do
Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], ["pun"])
message = %{
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"type" => "Create",
- "object" => %{"content" => "just a daily reminder that compLAINer is a good pun"}
+ "object" => %{
+ "content" => "just a daily reminder that compLAINer is a good pun",
+ "summary" => ""
+ }
}
{:ok, result} = KeywordPolicy.filter(message)
@@ -55,7 +93,45 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do
refute ["https://www.w3.org/ns/activitystreams#Public"] == result["to"]
end
- test "delists if regex matches" do
+ test "delists if string matches in summary" do
+ Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], ["pun"])
+
+ message = %{
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "type" => "Create",
+ "object" => %{
+ "summary" => "just a daily reminder that compLAINer is a good pun",
+ "content" => ""
+ }
+ }
+
+ {:ok, result} = KeywordPolicy.filter(message)
+ assert ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"]
+ refute ["https://www.w3.org/ns/activitystreams#Public"] == result["to"]
+ end
+
+ test "delists if regex matches in content" do
+ Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], [~r/comp[lL][aA][iI][nN]er/])
+
+ assert true ==
+ Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content ->
+ message = %{
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => %{
+ "content" => "just a daily reminder that #{content} is a good pun",
+ "summary" => ""
+ }
+ }
+
+ {:ok, result} = KeywordPolicy.filter(message)
+
+ ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"] and
+ not (["https://www.w3.org/ns/activitystreams#Public"] == result["to"])
+ end)
+ end
+
+ test "delists if regex matches in summary" do
Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], [~r/comp[lL][aA][iI][nN]er/])
assert true ==
@@ -64,7 +140,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do
"type" => "Create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"object" => %{
- "content" => "just a daily reminder that #{content} is a good pun"
+ "summary" => "just a daily reminder that #{content} is a good pun",
+ "content" => ""
}
}
@@ -77,20 +154,33 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do
end
describe "replacing keywords" do
- test "replaces keyword if string matches" do
+ test "replaces keyword if string matches in content" do
Pleroma.Config.put([:mrf_keyword, :replace], [{"opensource", "free software"}])
message = %{
"type" => "Create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
- "object" => %{"content" => "ZFS is opensource"}
+ "object" => %{"content" => "ZFS is opensource", "summary" => ""}
}
{:ok, %{"object" => %{"content" => result}}} = KeywordPolicy.filter(message)
assert result == "ZFS is free software"
end
- test "replaces keyword if regex matches" do
+ test "replaces keyword if string matches in summary" do
+ Pleroma.Config.put([:mrf_keyword, :replace], [{"opensource", "free software"}])
+
+ message = %{
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => %{"summary" => "ZFS is opensource", "content" => ""}
+ }
+
+ {:ok, %{"object" => %{"summary" => result}}} = KeywordPolicy.filter(message)
+ assert result == "ZFS is free software"
+ end
+
+ test "replaces keyword if regex matches in content" do
Pleroma.Config.put([:mrf_keyword, :replace], [
{~r/open(-|\s)?source\s?(software)?/, "free software"}
])
@@ -100,12 +190,30 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do
message = %{
"type" => "Create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
- "object" => %{"content" => "ZFS is #{content}"}
+ "object" => %{"content" => "ZFS is #{content}", "summary" => ""}
}
{:ok, %{"object" => %{"content" => result}}} = KeywordPolicy.filter(message)
result == "ZFS is free software"
end)
end
+
+ test "replaces keyword if regex matches in summary" do
+ Pleroma.Config.put([:mrf_keyword, :replace], [
+ {~r/open(-|\s)?source\s?(software)?/, "free software"}
+ ])
+
+ assert true ==
+ Enum.all?(["opensource", "open-source", "open source"], fn content ->
+ message = %{
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => %{"summary" => "ZFS is #{content}", "content" => ""}
+ }
+
+ {:ok, %{"object" => %{"summary" => result}}} = KeywordPolicy.filter(message)
+ result == "ZFS is free software"
+ end)
+ end
end
end
diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs
index 7fc870e96..0bc1d4728 100644
--- a/test/web/activity_pub/views/user_view_test.exs
+++ b/test/web/activity_pub/views/user_view_test.exs
@@ -15,4 +15,43 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
assert String.contains?(result["publicKey"]["publicKeyPem"], "BEGIN PUBLIC KEY")
end
+
+ describe "endpoints" do
+ test "local users have a usable endpoints structure" do
+ user = insert(:user)
+ {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+
+ result = UserView.render("user.json", %{user: user})
+
+ assert result["id"] == user.ap_id
+
+ %{
+ "sharedInbox" => _,
+ "oauthAuthorizationEndpoint" => _,
+ "oauthRegistrationEndpoint" => _,
+ "oauthTokenEndpoint" => _
+ } = result["endpoints"]
+ end
+
+ test "remote users have an empty endpoints structure" do
+ user = insert(:user, local: false)
+ {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+
+ result = UserView.render("user.json", %{user: user})
+
+ assert result["id"] == user.ap_id
+ assert result["endpoints"] == %{}
+ end
+
+ test "instance users do not expose oAuth endpoints" do
+ user = insert(:user, nickname: nil, local: true)
+ {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+
+ result = UserView.render("user.json", %{user: user})
+
+ refute result["endpoints"]["oauthAuthorizationEndpoint"]
+ refute result["endpoints"]["oauthRegistrationEndpoint"]
+ refute result["endpoints"]["oauthTokenEndpoint"]
+ end
+ end
end
diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs
index a7d9e6161..d26b6e49c 100644
--- a/test/web/common_api/common_api_test.exs
+++ b/test/web/common_api/common_api_test.exs
@@ -164,4 +164,30 @@ defmodule Pleroma.Web.CommonAPI.Test do
assert %User{info: %{pinned_activities: []}} = user
end
end
+
+ describe "mute tests" do
+ setup do
+ user = insert(:user)
+
+ activity = insert(:note_activity)
+
+ [user: user, activity: activity]
+ end
+
+ test "add mute", %{user: user, activity: activity} do
+ {:ok, _} = CommonAPI.add_mute(user, activity)
+ assert CommonAPI.thread_muted?(user, activity)
+ end
+
+ test "remove mute", %{user: user, activity: activity} do
+ CommonAPI.add_mute(user, activity)
+ {:ok, _} = CommonAPI.remove_mute(user, activity)
+ refute CommonAPI.thread_muted?(user, activity)
+ end
+
+ test "check that mutes can't be duplicate", %{user: user, activity: activity} do
+ CommonAPI.add_mute(user, activity)
+ {:error, _} = CommonAPI.add_mute(user, activity)
+ end
+ end
end
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index 19393f355..26c9c25a6 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -1754,4 +1754,36 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert [json_response(response2, 200)] == json_response(bookmarks, 200)
end
+
+ describe "conversation muting" do
+ setup do
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"})
+
+ [user: user, activity: activity]
+ end
+
+ test "mute conversation", %{conn: conn, user: user, activity: activity} do
+ id_str = to_string(activity.id)
+
+ assert %{"id" => ^id_str, "muted" => true} =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses/#{activity.id}/mute")
+ |> json_response(200)
+ end
+
+ test "unmute conversation", %{conn: conn, user: user, activity: activity} do
+ {:ok, _} = CommonAPI.add_mute(user, activity)
+
+ id_str = to_string(activity.id)
+ user = refresh_record(user)
+
+ assert %{"id" => ^id_str, "muted" => false} =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses/#{activity.id}/unmute")
+ |> json_response(200)
+ end
+ end
end