Merge branch 'notification-pleroma-settings' into 'develop'
Notification controls Closes #738 See merge request pleroma/pleroma!988
This commit is contained in:
commit
97395e013e
10 changed files with 217 additions and 14 deletions
|
@ -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"}`
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"])
|
||||||
|
|
|
@ -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, %{
|
||||||
|
|
|
@ -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])
|
||||||
|
|
Loading…
Reference in a new issue