diff options
-rw-r--r-- | lib/pleroma/user.ex | 28 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/activity_pub.ex | 18 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/views/user_view.ex | 3 | ||||
-rw-r--r-- | lib/pleroma/web/common_api/common_api.ex | 23 | ||||
-rw-r--r-- | lib/pleroma/web/common_api/utils.ex | 12 | ||||
-rw-r--r-- | lib/pleroma/web/ostatus/activity_representer.ex | 5 | ||||
-rw-r--r-- | lib/pleroma/web/router.ex | 15 | ||||
-rw-r--r-- | test/notification_test.exs | 121 | ||||
-rw-r--r-- | test/web/common_api/common_api_test.exs | 11 | ||||
-rw-r--r-- | test/web/common_api/common_api_utils_test.exs | 23 |
10 files changed, 246 insertions, 13 deletions
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 3bcfcdd91..88293a4f3 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -457,13 +457,29 @@ defmodule Pleroma.User do update_and_set_cache(cs) end + def get_notified_from_activity_query(to) do + from( + u in User, + where: u.ap_id in ^to, + where: u.local == true + ) + end + + def get_notified_from_activity(%Activity{recipients: to, data: %{"type" => "Announce"} = data}) do + object = Object.normalize(data["object"]) + + # ensure that the actor who published the announced object appears only once + to = + (to ++ [object.data["actor"]]) + |> Enum.uniq() + + query = get_notified_from_activity_query(to) + + Repo.all(query) + end + def get_notified_from_activity(%Activity{recipients: to}) do - query = - from( - u in User, - where: u.ap_id in ^to, - where: u.local == true - ) + query = get_notified_from_activity_query(to) Repo.all(query) end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 3a25f614e..68b398786 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -12,6 +12,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do @instance Application.get_env(:pleroma, :instance) + # For Announce activities, we filter the recipients based on following status for any actors + # that match actual users. See issue #164 for more information about why this is necessary. + def get_recipients(%{"type" => "Announce"} = data) do + recipients = (data["to"] || []) ++ (data["cc"] || []) + actor = User.get_cached_by_ap_id(data["actor"]) + + recipients + |> Enum.filter(fn recipient -> + case User.get_cached_by_ap_id(recipient) do + nil -> + true + + user -> + User.following?(user, actor) + end + end) + end + def get_recipients(data) do (data["to"] || []) ++ (data["cc"] || []) end diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index b57cbb9ac..16419e1b7 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -71,7 +71,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do "image" => %{ "type" => "Image", "url" => User.banner_url(user) - } + }, + "tag" => user.info["source_data"]["tag"] || [] } |> Map.merge(Utils.make_json_ld_header()) end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 3f18a68e8..125c57d05 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -1,5 +1,5 @@ defmodule Pleroma.Web.CommonAPI do - alias Pleroma.{Repo, Activity, Object} + alias Pleroma.{User, Repo, Activity, Object} alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Formatter @@ -61,8 +61,13 @@ defmodule Pleroma.Web.CommonAPI do do: visibility def get_visibility(%{"in_reply_to_status_id" => status_id}) when not is_nil(status_id) do - inReplyTo = get_replied_to_activity(status_id) - Pleroma.Web.MastodonAPI.StatusView.get_visibility(inReplyTo.data["object"]) + case get_replied_to_activity(status_id) do + nil -> + "public" + + inReplyTo -> + Pleroma.Web.MastodonAPI.StatusView.get_visibility(inReplyTo.data["object"]) + end end def get_visibility(_), do: "public" @@ -118,6 +123,18 @@ defmodule Pleroma.Web.CommonAPI do end def update(user) do + user = + with emoji <- emoji_from_profile(user), + source_data <- (user.info["source_data"] || %{}) |> Map.put("tag", emoji), + new_info <- Map.put(user.info, "source_data", source_data), + change <- User.info_changeset(user, %{info: new_info}), + {:ok, user} <- User.update_and_set_cache(change) do + user + else + _e -> + user + end + ActivityPub.update(%{ local: true, to: [user.follower_address], diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 869f4c566..358ca22ac 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -1,6 +1,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do alias Pleroma.{Repo, Object, Formatter, Activity} alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.Endpoint alias Pleroma.User alias Calendar.Strftime alias Comeonin.Pbkdf2 @@ -195,4 +196,15 @@ defmodule Pleroma.Web.CommonAPI.Utils do _ -> {:error, "Invalid password."} end end + + def emoji_from_profile(%{info: info} = user) do + (Formatter.get_emoji(user.bio) ++ Formatter.get_emoji(user.name)) + |> Enum.map(fn {shortcode, url} -> + %{ + "type" => "Emoji", + "icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"}, + "name" => ":#{shortcode}:" + } + end) + end end diff --git a/lib/pleroma/web/ostatus/activity_representer.ex b/lib/pleroma/web/ostatus/activity_representer.ex index 5d831459b..537bd9f77 100644 --- a/lib/pleroma/web/ostatus/activity_representer.ex +++ b/lib/pleroma/web/ostatus/activity_representer.ex @@ -184,7 +184,10 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do retweeted_xml = to_simple_form(retweeted_activity, retweeted_user, true) - mentions = activity.recipients |> get_mentions + mentions = + ([retweeted_user.ap_id] ++ activity.recipients) + |> Enum.uniq() + |> get_mentions() [ {:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']}, diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 7b4c81e00..927323794 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -9,8 +9,19 @@ defmodule Pleroma.Web.Router do @public Keyword.get(@instance, :public) @registrations_open Keyword.get(@instance, :registrations_open) - def user_fetcher(username) do - {:ok, Repo.get_by(User, %{nickname: username})} + def user_fetcher(username_or_email) do + { + :ok, + cond do + # First, try logging in as if it was a name + user = Repo.get_by(User, %{nickname: username_or_email}) -> + user + + # If we get nil, we try using it as an email + user = Repo.get_by(User, %{email: username_or_email}) -> + user + end + } end pipeline :api do diff --git a/test/notification_test.exs b/test/notification_test.exs index 2ca1ac13d..d86b5c1ab 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -1,6 +1,7 @@ defmodule Pleroma.NotificationTest do use Pleroma.DataCase alias Pleroma.Web.TwitterAPI.TwitterAPI + alias Pleroma.Web.CommonAPI alias Pleroma.{User, Notification} import Pleroma.Factory @@ -119,4 +120,124 @@ defmodule Pleroma.NotificationTest do assert Notification.for_user(third_user) != [] end end + + describe "notification lifecycle" do + test "liking an activity results in 1 notification, then 0 if the activity is deleted" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) + + assert length(Notification.for_user(user)) == 0 + + {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + + assert length(Notification.for_user(user)) == 1 + + {:ok, _} = CommonAPI.delete(activity.id, user) + + assert length(Notification.for_user(user)) == 0 + end + + test "liking an activity results in 1 notification, then 0 if the activity is unliked" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) + + assert length(Notification.for_user(user)) == 0 + + {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + + assert length(Notification.for_user(user)) == 1 + + {:ok, _, _, _} = CommonAPI.unfavorite(activity.id, other_user) + + assert length(Notification.for_user(user)) == 0 + end + + test "repeating an activity results in 1 notification, then 0 if the activity is deleted" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) + + assert length(Notification.for_user(user)) == 0 + + {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + + assert length(Notification.for_user(user)) == 1 + + {:ok, _} = CommonAPI.delete(activity.id, user) + + assert length(Notification.for_user(user)) == 0 + end + + test "repeating an activity results in 1 notification, then 0 if the activity is unrepeated" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) + + assert length(Notification.for_user(user)) == 0 + + {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + + assert length(Notification.for_user(user)) == 1 + + {:ok, _, _} = CommonAPI.unrepeat(activity.id, other_user) + + assert length(Notification.for_user(user)) == 0 + end + + test "liking an activity which is already deleted does not generate a notification" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) + + assert length(Notification.for_user(user)) == 0 + + {:ok, _deletion_activity} = CommonAPI.delete(activity.id, user) + + assert length(Notification.for_user(user)) == 0 + + {:error, _} = CommonAPI.favorite(activity.id, other_user) + + assert length(Notification.for_user(user)) == 0 + end + + test "repeating an activity which is already deleted does not generate a notification" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) + + assert length(Notification.for_user(user)) == 0 + + {:ok, _deletion_activity} = CommonAPI.delete(activity.id, user) + + assert length(Notification.for_user(user)) == 0 + + {:error, _} = CommonAPI.repeat(activity.id, other_user) + + assert length(Notification.for_user(user)) == 0 + end + + test "replying to a deleted post without tagging does not generate a notification" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) + {:ok, _deletion_activity} = CommonAPI.delete(activity.id, user) + + {:ok, _reply_activity} = + CommonAPI.post(other_user, %{ + "status" => "test reply", + "in_reply_to_status_id" => activity.id + }) + + assert length(Notification.for_user(user)) == 0 + end + end end diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index a5da271b3..2a2c40833 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -1,6 +1,7 @@ defmodule Pleroma.Web.CommonAPI.Test do use Pleroma.DataCase alias Pleroma.Web.CommonAPI + alias Pleroma.User import Pleroma.Factory @@ -10,4 +11,14 @@ defmodule Pleroma.Web.CommonAPI.Test do assert activity.data["object"]["tag"] == ["2hu"] end + + test "it adds emoji when updating profiles" do + user = insert(:user, %{name: ":karjalanpiirakka:"}) + + CommonAPI.update(user) + user = User.get_cached_by_ap_id(user.ap_id) + [karjalanpiirakka] = user.info["source_data"]["tag"] + + assert karjalanpiirakka["name"] == ":karjalanpiirakka:" + end end diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs index f39472ee3..b01ce04f8 100644 --- a/test/web/common_api/common_api_utils_test.exs +++ b/test/web/common_api/common_api_utils_test.exs @@ -1,5 +1,6 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do alias Pleroma.Web.CommonAPI.Utils + alias Pleroma.Web.Endpoint alias Pleroma.Builders.{UserBuilder} use Pleroma.DataCase @@ -29,4 +30,26 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do assert Utils.confirm_current_password(user, "test") == {:ok, user} end end + + test "parses emoji from name and bio" do + {:ok, user} = UserBuilder.insert(%{name: ":karjalanpiirakka:", bio: ":perkele:"}) + + expected = [ + %{ + "type" => "Emoji", + "icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}/finmoji/128px/perkele-128.png"}, + "name" => ":perkele:" + }, + %{ + "type" => "Emoji", + "icon" => %{ + "type" => "Image", + "url" => "#{Endpoint.url()}/finmoji/128px/karjalanpiirakka-128.png" + }, + "name" => ":karjalanpiirakka:" + } + ] + + assert expected == Utils.emoji_from_profile(user) + end end |