aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlambda <lain@soykaf.club>2019-04-10 10:04:20 +0000
committerlambda <lain@soykaf.club>2019-04-10 10:04:20 +0000
commit6504b43f96860d1911ed9a17d1c487a6e8ac93ba (patch)
tree8414fc2108941ccadd9b9fd4dd37c9d3cf731399
parentc8440b5e0c97c5d5c03e4b99e30a61df6ab95319 (diff)
parentbe8350baa2f8c9cf9be3f53ee4a0041dada1386f (diff)
downloadpleroma-6504b43f96860d1911ed9a17d1c487a6e8ac93ba.tar.gz
Merge branch 'feature/user-status-subscriptions' into 'develop'
Add ability to subscribe to users See merge request pleroma/pleroma!1024
-rw-r--r--docs/api/pleroma_api.md53
-rw-r--r--lib/pleroma/notification.ex1
-rw-r--r--lib/pleroma/user.ex49
-rw-r--r--lib/pleroma/user/info.ex17
-rw-r--r--lib/pleroma/web/common_api/utils.ex19
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api_controller.ex28
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex1
-rw-r--r--lib/pleroma/web/router.ex3
-rw-r--r--priv/repo/migrations/20190405160700_add_index_on_subscribers.exs8
-rw-r--r--test/notification_test.exs34
-rw-r--r--test/user_test.exs25
-rw-r--r--test/web/mastodon_api/account_view_test.exs2
-rw-r--r--test/web/mastodon_api/mastodon_api_controller_test.exs19
13 files changed, 257 insertions, 2 deletions
diff --git a/docs/api/pleroma_api.md b/docs/api/pleroma_api.md
index a1ea41fe3..dbe250300 100644
--- a/docs/api/pleroma_api.md
+++ b/docs/api/pleroma_api.md
@@ -74,7 +74,7 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
* `confirm`
* `captcha_solution`: optional, contains provider-specific captcha solution,
* `captcha_token`: optional, contains provider-specific captcha token
- * `token`: invite token required when the registerations aren't public.
+ * `token`: invite token required when the registrations aren't public.
* Response: JSON. Returns a user object on success, otherwise returns `{"error": "error_msg"}`
* Example response:
```
@@ -136,8 +136,57 @@ See [Admin-API](Admin-API.md)
* Method `POST`
* Authentication: required
* Params:
- * `id`: notifications's id
+ * `id`: notification's id
* Response: JSON. Returns `{"status": "success"}` if the reading was successful, otherwise returns `{"error": "error_msg"}`
+
+## `/api/v1/pleroma/accounts/:id/subscribe`
+### Subscribe to receive notifications for all statuses posted by a user
+* Method `POST`
+* Authentication: required
+* Params:
+ * `id`: account id to subscribe to
+* Response: JSON, returns a mastodon relationship object on success, otherwise returns `{"error": "error_msg"}`
+* Example response:
+```json
+{
+ "id": "abcdefg",
+ "following": true,
+ "followed_by": false,
+ "blocking": false,
+ "muting": false,
+ "muting_notifications": false,
+ "subscribing": true,
+ "requested": false,
+ "domain_blocking": false,
+ "showing_reblogs": true,
+ "endorsed": false
+}
+```
+
+## `/api/v1/pleroma/accounts/:id/unsubscribe`
+### Unsubscribe to stop receiving notifications from user statuses
+* Method `POST`
+* Authentication: required
+* Params:
+ * `id`: account id to unsubscribe from
+* Response: JSON, returns a mastodon relationship object on success, otherwise returns `{"error": "error_msg"}`
+* Example response:
+```json
+{
+ "id": "abcdefg",
+ "following": true,
+ "followed_by": false,
+ "blocking": false,
+ "muting": false,
+ "muting_notifications": false,
+ "subscribing": false,
+ "requested": false,
+ "domain_blocking": false,
+ "showing_reblogs": true,
+ "endorsed": false
+}
+```
+
## `/api/pleroma/notification_settings`
### Updates user notification settings
* Method `PUT`
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index 14de1a097..15789907a 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -142,6 +142,7 @@ defmodule Pleroma.Notification do
[]
|> Utils.maybe_notify_to_recipients(activity)
|> Utils.maybe_notify_mentioned_recipients(activity)
+ |> Utils.maybe_notify_subscribers(activity)
|> Enum.uniq()
User.get_users_from_set(recipients, local_only)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index ed23b8ef0..6e2269aff 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -931,6 +931,38 @@ defmodule Pleroma.User do
update_and_set_cache(cng)
end
+ def subscribe(subscriber, %{ap_id: ap_id}) do
+ deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
+
+ with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
+ blocked = blocks?(subscribed, subscriber) and deny_follow_blocked
+
+ if blocked do
+ {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
+ else
+ info_cng =
+ subscribed.info
+ |> User.Info.add_to_subscribers(subscriber.ap_id)
+
+ change(subscribed)
+ |> put_embed(:info, info_cng)
+ |> update_and_set_cache()
+ end
+ end
+ end
+
+ def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
+ with %User{} = user <- get_cached_by_ap_id(ap_id) do
+ info_cng =
+ user.info
+ |> User.Info.remove_from_subscribers(unsubscriber.ap_id)
+
+ change(user)
+ |> put_embed(:info, info_cng)
+ |> update_and_set_cache()
+ end
+ end
+
def block(blocker, %User{ap_id: ap_id} = blocked) do
# sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
blocker =
@@ -941,6 +973,14 @@ defmodule Pleroma.User do
blocker
end
+ blocker =
+ if subscribed_to?(blocked, blocker) do
+ {:ok, blocker} = unsubscribe(blocked, blocker)
+ blocker
+ else
+ blocker
+ end
+
if following?(blocked, blocker) do
unfollow(blocked, blocker)
end
@@ -989,12 +1029,21 @@ defmodule Pleroma.User do
end)
end
+ def subscribed_to?(user, %{ap_id: ap_id}) do
+ with %User{} = target <- User.get_by_ap_id(ap_id) do
+ Enum.member?(target.info.subscribers, user.ap_id)
+ end
+ end
+
def muted_users(user),
do: Repo.all(from(u in User, where: u.ap_id in ^user.info.mutes))
def blocked_users(user),
do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks))
+ def subscribers(user),
+ do: Repo.all(from(u in User, where: u.ap_id in ^user.info.subscribers))
+
def block_domain(user, domain) do
info_cng =
user.info
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
index 33fd77b02..5afa7988c 100644
--- a/lib/pleroma/user/info.ex
+++ b/lib/pleroma/user/info.ex
@@ -22,6 +22,7 @@ defmodule Pleroma.User.Info do
field(:domain_blocks, {:array, :string}, default: [])
field(:mutes, {:array, :string}, default: [])
field(:muted_reblogs, {:array, :string}, default: [])
+ field(:subscribers, {:array, :string}, default: [])
field(:deactivated, :boolean, default: false)
field(:no_rich_text, :boolean, default: false)
field(:ap_enabled, :boolean, default: false)
@@ -110,6 +111,14 @@ defmodule Pleroma.User.Info do
|> validate_required([:blocks])
end
+ def set_subscribers(info, subscribers) do
+ params = %{subscribers: subscribers}
+
+ info
+ |> cast(params, [:subscribers])
+ |> validate_required([:subscribers])
+ end
+
def add_to_mutes(info, muted) do
set_mutes(info, Enum.uniq([muted | info.mutes]))
end
@@ -126,6 +135,14 @@ defmodule Pleroma.User.Info do
set_blocks(info, List.delete(info.blocks, blocked))
end
+ def add_to_subscribers(info, subscribed) do
+ set_subscribers(info, Enum.uniq([subscribed | info.subscribers]))
+ end
+
+ def remove_from_subscribers(info, subscribed) do
+ set_subscribers(info, List.delete(info.subscribers, subscribed))
+ end
+
def set_domain_blocks(info, domain_blocks) do
params = %{domain_blocks: domain_blocks}
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 051db6c79..7b9f0ea06 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -12,6 +12,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Endpoint
alias Pleroma.Web.MediaProxy
@@ -335,6 +336,24 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def maybe_notify_mentioned_recipients(recipients, _), do: recipients
+ def maybe_notify_subscribers(
+ recipients,
+ %Activity{data: %{"actor" => actor, "type" => type}} = activity
+ )
+ when type == "Create" do
+ with %User{} = user <- User.get_cached_by_ap_id(actor) do
+ subscriber_ids =
+ user
+ |> User.subscribers()
+ |> Enum.filter(&Visibility.visible_for_user?(activity, &1))
+ |> Enum.map(& &1.ap_id)
+
+ recipients ++ subscriber_ids
+ end
+ end
+
+ def maybe_notify_subscribers(recipients, _), do: recipients
+
def maybe_extract_mentions(%{"tag" => tag}) do
tag
|> Enum.filter(fn x -> is_map(x) end)
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 5462ce8be..ed082abdf 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -931,6 +931,34 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
json(conn, %{})
end
+ def subscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ with %User{} = subscription_target <- User.get_cached_by_id(id),
+ {:ok, subscription_target} = User.subscribe(user, subscription_target) do
+ conn
+ |> put_view(AccountView)
+ |> render("relationship.json", %{user: user, target: subscription_target})
+ else
+ {:error, message} ->
+ conn
+ |> put_resp_content_type("application/json")
+ |> send_resp(403, Jason.encode!(%{"error" => message}))
+ end
+ end
+
+ def unsubscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ with %User{} = subscription_target <- User.get_cached_by_id(id),
+ {:ok, subscription_target} = User.unsubscribe(user, subscription_target) do
+ conn
+ |> put_view(AccountView)
+ |> render("relationship.json", %{user: user, target: subscription_target})
+ else
+ {:error, message} ->
+ conn
+ |> put_resp_content_type("application/json")
+ |> send_resp(403, Jason.encode!(%{"error" => message}))
+ end
+ end
+
def status_search(user, query) do
fetched =
if Regex.match?(~r/https?:/, query) do
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 25899e491..af56c4149 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -53,6 +53,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
blocking: User.blocks?(user, target),
muting: User.mutes?(user, target),
muting_notifications: false,
+ subscribing: User.subscribed_to?(user, target),
requested: requested,
domain_blocking: false,
showing_reblogs: User.showing_reblogs?(user, target),
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 66db5e628..06c8d7a03 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -337,6 +337,9 @@ defmodule Pleroma.Web.Router do
post("/domain_blocks", MastodonAPIController, :block_domain)
delete("/domain_blocks", MastodonAPIController, :unblock_domain)
+
+ post("/pleroma/accounts/:id/subscribe", MastodonAPIController, :subscribe)
+ post("/pleroma/accounts/:id/unsubscribe", MastodonAPIController, :unsubscribe)
end
scope [] do
diff --git a/priv/repo/migrations/20190405160700_add_index_on_subscribers.exs b/priv/repo/migrations/20190405160700_add_index_on_subscribers.exs
new file mode 100644
index 000000000..232f75c92
--- /dev/null
+++ b/priv/repo/migrations/20190405160700_add_index_on_subscribers.exs
@@ -0,0 +1,8 @@
+defmodule Pleroma.Repo.Migrations.AddIndexOnSubscribers do
+ use Ecto.Migration
+
+ @disable_ddl_transaction true
+ def change do
+ create index(:users, ["(info->'subscribers')"], name: :users_subscribers_index, using: :gin, concurrently: true)
+ end
+end
diff --git a/test/notification_test.exs b/test/notification_test.exs
index 89d06b3a2..c3db77b6c 100644
--- a/test/notification_test.exs
+++ b/test/notification_test.exs
@@ -29,6 +29,18 @@ defmodule Pleroma.NotificationTest do
assert notification.activity_id == activity.id
assert other_notification.activity_id == activity.id
end
+
+ test "it creates a notification for subscribed users" do
+ user = insert(:user)
+ subscriber = insert(:user)
+
+ User.subscribe(subscriber, user)
+
+ {:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"})
+ {:ok, [notification]} = Notification.create_notifications(status)
+
+ assert notification.user_id == subscriber.id
+ end
end
describe "create_notification" do
@@ -153,6 +165,28 @@ defmodule Pleroma.NotificationTest do
{:ok, dupe} = TwitterAPI.repeat(user, status.id)
assert nil == Notification.create_notification(dupe, retweeted_user)
end
+
+ test "it doesn't create duplicate notifications for follow+subscribed users" do
+ user = insert(:user)
+ subscriber = insert(:user)
+
+ {:ok, _, _, _} = TwitterAPI.follow(subscriber, %{"user_id" => user.id})
+ User.subscribe(subscriber, user)
+ {:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"})
+ {:ok, [_notif]} = Notification.create_notifications(status)
+ end
+
+ test "it doesn't create subscription notifications if the recipient cannot see the status" do
+ user = insert(:user)
+ subscriber = insert(:user)
+
+ User.subscribe(subscriber, user)
+
+ {:ok, status} =
+ TwitterAPI.create_status(user, %{"status" => "inwisible", "visibility" => "direct"})
+
+ assert {:ok, []} == Notification.create_notifications(status)
+ end
end
describe "get notification" do
diff --git a/test/user_test.exs b/test/user_test.exs
index 4f98af683..d2167a970 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -146,6 +146,15 @@ defmodule Pleroma.UserTest do
{:error, _} = User.follow(blockee, blocker)
end
+ test "can't subscribe to a user who blocked us" do
+ blocker = insert(:user)
+ blocked = insert(:user)
+
+ {:ok, blocker} = User.block(blocker, blocked)
+
+ {:error, _} = User.subscribe(blocked, blocker)
+ end
+
test "local users do not automatically follow local locked accounts" do
follower = insert(:user, info: %{locked: true})
followed = insert(:user, info: %{locked: true})
@@ -729,6 +738,22 @@ defmodule Pleroma.UserTest do
refute User.following?(blocker, blocked)
refute User.following?(blocked, blocker)
end
+
+ test "blocks tear down blocked->blocker subscription relationships" do
+ blocker = insert(:user)
+ blocked = insert(:user)
+
+ {:ok, blocker} = User.subscribe(blocked, blocker)
+
+ assert User.subscribed_to?(blocked, blocker)
+ refute User.subscribed_to?(blocker, blocked)
+
+ {:ok, blocker} = User.block(blocker, blocked)
+
+ assert User.blocks?(blocker, blocked)
+ refute User.subscribed_to?(blocker, blocked)
+ refute User.subscribed_to?(blocked, blocker)
+ end
end
describe "domain blocking" do
diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/account_view_test.exs
index aa6a1e960..d7487bed9 100644
--- a/test/web/mastodon_api/account_view_test.exs
+++ b/test/web/mastodon_api/account_view_test.exs
@@ -156,6 +156,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
blocking: true,
muting: false,
muting_notifications: false,
+ subscribing: false,
requested: false,
domain_blocking: false,
showing_reblogs: true,
@@ -212,6 +213,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
following: false,
followed_by: false,
blocking: true,
+ subscribing: false,
muting: false,
muting_notifications: false,
requested: false,
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index e16862a48..3ac5c37a6 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -1556,6 +1556,25 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert %{"id" => _id, "muting" => false} = json_response(conn, 200)
end
+ test "subscribing / unsubscribing to a user", %{conn: conn} do
+ user = insert(:user)
+ subscription_target = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe")
+
+ assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200)
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe")
+
+ assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200)
+ end
+
test "getting a list of mutes", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)