Skip to content

Commit

Permalink
Merge branch 'bites' into fork
Browse files Browse the repository at this point in the history
Signed-off-by: marcin mikołajczak <[email protected]>
  • Loading branch information
mkljczk committed Aug 27, 2024
2 parents 06441a4 + 5a32365 commit e110fc5
Show file tree
Hide file tree
Showing 21 changed files with 321 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
*.exs diff=elixir

priv/static/instance/static.css diff=css
priv/static/schemas/litepub-0.1.jsonld diff

# Most of js/css files included in the repo are minified bundles,
# and we don't want to search/diff those as text files.
Expand Down
10 changes: 8 additions & 2 deletions lib/pleroma/notification.ex
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ defmodule Pleroma.Notification do
pleroma:participation_request
pleroma:event_reminder
pleroma:event_update
bite
}

def changeset(%Notification{} = notification, attrs) do
Expand Down Expand Up @@ -390,7 +391,8 @@ defmodule Pleroma.Notification do
"Flag",
"Update",
"Accept",
"Join"
"Join",
"Bite"
] do
do_create_notifications(activity)
end
Expand Down Expand Up @@ -462,6 +464,9 @@ defmodule Pleroma.Notification do
"Join" ->
"pleroma:participation_request"

"Bite" ->
"bite"

t ->
raise "No notification type for activity type #{t}"
end
Expand Down Expand Up @@ -562,7 +567,8 @@ defmodule Pleroma.Notification do
"Flag",
"Update",
"Accept",
"Join"
"Join",
"Bite"
] do
potential_receiver_ap_ids = get_potential_receiver_ap_ids(activity)

Expand Down
11 changes: 11 additions & 0 deletions lib/pleroma/web/activity_pub/builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -478,4 +478,15 @@ defmodule Pleroma.Web.ActivityPub.Builder do

{:ok, data, []}
end

def bite(%User{} = biting, %User{} = bitten) do
{:ok,
%{
"id" => Utils.generate_activity_id(),
"object" => bitten.ap_id,
"actor" => biting.ap_id,
"type" => "Bite",
"to" => [bitten.ap_id]
}, []}
end
end
4 changes: 3 additions & 1 deletion lib/pleroma/web/activity_pub/object_validator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.AudioImageVideoValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.BiteValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
Expand Down Expand Up @@ -191,7 +192,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do

def validate(%{"type" => type} = object, meta)
when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
ChatMessage Answer Join Leave] do
ChatMessage Answer Join Leave Bite] do
validator =
case type do
"Accept" -> AcceptRejectValidator
Expand All @@ -205,6 +206,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
"Answer" -> AnswerValidator
"Join" -> JoinValidator
"Leave" -> LeaveValidator
"Bite" -> BiteValidator
end

with {:ok, object} <-
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
|> validate_required([:id, :type, :actor, :to, :object])
|> validate_inclusion(:type, ["Accept", "Reject"])
|> validate_actor_presence()
|> validate_object_presence(allowed_types: ["Follow", "Join"])
|> validate_object_presence(allowed_types: ["Follow", "Join", "Bite"])
|> validate_accept_reject_rights()
end

Expand Down Expand Up @@ -62,5 +62,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
defp validate_actor(%Activity{data: %{"type" => "Join", "object" => joined_event}}, actor) do
%Object{data: %{"actor" => event_author}} = Object.get_cached_by_ap_id(joined_event)
event_author == actor
end
endtor == actor
end

defp validate_actor(%Activity{data: %{"type" => "Bite", "object" => biten_actor}}, actor) do
biten_actor == actor
end
end
41 changes: 41 additions & 0 deletions lib/pleroma/web/activity_pub/object_validators/bite_validator.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.ActivityPub.ObjectValidators.BiteValidator do
use Ecto.Schema

import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations

@primary_key false

embedded_schema do
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
activity_fields()
end
end
end

def cast_data(data) do
%__MODULE__{}
|> cast(data, __schema__(:fields))
end

defp validate_data(cng) do
cng
|> validate_required([:id, :type, :actor, :to, :object])
|> validate_inclusion(:type, ["Bite"])
|> validate_actor_presence()
|> validate_actor_presence(field_name: :object)
end

def cast_and_validate(data) do
data
|> cast_data
|> validate_data
end
end
58 changes: 58 additions & 0 deletions lib/pleroma/web/activity_pub/side_effects.ex
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,56 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
end

# Task this handles
# - Bites
# - Sends a notification
@impl true
def handle(
%{
data: %{
"id" => bite_id,
"type" => "Bite",
"object" => bitten_user,
"actor" => biting_user
}
} = object,
meta
) do
with %User{} = biting <- User.get_cached_by_ap_id(biting_user),
%User{} = bitten <- User.get_cached_by_ap_id(bitten_user),
{:previous_bite, previous_bite} <-
{:previous_bite, Utils.fetch_latest_bite(biting, bitten, object)},
{:reverse_bite, reverse_bite} <-
{:reverse_bite, Utils.fetch_latest_bite(bitten, biting)},
{:can_bite, true, _} <- {:can_bite, can_bite?(previous_bite, reverse_bite), bitten} do
if bitten.local do
{:ok, accept_data, _} = Builder.accept(bitten, object)
{:ok, _activity, _} = Pipeline.common_pipeline(accept_data, local: true)
end

if reverse_bite do
Notification.dismiss(reverse_bite)
end

{:ok, notifications} = Notification.create_notifications(object)

meta
|> add_notifications(notifications)
else
{:can_bite, false, bitten} ->
{:ok, reject_data, _} = Builder.reject(bitten, object)
{:ok, _activity, _} = Pipeline.common_pipeline(reject_data, local: true)
meta

_ ->
meta
end

updated_object = Activity.get_by_ap_id(bite_id)

{:ok, updated_object, meta}
end

# Nothing to do
@impl true
def handle(object, meta) do
Expand Down Expand Up @@ -726,4 +776,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|> stream_notifications()
|> send_streamables()
end

defp can_bite?(nil, _), do: true

defp can_bite?(_, nil), do: false

defp can_bite?(previous_bite, reverse_bite) do
NaiveDateTime.diff(previous_bite.inserted_at, reverse_bite.inserted_at) < 0
end
end
2 changes: 1 addition & 1 deletion lib/pleroma/web/activity_pub/transmogrifier.ex
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => type} = data,
_options
)
when type in ~w{Update Block Follow Accept Reject Join Leave} do
when type in ~w{Update Block Follow Accept Reject Join Leave Bite} do
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
{:ok, activity, _} <-
Pipeline.common_pipeline(data, local: false) do
Expand Down
32 changes: 32 additions & 0 deletions lib/pleroma/web/activity_pub/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1048,4 +1048,36 @@ defmodule Pleroma.Web.ActivityPub.Utils do
{:ok, activity}
end
end

def make_bite_data(biting, bitten, activity_id) do
%{
"type" => "Bite",
"actor" => biting.ap_id,
"to" => [bitten.ap_id],
"object" => bitten.ap_id
}
|> Maps.put_if_present("id", activity_id)
end

def fetch_latest_bite(
%User{ap_id: biting_ap_id},
%{ap_id: bitten_ap_id},
exclude_activity \\ nil
) do
"Bite"
|> Activity.Queries.by_type()
|> where(actor: ^biting_ap_id)
|> maybe_exclude_activity_id(exclude_activity)
|> Activity.Queries.by_object_id(bitten_ap_id)
|> order_by([activity], fragment("? desc nulls last", activity.id))
|> limit(1)
|> Repo.one()
end

defp maybe_exclude_activity_id(query, nil), do: query

defp maybe_exclude_activity_id(query, %Activity{id: activity_id}) do
query
|> where([a], a.id != ^activity_id)
end
end
2 changes: 1 addition & 1 deletion lib/pleroma/web/api_spec.ex
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ defmodule Pleroma.Web.ApiSpec do
"x-tagGroups": [
%{
"name" => "Accounts",
"tags" => ["Account actions", "Retrieve account information", "Scrobbles"]
"tags" => ["Account actions", "Bites", "Retrieve account information", "Scrobbles"]
},
%{
"name" => "Administration",
Expand Down
33 changes: 33 additions & 0 deletions lib/pleroma/web/api_spec/operations/bite_operation.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.ApiSpec.BiteOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.ApiError

@spec open_api_operation(atom) :: Operation.t()
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end

def bite_operation do
%Operation{
tags: ["Bites"],
summary: "Bite",
operationId: "BiteController.bite",
security: [%{"oAuth" => ["write:bites"]}],
description: "Bite the given account",
parameters: [
Operation.parameter(:id, :query, :string, "Bitten account ID")
],
responses: %{
200 => Operation.response("Empty object", "application/json", %Schema{type: :object}),
400 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
}
}
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
"pleroma:event_reminder",
"pleroma:event_update",
"admin.sign_up",
"admin.report"
"admin.report",
"bite"
],
description: """
The type of event that resulted in the notification.
Expand All @@ -237,6 +238,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
- `pleroma:participation_accepted - Your event participation request was accepted
- `admin.sign_up` - Someone signed up (optionally sent to admins)
- `admin.report` - A new report has been filed
- `bite` - Someone bit you
"""
}
end
Expand Down
11 changes: 11 additions & 0 deletions lib/pleroma/web/common_api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -859,4 +859,15 @@ defmodule Pleroma.Web.CommonAPI do
end

defp maybe_cancel_jobs(_), do: {:ok, 0}

def bite(biting, bitten) do
with {:ok, bite_data, _} <- Builder.bite(biting, bitten),
{:ok, activity, _} <- Pipeline.common_pipeline(bite_data, local: true) do
if activity.data["state"] == "reject" do
{:error, :rejected}
else
{:ok, biting, bitten, activity}
end
end
end
end
39 changes: 39 additions & 0 deletions lib/pleroma/web/mastodon_api/controllers/bite_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.MastodonAPI.BiteController do
use Pleroma.Web, :controller

import Pleroma.Web.ControllerHelper, only: [assign_account_by_id: 2, json_response: 3]

alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Plugs.OAuthScopesPlug
# alias Pleroma.Web.Plugs.RateLimiter

plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false)

plug(OAuthScopesPlug, %{scopes: ["write:bite"]} when action == :bite)

# plug(RateLimiter, [name: :relations_actions] when action in @relationship_actions)
# plug(RateLimiter, [name: :app_account_creation] when action == :create)

plug(:assign_account_by_id)

action_fallback(Pleroma.Web.MastodonAPI.FallbackController)

defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.BiteOperation

@doc "POST /api/v1/bite"
def bite(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
{:error, "Can not bite yourself"}
end

def bite(%{assigns: %{user: biting, account: bitten}} = conn, _) do
with {:ok, _, _, _} <- CommonAPI.bite(biting, bitten) do
json_response(conn, :ok, %{})
else
{:error, message} -> json_response(conn, :forbidden, %{error: message})
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
pleroma:participation_accepted
pleroma:event_reminder
pleroma:event_update
bite
}

# GET /api/v1/notifications
Expand Down
Loading

0 comments on commit e110fc5

Please sign in to comment.