Merge branch 'notification-pleroma-settings' into 'develop'

Notification controls

Closes #738

See merge request pleroma/pleroma!988
This commit is contained in:
kaniini 2019-04-10 02:10:31 +00:00
commit 97395e013e
10 changed files with 217 additions and 14 deletions

View File

@ -116,3 +116,13 @@ See [Admin-API](Admin-API.md)
* Params: * Params:
* `id`: notifications's id * `id`: notifications's id
* Response: JSON. Returns `{"status": "success"}` if the reading was successful, otherwise returns `{"error": "error_msg"}` * Response: JSON. Returns `{"status": "success"}` if the reading was successful, otherwise returns `{"error": "error_msg"}`
## `/api/pleroma/notification_settings`
### Updates user notification settings
* Method `PUT`
* Authentication: required
* Params:
* `followers`: BOOLEAN field, receives notifications from followers
* `follows`: BOOLEAN field, receives notifications from people the user follows
* `remote`: BOOLEAN field, receives notifications from people on remote instances
* `local`: BOOLEAN field, receives notifications from people on the local instance
* Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}`

View File

@ -122,13 +122,7 @@ def create_notifications(_), do: {:ok, []}
# TODO move to sql, too. # TODO move to sql, too.
def create_notification(%Activity{} = activity, %User{} = user) do def create_notification(%Activity{} = activity, %User{} = user) do
unless User.blocks?(user, %{ap_id: activity.data["actor"]}) or unless skip?(activity, user) do
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
notif.activity.data["actor"] == activity.data["actor"]
end)) do
notification = %Notification{user_id: user.id, activity: activity} notification = %Notification{user_id: user.id, activity: activity}
{:ok, notification} = Repo.insert(notification) {:ok, notification} = Repo.insert(notification)
Pleroma.Web.Streamer.stream("user", notification) Pleroma.Web.Streamer.stream("user", notification)
@ -154,4 +148,59 @@ def get_notified_from_activity(
end end
def get_notified_from_activity(_, _local_only), do: [] def get_notified_from_activity(_, _local_only), do: []
def skip?(activity, user) do
[:self, :blocked, :local, :muted, :followers, :follows, :recently_followed]
|> Enum.any?(&skip?(&1, activity, user))
end
def skip?(:self, activity, user) do
activity.data["actor"] == user.ap_id
end
def skip?(:blocked, activity, user) do
actor = activity.data["actor"]
User.blocks?(user, %{ap_id: actor})
end
def skip?(:local, %{local: true}, %{info: %{notification_settings: %{"local" => false}}}),
do: true
def skip?(:local, %{local: false}, %{info: %{notification_settings: %{"remote" => false}}}),
do: true
def skip?(:muted, activity, user) do
actor = activity.data["actor"]
User.mutes?(user, %{ap_id: actor}) or
CommonAPI.thread_muted?(user, activity)
end
def skip?(
:followers,
activity,
%{info: %{notification_settings: %{"followers" => false}}} = user
) do
actor = activity.data["actor"]
follower = User.get_cached_by_ap_id(actor)
User.following?(follower, user)
end
def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
actor = activity.data["actor"]
followed = User.get_by_ap_id(actor)
User.following?(user, followed)
end
def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do
actor = activity.data["actor"]
Notification.for_user(user)
|> Enum.any?(fn
%{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}} -> true
_ -> false
end)
end
def skip?(_, _, _), do: false
end end

View File

@ -1092,6 +1092,14 @@ def deactivate(%User{} = user, status \\ true) do
update_and_set_cache(cng) update_and_set_cache(cng)
end end
def update_notification_settings(%User{} = user, settings \\ %{}) do
info_changeset = User.Info.update_notification_settings(user.info, settings)
change(user)
|> put_embed(:info, info_changeset)
|> update_and_set_cache()
end
def delete(%User{} = user) do def delete(%User{} = user) do
{:ok, user} = User.deactivate(user) {:ok, user} = User.deactivate(user)

View File

@ -40,6 +40,10 @@ defmodule Pleroma.User.Info do
field(:pinned_activities, {:array, :string}, default: []) field(:pinned_activities, {:array, :string}, default: [])
field(:flavour, :string, default: nil) field(:flavour, :string, default: nil)
field(:notification_settings, :map,
default: %{"remote" => true, "local" => true, "followers" => true, "follows" => true}
)
# Found in the wild # Found in the wild
# ap_id -> Where is this used? # ap_id -> Where is this used?
# bio -> Where is this used? # bio -> Where is this used?
@ -57,6 +61,19 @@ def set_activation_status(info, deactivated) do
|> validate_required([:deactivated]) |> validate_required([:deactivated])
end end
def update_notification_settings(info, settings) do
notification_settings =
info.notification_settings
|> Map.merge(settings)
|> Map.take(["remote", "local", "followers", "follows"])
params = %{notification_settings: notification_settings}
info
|> cast(params, [:notification_settings])
|> validate_required([:notification_settings])
end
def add_to_note_count(info, number) do def add_to_note_count(info, number) do
set_note_count(info, info.note_count + number) set_note_count(info, info.note_count + number)
end end

View File

@ -117,13 +117,15 @@ defp do_render("account.json", %{user: user} = opts) do
}, },
# Pleroma extension # Pleroma extension
pleroma: %{ pleroma:
confirmation_pending: user_info.confirmation_pending, %{
tags: user.tags, confirmation_pending: user_info.confirmation_pending,
is_moderator: user.info.is_moderator, tags: user.tags,
is_admin: user.info.is_admin, is_moderator: user.info.is_moderator,
relationship: relationship is_admin: user.info.is_admin,
} relationship: relationship
}
|> with_notification_settings(user, opts[:for])
} }
end end
@ -132,4 +134,10 @@ defp username_from_nickname(string) when is_binary(string) do
end end
defp username_from_nickname(_), do: nil defp username_from_nickname(_), do: nil
defp with_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
Map.put(data, :notification_settings, user.info.notification_settings)
end
defp with_notification_settings(data, _, _), do: data
end end

View File

@ -193,6 +193,7 @@ defmodule Pleroma.Web.Router do
post("/change_password", UtilController, :change_password) post("/change_password", UtilController, :change_password)
post("/delete_account", UtilController, :delete_account) post("/delete_account", UtilController, :delete_account)
put("/notification_settings", UtilController, :update_notificaton_settings)
end end
scope [] do scope [] do

View File

@ -292,6 +292,12 @@ def emoji(conn, _params) do
json(conn, emoji) json(conn, emoji)
end end
def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
with {:ok, _} <- User.update_notification_settings(user, params) do
json(conn, %{status: "success"})
end
end
def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
follow_import(conn, %{"list" => File.read!(listfile.path)}) follow_import(conn, %{"list" => File.read!(listfile.path)})
end end

View File

@ -41,6 +41,75 @@ test "it doesn't create a notification for user if the user blocks the activity
assert nil == Notification.create_notification(activity, user) assert nil == Notification.create_notification(activity, user)
end end
test "it doesn't create a notificatin for the user if the user mutes the activity author" do
muter = insert(:user)
muted = insert(:user)
{:ok, _} = User.mute(muter, muted)
muter = Repo.get(User, muter.id)
{:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"})
assert nil == Notification.create_notification(activity, muter)
end
test "it doesn't create a notification for an activity from a muted thread" do
muter = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(muter, %{"status" => "hey"})
CommonAPI.add_mute(muter, activity)
{:ok, activity} =
CommonAPI.post(other_user, %{
"status" => "Hi @#{muter.nickname}",
"in_reply_to_status_id" => activity.id
})
assert nil == Notification.create_notification(activity, muter)
end
test "it disables notifications from people on remote instances" do
user = insert(:user, info: %{notification_settings: %{"remote" => false}})
other_user = insert(:user)
create_activity = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"actor" => other_user.ap_id,
"object" => %{
"type" => "Note",
"content" => "Hi @#{user.nickname}",
"attributedTo" => other_user.ap_id
}
}
{:ok, %{local: false} = activity} = Transmogrifier.handle_incoming(create_activity)
assert nil == Notification.create_notification(activity, user)
end
test "it disables notifications from people on the local instance" do
user = insert(:user, info: %{notification_settings: %{"local" => false}})
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
assert nil == Notification.create_notification(activity, user)
end
test "it disables notifications from followers" do
follower = insert(:user)
followed = insert(:user, info: %{notification_settings: %{"followers" => false}})
User.follow(follower, followed)
{:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"})
assert nil == Notification.create_notification(activity, followed)
end
test "it disables notifications from people the user follows" do
follower = insert(:user, info: %{notification_settings: %{"follows" => false}})
followed = insert(:user)
User.follow(follower, followed)
follower = Repo.get(User, follower.id)
{:ok, activity} = CommonAPI.post(followed, %{"status" => "hey @#{follower.nickname}"})
assert nil == Notification.create_notification(activity, follower)
end
test "it doesn't create a notification for user if he is the activity author" do test "it doesn't create a notification for user if he is the activity author" do
activity = insert(:note_activity) activity = insert(:note_activity)
author = User.get_by_ap_id(activity.data["actor"]) author = User.get_by_ap_id(activity.data["actor"])

View File

@ -71,6 +71,20 @@ test "Represent a user account" do
assert expected == AccountView.render("account.json", %{user: user}) assert expected == AccountView.render("account.json", %{user: user})
end end
test "Represent the user account for the account owner" do
user = insert(:user)
notification_settings = %{
"remote" => true,
"local" => true,
"followers" => true,
"follows" => true
}
assert %{pleroma: %{notification_settings: ^notification_settings}} =
AccountView.render("account.json", %{user: user, for: user})
end
test "Represent a Service(bot) account" do test "Represent a Service(bot) account" do
user = user =
insert(:user, %{ insert(:user, %{

View File

@ -3,6 +3,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
import Pleroma.Factory import Pleroma.Factory
@ -79,6 +80,26 @@ test "it marks a single notification as read", %{conn: conn} do
end end
end end
describe "PUT /api/pleroma/notification_settings" do
test "it updates notification settings", %{conn: conn} do
user = insert(:user)
conn
|> assign(:user, user)
|> put("/api/pleroma/notification_settings", %{
"remote" => false,
"followers" => false,
"bar" => 1
})
|> json_response(:ok)
user = Repo.get(User, user.id)
assert %{"remote" => false, "local" => true, "followers" => false, "follows" => true} ==
user.info.notification_settings
end
end
describe "GET /api/statusnet/config.json" do describe "GET /api/statusnet/config.json" do
test "returns the state of safe_dm_mentions flag", %{conn: conn} do test "returns the state of safe_dm_mentions flag", %{conn: conn} do
option = Pleroma.Config.get([:instance, :safe_dm_mentions]) option = Pleroma.Config.get([:instance, :safe_dm_mentions])