Compare commits
47 commits
Author | SHA1 | Date | |
---|---|---|---|
|
53416190a9 | ||
|
050bc74437 | ||
|
c02e3432d9 | ||
|
f614bf2725 | ||
|
26a91d5c9e | ||
|
d71d52302c | ||
|
11c305b64b | ||
|
14515d8d4a | ||
|
a03f3a9d89 | ||
|
ebfb617b26 | ||
|
0af8e93135 | ||
|
98a64ab145 | ||
|
94d1af2c4c | ||
|
43c5fd5db0 | ||
|
c887dd4f2e | ||
|
ba1ed37edf | ||
|
f1de9bd9ba | ||
|
a4bab7bdfa | ||
|
a7dbca885f | ||
|
9d7c877de0 | ||
|
39a878f530 | ||
|
dcee1b109b | ||
|
9a8373a3f5 | ||
|
ccae7ef824 | ||
|
8504878187 | ||
|
fef4bae006 | ||
|
86dcf273c5 | ||
|
36cb19dbf2 | ||
|
71d08991ea | ||
|
d756607112 | ||
|
367bc9c818 | ||
|
81caf77223 | ||
|
551f92dd50 | ||
|
d9508474b6 | ||
|
f676007b18 | ||
|
63f2d1cbef | ||
|
f91b896731 | ||
|
af90a4e51b | ||
|
5e7be063c7 | ||
|
a1317bf541 | ||
|
a0dd670e68 | ||
|
11d29d27b8 | ||
|
44da806a77 | ||
|
d7c805b0bb | ||
|
c52982e9c5 | ||
|
2e433e106f | ||
|
bfbe4e8dce |
22 changed files with 369 additions and 36 deletions
|
@ -76,6 +76,7 @@ pipeline:
|
||||||
- *clean
|
- *clean
|
||||||
- echo "import Config" > config/prod.secret.exs
|
- echo "import Config" > config/prod.secret.exs
|
||||||
- *setup-hex
|
- *setup-hex
|
||||||
|
- *mix-clean
|
||||||
- *tag-build
|
- *tag-build
|
||||||
- mix deps.get --only prod
|
- mix deps.get --only prod
|
||||||
- mix release --path release
|
- mix release --path release
|
||||||
|
|
|
@ -339,6 +339,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## 2022.08
|
## 2022.08
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Non-finch HTTP adapters. `:tesla, :adapter` is now highly recommended to be set to the default.
|
||||||
|
|
||||||
|
## 2022.08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- extended runtime module support, see config cheatsheet
|
- extended runtime module support, see config cheatsheet
|
||||||
- quote posting; quotes are limited to public posts
|
- quote posting; quotes are limited to public posts
|
||||||
|
|
|
@ -72,6 +72,7 @@ defmodule Pleroma.Notification do
|
||||||
pleroma:report
|
pleroma:report
|
||||||
reblog
|
reblog
|
||||||
poll
|
poll
|
||||||
|
bite
|
||||||
}
|
}
|
||||||
|
|
||||||
def changeset(%Notification{} = notification, attrs) do
|
def changeset(%Notification{} = notification, attrs) do
|
||||||
|
@ -402,7 +403,7 @@ defmodule Pleroma.Notification do
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_notifications(%Activity{data: %{"type" => type}} = activity, options)
|
def create_notifications(%Activity{data: %{"type" => type}} = activity, options)
|
||||||
when type in ["Follow", "Like", "Announce", "Move", "EmojiReact", "Flag", "Update"] do
|
when type in ["Follow", "Like", "Announce", "Move", "EmojiReact", "Flag", "Update", "Bite"] do
|
||||||
do_create_notifications(activity, options)
|
do_create_notifications(activity, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -459,6 +460,9 @@ defmodule Pleroma.Notification do
|
||||||
"Update" ->
|
"Update" ->
|
||||||
"update"
|
"update"
|
||||||
|
|
||||||
|
"Bite" ->
|
||||||
|
"bite"
|
||||||
|
|
||||||
t ->
|
t ->
|
||||||
raise "No notification type for activity type #{t}"
|
raise "No notification type for activity type #{t}"
|
||||||
end
|
end
|
||||||
|
@ -532,7 +536,8 @@ defmodule Pleroma.Notification do
|
||||||
"Move",
|
"Move",
|
||||||
"EmojiReact",
|
"EmojiReact",
|
||||||
"Flag",
|
"Flag",
|
||||||
"Update"
|
"Update",
|
||||||
|
"Bite"
|
||||||
] do
|
] do
|
||||||
potential_receiver_ap_ids = get_potential_receiver_ap_ids(activity)
|
potential_receiver_ap_ids = get_potential_receiver_ap_ids(activity)
|
||||||
|
|
||||||
|
|
|
@ -406,4 +406,15 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
||||||
defp pinned_url(nickname) when is_binary(nickname) do
|
defp pinned_url(nickname) when is_binary(nickname) do
|
||||||
url(~p[/users/#{nickname}/collections/featured])
|
url(~p[/users/#{nickname}/collections/featured])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def bite(%User{} = biting, %User{} = bitten) do
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
"id" => Utils.generate_activity_id(),
|
||||||
|
"target" => bitten.ap_id,
|
||||||
|
"actor" => biting.ap_id,
|
||||||
|
"type" => "Bite",
|
||||||
|
"to" => [bitten.ap_id]
|
||||||
|
}, []}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,6 +22,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.BiteValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
|
||||||
|
@ -160,7 +161,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
|
|
||||||
def validate(%{"type" => type} = object, meta)
|
def validate(%{"type" => type} = object, meta)
|
||||||
when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
|
when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
|
||||||
Answer] do
|
Answer Bite] do
|
||||||
validator =
|
validator =
|
||||||
case type do
|
case type do
|
||||||
"Accept" -> AcceptRejectValidator
|
"Accept" -> AcceptRejectValidator
|
||||||
|
@ -171,6 +172,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
"EmojiReact" -> EmojiReactValidator
|
"EmojiReact" -> EmojiReactValidator
|
||||||
"Announce" -> AnnounceValidator
|
"Announce" -> AnnounceValidator
|
||||||
"Answer" -> AnswerValidator
|
"Answer" -> AnswerValidator
|
||||||
|
"Bite" -> BiteValidator
|
||||||
end
|
end
|
||||||
|
|
||||||
with {:ok, object} <-
|
with {:ok, object} <-
|
||||||
|
|
|
@ -33,7 +33,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
|
||||||
|> validate_required([:type, :actor, :to, :cc, :object])
|
|> validate_required([:type, :actor, :to, :cc, :object])
|
||||||
|> validate_inclusion(:type, ["Accept", "Reject"])
|
|> validate_inclusion(:type, ["Accept", "Reject"])
|
||||||
|> validate_actor_presence()
|
|> validate_actor_presence()
|
||||||
|> validate_object_presence(allowed_types: ["Follow"])
|
|> validate_object_presence(allowed_types: ["Follow", "Bite"])
|
||||||
|> validate_accept_reject_rights()
|
|> validate_accept_reject_rights()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -46,8 +46,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
|
||||||
|
|
||||||
def validate_accept_reject_rights(cng) do
|
def validate_accept_reject_rights(cng) do
|
||||||
with object_id when is_binary(object_id) <- get_field(cng, :object),
|
with object_id when is_binary(object_id) <- get_field(cng, :object),
|
||||||
%Activity{data: %{"object" => followed_actor}} <- Activity.get_by_ap_id(object_id),
|
%Activity{} = activity <- Activity.get_by_ap_id(object_id),
|
||||||
true <- followed_actor == get_field(cng, :actor) do
|
true <- validate_actor(activity, get_field(cng, :actor)) do
|
||||||
cng
|
cng
|
||||||
else
|
else
|
||||||
_e ->
|
_e ->
|
||||||
|
@ -56,6 +56,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp validate_actor(%Activity{data: %{"type" => "Follow", "object" => followed_actor}}, actor) do
|
||||||
|
followed_actor == actor
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_actor(%Activity{data: %{"type" => "Bite", "target" => biten_actor}}, actor) do
|
||||||
|
biten_actor == actor
|
||||||
|
end
|
||||||
|
|
||||||
defp maybe_fetch_object(%{"object" => %{} = object} = activity) do
|
defp maybe_fetch_object(%{"object" => %{} = object} = activity) do
|
||||||
# If we don't have an ID, we may have to fetch the object
|
# If we don't have an ID, we may have to fetch the object
|
||||||
if Map.has_key?(object, "id") do
|
if Map.has_key?(object, "id") do
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
# 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
|
||||||
|
|
||||||
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
field(:target, ObjectValidators.ObjectID)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_data(data) do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> cast(data |> fix_object(), __schema__(:fields))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fix_object(data) do
|
||||||
|
Map.put(data, "object", data["target"])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_data(cng) do
|
||||||
|
cng
|
||||||
|
|> validate_required([:id, :type, :actor, :to, :target])
|
||||||
|
|> validate_inclusion(:type, ["Bite"])
|
||||||
|
|> validate_actor_presence()
|
||||||
|
|> validate_actor_presence(field_name: :target)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_validate(data) do
|
||||||
|
data
|
||||||
|
|> cast_data
|
||||||
|
|> validate_data
|
||||||
|
end
|
||||||
|
end
|
|
@ -40,23 +40,16 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
# - Sends a notification
|
# - Sends a notification
|
||||||
@impl true
|
@impl true
|
||||||
def handle(
|
def handle(
|
||||||
%{
|
%{data: %{"actor" => actor, "type" => "Accept", "object" => activity_id}} = object,
|
||||||
data: %{
|
|
||||||
"actor" => actor,
|
|
||||||
"type" => "Accept",
|
|
||||||
"object" => follow_activity_id
|
|
||||||
}
|
|
||||||
} = object,
|
|
||||||
meta
|
meta
|
||||||
) do
|
) do
|
||||||
with %Activity{actor: follower_id} = follow_activity <-
|
with %Activity{} = activity <-
|
||||||
Activity.get_by_ap_id(follow_activity_id),
|
Activity.get_by_ap_id(activity_id) do
|
||||||
%User{} = followed <- User.get_cached_by_ap_id(actor),
|
handle_accepted(activity, actor)
|
||||||
%User{} = follower <- User.get_cached_by_ap_id(follower_id),
|
|
||||||
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
|
if activity.data["type"] === "Join" do
|
||||||
{:ok, _follower, followed} <-
|
Notification.create_notifications(object)
|
||||||
FollowingRelationship.update(follower, followed, :follow_accept) do
|
end
|
||||||
Notification.update_notification_type(followed, follow_activity)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
|
@ -72,18 +65,14 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
data: %{
|
data: %{
|
||||||
"actor" => actor,
|
"actor" => actor,
|
||||||
"type" => "Reject",
|
"type" => "Reject",
|
||||||
"object" => follow_activity_id
|
"object" => activity_id
|
||||||
}
|
}
|
||||||
} = object,
|
} = object,
|
||||||
meta
|
meta
|
||||||
) do
|
) do
|
||||||
with %Activity{actor: follower_id} = follow_activity <-
|
with %Activity{} = activity <-
|
||||||
Activity.get_by_ap_id(follow_activity_id),
|
Activity.get_by_ap_id(activity_id) do
|
||||||
%User{} = followed <- User.get_cached_by_ap_id(actor),
|
handle_rejected(activity, actor)
|
||||||
%User{} = follower <- User.get_cached_by_ap_id(follower_id),
|
|
||||||
{:ok, _follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject") do
|
|
||||||
FollowingRelationship.update(follower, followed, :follow_reject)
|
|
||||||
Notification.dismiss(follow_activity)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
|
@ -397,12 +386,93 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Task this handles
|
||||||
|
# - Bites
|
||||||
|
# - Sends a notification
|
||||||
|
@impl true
|
||||||
|
def handle(
|
||||||
|
%{
|
||||||
|
data: %{
|
||||||
|
"id" => bite_id,
|
||||||
|
"type" => "Bite",
|
||||||
|
"target" => 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
|
# Nothing to do
|
||||||
@impl true
|
@impl true
|
||||||
def handle(object, meta) do
|
def handle(object, meta) do
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp handle_accepted(
|
||||||
|
%Activity{actor: follower_id, data: %{"type" => "Follow"}} = follow_activity,
|
||||||
|
actor
|
||||||
|
) do
|
||||||
|
with %User{} = followed <- User.get_cached_by_ap_id(actor),
|
||||||
|
%User{} = follower <- User.get_cached_by_ap_id(follower_id),
|
||||||
|
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
|
||||||
|
{:ok, _follower, followed} <-
|
||||||
|
FollowingRelationship.update(follower, followed, :follow_accept) do
|
||||||
|
Notification.update_notification_type(followed, follow_activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_accepted(_, _), do: nil
|
||||||
|
|
||||||
|
defp handle_rejected(
|
||||||
|
%Activity{actor: follower_id, data: %{"type" => "Follow"}} = follow_activity,
|
||||||
|
actor
|
||||||
|
) do
|
||||||
|
with %User{} = followed <- User.get_cached_by_ap_id(actor),
|
||||||
|
%User{} = follower <- User.get_cached_by_ap_id(follower_id),
|
||||||
|
{:ok, _follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject") do
|
||||||
|
FollowingRelationship.update(follower, followed, :follow_reject)
|
||||||
|
Notification.dismiss(follow_activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_rejected(%Activity{data: %{"type" => "Bite"}} = bite_activity, _actor) do
|
||||||
|
Notification.dismiss(bite_activity)
|
||||||
|
end
|
||||||
|
|
||||||
defp handle_update_user(
|
defp handle_update_user(
|
||||||
%{data: %{"type" => "Update", "object" => updated_object}} = object,
|
%{data: %{"type" => "Update", "object" => updated_object}} = object,
|
||||||
meta
|
meta
|
||||||
|
@ -587,4 +657,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
|> send_notifications()
|
|> send_notifications()
|
||||||
|> send_streamables()
|
|> send_streamables()
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -519,7 +519,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp handle_incoming_normalised(%{"type" => type} = data, _options)
|
defp handle_incoming_normalised(%{"type" => type} = data, _options)
|
||||||
when type in ~w{Like EmojiReact Announce Add Remove} do
|
when type in ~w{Like EmojiReact Announce Add Remove Bite} do
|
||||||
with :ok <- ObjectValidator.fetch_actor_and_object(data),
|
with :ok <- ObjectValidator.fetch_actor_and_object(data),
|
||||||
{:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do
|
{:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
@ -533,7 +533,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
%{"type" => type} = data,
|
%{"type" => type} = data,
|
||||||
_options
|
_options
|
||||||
)
|
)
|
||||||
when type in ~w{Update Block Follow Accept Reject} do
|
when type in ~w{Update Block Follow Accept Reject Bite} do
|
||||||
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
||||||
{:ok, activity, _} <-
|
{:ok, activity, _} <-
|
||||||
Pipeline.common_pipeline(data, local: false) do
|
Pipeline.common_pipeline(data, local: false) do
|
||||||
|
|
|
@ -888,4 +888,36 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
||||||
|> where([a, object: o], fragment("(?)->>'type' = 'Answer'", o.data))
|
|> where([a, object: o], fragment("(?)->>'type' = 'Answer'", o.data))
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def make_bite_data(biting, bitten, activity_id) do
|
||||||
|
%{
|
||||||
|
"type" => "Bite",
|
||||||
|
"actor" => biting.ap_id,
|
||||||
|
"to" => [bitten.ap_id],
|
||||||
|
"target" => 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
|
end
|
||||||
|
|
|
@ -79,7 +79,7 @@ defmodule Pleroma.Web.ApiSpec do
|
||||||
"x-tagGroups": [
|
"x-tagGroups": [
|
||||||
%{
|
%{
|
||||||
"name" => "Accounts",
|
"name" => "Accounts",
|
||||||
"tags" => ["Account actions", "Retrieve account information"]
|
"tags" => ["Account actions", "Bites", "Retrieve account information"]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
"name" => "Administration",
|
"name" => "Administration",
|
||||||
|
|
33
lib/pleroma/web/api_spec/operations/bite_operation.ex
Normal file
33
lib/pleroma/web/api_spec/operations/bite_operation.ex
Normal file
|
@ -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
|
|
@ -177,7 +177,8 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
|
||||||
"pleroma:report",
|
"pleroma:report",
|
||||||
"move",
|
"move",
|
||||||
"follow_request",
|
"follow_request",
|
||||||
"poll"
|
"poll",
|
||||||
|
"bite"
|
||||||
],
|
],
|
||||||
description: """
|
description: """
|
||||||
The type of event that resulted in the notification.
|
The type of event that resulted in the notification.
|
||||||
|
@ -190,6 +191,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
|
||||||
- `move` - Someone moved their account
|
- `move` - Someone moved their account
|
||||||
- `pleroma:emoji_reaction` - Someone reacted with emoji to your status
|
- `pleroma:emoji_reaction` - Someone reacted with emoji to your status
|
||||||
- `pleroma:report` - Someone was reported
|
- `pleroma:report` - Someone was reported
|
||||||
|
- `bite` - Someone bit you
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -578,4 +578,11 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def bite(biting, bitten) do
|
||||||
|
with {:ok, bite_data, _} <- Builder.bite(biting, bitten),
|
||||||
|
{:ok, activity, _} <- Pipeline.common_pipeline(bite_data, local: true) do
|
||||||
|
{:ok, biting, bitten, activity}
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
39
lib/pleroma/web/mastodon_api/controllers/bite_controller.ex
Normal file
39
lib/pleroma/web/mastodon_api/controllers/bite_controller.ex
Normal file
|
@ -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
|
|
@ -52,6 +52,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
|
||||||
pleroma:emoji_reaction
|
pleroma:emoji_reaction
|
||||||
poll
|
poll
|
||||||
update
|
update
|
||||||
|
bite
|
||||||
}
|
}
|
||||||
def index(%{assigns: %{user: user}} = conn, params) do
|
def index(%{assigns: %{user: user}} = conn, params) do
|
||||||
params =
|
params =
|
||||||
|
|
|
@ -90,7 +90,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
||||||
"akkoma:machine_translation"
|
"akkoma:machine_translation"
|
||||||
end,
|
end,
|
||||||
"custom_emoji_reactions",
|
"custom_emoji_reactions",
|
||||||
"pleroma:get:main/ostatus"
|
"pleroma:get:main/ostatus",
|
||||||
|
"pleroma:bites"
|
||||||
]
|
]
|
||||||
|> Enum.filter(& &1)
|
|> Enum.filter(& &1)
|
||||||
end
|
end
|
||||||
|
|
|
@ -128,7 +128,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|
||||||
"pleroma:report" ->
|
"pleroma:report" ->
|
||||||
put_report(response, activity)
|
put_report(response, activity)
|
||||||
|
|
||||||
type when type in ["follow", "follow_request"] ->
|
type when type in ["follow", "follow_request", "bite"] ->
|
||||||
response
|
response
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -80,6 +80,10 @@ defmodule Pleroma.Web.Nodeinfo.Nodeinfo do
|
||||||
bubble: !Config.restrict_unauthenticated_access?(:timelines, :bubble)
|
bubble: !Config.restrict_unauthenticated_access?(:timelines, :bubble)
|
||||||
},
|
},
|
||||||
federatedTimelineAvailable: Config.get([:instance, :federated_timeline_available], true)
|
federatedTimelineAvailable: Config.get([:instance, :federated_timeline_available], true)
|
||||||
|
},
|
||||||
|
operations: %{
|
||||||
|
"com.shinolabs.api.bite": ["1.0.0"],
|
||||||
|
"jetzt.mia.ns.activitypub.accept.bite": ["1.0.0"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -631,6 +631,8 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/followed_tags", TagController, :show_followed)
|
get("/followed_tags", TagController, :show_followed)
|
||||||
|
|
||||||
get("/preferences", AccountController, :preferences)
|
get("/preferences", AccountController, :preferences)
|
||||||
|
|
||||||
|
post("/bite", BiteController, :bite)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/web", Pleroma.Web do
|
scope "/api/web", Pleroma.Web do
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.AddBiteToNotificationsEnum do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
@disable_ddl_transaction true
|
||||||
|
|
||||||
|
def up do
|
||||||
|
"""
|
||||||
|
alter type notification_type add value 'bite'
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
end
|
||||||
|
|
||||||
|
# 20220605185734_add_update_to_notifications_enum.exs
|
||||||
|
def down do
|
||||||
|
alter table(:notifications) do
|
||||||
|
modify(:type, :string)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
delete from notifications where type = 'bite'
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
|
||||||
|
"""
|
||||||
|
drop type if exists notification_type
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
|
||||||
|
"""
|
||||||
|
create type notification_type as enum (
|
||||||
|
'follow',
|
||||||
|
'follow_request',
|
||||||
|
'mention',
|
||||||
|
'move',
|
||||||
|
'pleroma:emoji_reaction',
|
||||||
|
'pleroma:chat_mention',
|
||||||
|
'reblog',
|
||||||
|
'favourite',
|
||||||
|
'pleroma:report',
|
||||||
|
'poll',
|
||||||
|
'update'
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
|
||||||
|
"""
|
||||||
|
alter table notifications
|
||||||
|
alter column type type notification_type using (type::notification_type)
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
end
|
||||||
|
end
|
|
@ -54,7 +54,8 @@
|
||||||
"backgroundUrl": {
|
"backgroundUrl": {
|
||||||
"@id": "sharkey:backgroundUrl",
|
"@id": "sharkey:backgroundUrl",
|
||||||
"@type": "@id"
|
"@type": "@id"
|
||||||
}
|
},
|
||||||
|
"Bite": "https://ns.mia.jetzt/as#Bite"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue