diff --git a/config/config.exs b/config/config.exs
index 776c5eef28..80ea57ddd2 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -747,7 +747,8 @@
events_actions: {10_000, 15},
password_reset: {1_800_000, 5},
account_confirmation_resend: {8_640_000, 5},
- ap_routes: {60_000, 15}
+ ap_routes: {60_000, 15},
+ bites: {10_000, 10}
config :pleroma, Pleroma.Workers.PurgeExpiredActivity, enabled: true, min_lifetime: 600
diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex
index e341ab8df9..ab11315e85 100644
--- a/lib/pleroma/constants.ex
+++ b/lib/pleroma/constants.ex
@@ -115,7 +115,9 @@ defmodule Pleroma.Constants do
"Announce",
"Undo",
"Flag",
- "EmojiReact"
+ "EmojiReact",
+ "Bite",
+ "Join"
]
)
diff --git a/lib/pleroma/web/activity_pub/object_validators/bite_validator.ex b/lib/pleroma/web/activity_pub/object_validators/bite_validator.ex
index a2e0bac85e..51e58640e3 100644
--- a/lib/pleroma/web/activity_pub/object_validators/bite_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/bite_validator.ex
@@ -38,7 +38,7 @@ defp validate_data(cng) do
|> validate_required([:id, :type, :actor, :to, :target])
|> validate_inclusion(:type, ["Bite"])
|> validate_actor_presence()
- |> validate_actor_presence(field_name: :target)
+ |> validate_object_or_user_presence(field_name: :target)
end
def cast_and_validate(data) do
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 4ecdb19e1b..8e7cd7a5cb 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -497,6 +497,32 @@ def handle_incoming(
end
end
+ def handle_incoming(
+ %{"type" => "Bite", "target" => target_id} = data,
+ _options
+ ) do
+ target_id =
+ cond do
+ %User{ap_id: actor_id} = User.get_by_ap_id(target_id) ->
+ actor_id
+
+ %Object{data: data} = Object.get_by_ap_id(target_id) ->
+ data["actor"] || data["attributedTo"]
+
+ true ->
+ target_id
+ end
+
+ with data = Map.put(data, "target", target_id),
+ :ok <- ObjectValidator.fetch_actor_and_object(data),
+ {:ok, activity, _meta} <-
+ Pipeline.common_pipeline(data, local: false) do
+ {:ok, activity}
+ else
+ e -> {:error, e}
+ end
+ end
+
def handle_incoming(%{"type" => type} = data, _options)
when type in ~w{Like EmojiReact Announce Add Remove} do
with :ok <- ObjectValidator.fetch_actor_and_object(data),
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 6b2983d9d8..19b1438717 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -1070,6 +1070,7 @@ def fetch_latest_bite(
|> maybe_exclude_activity_id(exclude_activity)
|> Activity.Queries.by_object_id(bitten_ap_id)
|> order_by([activity], fragment("? desc nulls last", activity.id))
+ |> exclude_rejected()
|> limit(1)
|> Repo.one()
end
@@ -1080,4 +1081,14 @@ defp maybe_exclude_activity_id(query, %Activity{id: activity_id}) do
query
|> where([a], a.id != ^activity_id)
end
+
+ defp exclude_rejected(query) do
+ rejected_activities =
+ "Reject"
+ |> Activity.Queries.by_type()
+ |> select([a], fragment("?->>'object'", a.data))
+
+ query
+ |> where([a], fragment("?->>'id'", a.data) not in subquery(rejected_activities))
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/bite_controller.ex b/lib/pleroma/web/mastodon_api/controllers/bite_controller.ex
index 69d865cb9b..b9b1310103 100644
--- a/lib/pleroma/web/mastodon_api/controllers/bite_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/bite_controller.ex
@@ -9,14 +9,13 @@ defmodule Pleroma.Web.MastodonAPI.BiteController do
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Plugs.OAuthScopesPlug
- # alias Pleroma.Web.Plugs.RateLimiter
+ alias Pleroma.Web.Plugs.RateLimiter
plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false)
- plug(OAuthScopesPlug, %{scopes: ["write:bite"]} when action == :bite)
+ plug(OAuthScopesPlug, %{scopes: ["write:bites"]} when action == :bite)
- # plug(RateLimiter, [name: :relations_actions] when action in @relationship_actions)
- # plug(RateLimiter, [name: :app_account_creation] when action == :create)
+ plug(RateLimiter, name: :bites)
plug(:assign_account_by_id)
diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld
index 921c373af1..5a9e5c4bdc 100644
--- a/priv/static/schemas/litepub-0.1.jsonld
+++ b/priv/static/schemas/litepub-0.1.jsonld
@@ -44,6 +44,7 @@
"formerRepresentations": "litepub:formerRepresentations",
"sm": "http://smithereen.software/ns#",
"nonAnonymous": "sm:nonAnonymous",
+<<<<<<< HEAD
"votersCount": "toot:votersCount",
"mz": "https://joinmobilizon.org/ns#",
"joinMode": {
@@ -67,6 +68,8 @@
"@id": "schema:location",
"@type": "schema:Place"
},
+=======
+>>>>>>> bites-pleroma
"Bite": "https://ns.mia.jetzt/as#Bite"
}
]
diff --git a/test/pleroma/web/activity_pub/object_validators/bite_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/bite_validator_test.exs
new file mode 100644
index 0000000000..94e433d5af
--- /dev/null
+++ b/test/pleroma/web/activity_pub/object_validators/bite_validator_test.exs
@@ -0,0 +1,51 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2024 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.BiteValidationTest do
+ use Pleroma.DataCase, async: true
+
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.BiteValidator
+ alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "bites" do
+ setup do
+ biting = insert(:user)
+ bitten = insert(:user)
+
+ valid_bite = %{
+ "id" => Utils.generate_activity_id(),
+ "type" => "Bite",
+ "actor" => biting.ap_id,
+ "target" => bitten.ap_id,
+ "to" => [bitten.ap_id]
+ }
+
+ %{valid_bite: valid_bite, biting: biting, bitten: bitten}
+ end
+
+ test "returns ok when called in the ObjectValidator", %{valid_bite: valid_bite} do
+ {:ok, object, _meta} = ObjectValidator.validate(valid_bite, [])
+
+ assert "id" in Map.keys(object)
+ end
+
+ test "is valid for a valid object", %{valid_bite: valid_bite} do
+ assert BiteValidator.cast_and_validate(valid_bite).valid?
+ end
+
+ test "is valid when biting an object", %{valid_bite: valid_bite, bitten: bitten} do
+ {:ok, activity} = CommonAPI.post(bitten, %{status: "uguu"})
+
+ valid_bite =
+ valid_bite
+ |> Map.put("target", activity.data["object"])
+
+ assert BiteValidator.cast_and_validate(valid_bite).valid?
+ end
+ end
+end
diff --git a/test/pleroma/web/mastodon_api/controllers/bite_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/bite_controller_test.exs
new file mode 100644
index 0000000000..96cd38e762
--- /dev/null
+++ b/test/pleroma/web/mastodon_api/controllers/bite_controller_test.exs
@@ -0,0 +1,30 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2024 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.BiteControllerTest do
+ use Pleroma.Web.ConnCase
+ import Pleroma.Factory
+
+ setup do: oauth_access(["write:bites"])
+
+ test "bites a user", %{conn: conn} do
+ %{id: bitten_id} = insert(:user)
+
+ response =
+ conn
+ |> post("/api/v1/bite?id=#{bitten_id}")
+ |> json_response_and_validate_schema(200)
+
+ assert response == %{}
+ end
+
+ test "self harm is not supported", %{conn: conn, user: %{id: self_id}} do
+ response =
+ conn
+ |> post("/api/v1/bite?id=#{self_id}")
+ |> json_response_and_validate_schema(400)
+
+ assert %{"error" => "Can not bite yourself"} = response
+ end
+end