forked from mirrors/akkoma
Add support for activity expiration to common and Masto API
The "expires_at" parameter accepts an ISO8601-formatted date which defines when the activity will expire. At this point the API will not give you any feedback about if your post will expire or not.
This commit is contained in:
parent
378f5f0fbe
commit
704960b3c1
|
@ -79,6 +79,7 @@ Additional parameters can be added to the JSON body/Form data:
|
||||||
- `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint.
|
- `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint.
|
||||||
- `to`: A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply.
|
- `to`: A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply.
|
||||||
- `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted` or `public`) it can be used to address a List by setting it to `list:LIST_ID`.
|
- `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted` or `public`) it can be used to address a List by setting it to `list:LIST_ID`.
|
||||||
|
- `expires_on`: datetime (iso8601), sets when the posted activity should expire. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated.
|
||||||
|
|
||||||
## PATCH `/api/v1/update_credentials`
|
## PATCH `/api/v1/update_credentials`
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.ActivityExpiration do
|
||||||
alias Pleroma.FlakeId
|
alias Pleroma.FlakeId
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@type t :: %__MODULE__{}
|
||||||
|
@ -19,6 +20,24 @@ defmodule Pleroma.ActivityExpiration do
|
||||||
field(:scheduled_at, :naive_datetime)
|
field(:scheduled_at, :naive_datetime)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def changeset(%ActivityExpiration{} = expiration, attrs) do
|
||||||
|
expiration
|
||||||
|
|> cast(attrs, [:scheduled_at])
|
||||||
|
|> validate_required([:scheduled_at])
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_by_activity_id(activity_id) do
|
||||||
|
ActivityExpiration
|
||||||
|
|> where([exp], exp.activity_id == ^activity_id)
|
||||||
|
|> Repo.one()
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(%Activity{} = activity, scheduled_at) do
|
||||||
|
%ActivityExpiration{activity_id: activity.id}
|
||||||
|
|> changeset(%{scheduled_at: scheduled_at})
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
|
||||||
def due_expirations(offset \\ 0) do
|
def due_expirations(offset \\ 0) do
|
||||||
naive_datetime =
|
naive_datetime =
|
||||||
NaiveDateTime.utc_now()
|
NaiveDateTime.utc_now()
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.CommonAPI do
|
defmodule Pleroma.Web.CommonAPI do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.ActivityExpiration
|
||||||
alias Pleroma.Formatter
|
alias Pleroma.Formatter
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.ThreadMute
|
alias Pleroma.ThreadMute
|
||||||
|
@ -218,6 +219,7 @@ def post(user, %{"status" => status} = data) do
|
||||||
context <- make_context(in_reply_to),
|
context <- make_context(in_reply_to),
|
||||||
cw <- data["spoiler_text"] || "",
|
cw <- data["spoiler_text"] || "",
|
||||||
sensitive <- data["sensitive"] || Enum.member?(tags, {"#nsfw", "nsfw"}),
|
sensitive <- data["sensitive"] || Enum.member?(tags, {"#nsfw", "nsfw"}),
|
||||||
|
{:ok, expires_at} <- Ecto.Type.cast(:naive_datetime, data["expires_at"]),
|
||||||
full_payload <- String.trim(status <> cw),
|
full_payload <- String.trim(status <> cw),
|
||||||
:ok <- validate_character_limit(full_payload, attachments, limit),
|
:ok <- validate_character_limit(full_payload, attachments, limit),
|
||||||
object <-
|
object <-
|
||||||
|
@ -243,15 +245,24 @@ def post(user, %{"status" => status} = data) do
|
||||||
preview? = Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false
|
preview? = Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false
|
||||||
direct? = visibility == "direct"
|
direct? = visibility == "direct"
|
||||||
|
|
||||||
%{
|
result =
|
||||||
to: to,
|
%{
|
||||||
actor: user,
|
to: to,
|
||||||
context: context,
|
actor: user,
|
||||||
object: object,
|
context: context,
|
||||||
additional: %{"cc" => cc, "directMessage" => direct?}
|
object: object,
|
||||||
}
|
additional: %{"cc" => cc, "directMessage" => direct?}
|
||||||
|> maybe_add_list_data(user, visibility)
|
}
|
||||||
|> ActivityPub.create(preview?)
|
|> maybe_add_list_data(user, visibility)
|
||||||
|
|> ActivityPub.create(preview?)
|
||||||
|
|
||||||
|
if expires_at do
|
||||||
|
with {:ok, activity} <- result do
|
||||||
|
ActivityExpiration.create(activity, expires_at)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
result
|
||||||
else
|
else
|
||||||
{:private_to_public, true} ->
|
{:private_to_public, true} ->
|
||||||
{:error, dgettext("errors", "The message visibility must be direct")}
|
{:error, dgettext("errors", "The message visibility must be direct")}
|
||||||
|
|
|
@ -143,12 +143,14 @@ def note_activity_factory(attrs \\ %{}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp expiration_offset_by_minutes(attrs, minutes) do
|
defp expiration_offset_by_minutes(attrs, minutes) do
|
||||||
|
scheduled_at =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(minutes), :millisecond)
|
||||||
|
|> NaiveDateTime.truncate(:second)
|
||||||
|
|
||||||
%Pleroma.ActivityExpiration{}
|
%Pleroma.ActivityExpiration{}
|
||||||
|> Map.merge(attrs)
|
|> Map.merge(attrs)
|
||||||
|> Map.put(
|
|> Map.put(:scheduled_at, scheduled_at)
|
||||||
:scheduled_at,
|
|
||||||
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(minutes), :millisecond)
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def expiration_in_the_past_factory(attrs \\ %{}) do
|
def expiration_in_the_past_factory(attrs \\ %{}) do
|
||||||
|
|
|
@ -160,6 +160,23 @@ test "it returns error when character limit is exceeded" do
|
||||||
|
|
||||||
Pleroma.Config.put([:instance, :limit], limit)
|
Pleroma.Config.put([:instance, :limit], limit)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it can handle activities that expire" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
expires_at =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.truncate(:second)
|
||||||
|
|> NaiveDateTime.add(1_000_000, :second)
|
||||||
|
|
||||||
|
expires_at_iso8601 = expires_at |> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
assert {:ok, activity} =
|
||||||
|
CommonAPI.post(user, %{"status" => "chai", "expires_at" => expires_at_iso8601})
|
||||||
|
|
||||||
|
assert expiration = Pleroma.ActivityExpiration.get_by_activity_id(activity.id)
|
||||||
|
assert expiration.scheduled_at == expires_at
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "reactions" do
|
describe "reactions" do
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|
||||||
|
|
||||||
alias Ecto.Changeset
|
alias Ecto.Changeset
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.ActivityExpiration
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
@ -151,6 +152,24 @@ test "posting a status", %{conn: conn} do
|
||||||
|
|
||||||
assert %{"id" => third_id} = json_response(conn_three, 200)
|
assert %{"id" => third_id} = json_response(conn_three, 200)
|
||||||
refute id == third_id
|
refute id == third_id
|
||||||
|
|
||||||
|
# An activity that will expire:
|
||||||
|
expires_at =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(120), :millisecond)
|
||||||
|
|> NaiveDateTime.truncate(:second)
|
||||||
|
|
||||||
|
conn_four =
|
||||||
|
conn
|
||||||
|
|> post("api/v1/statuses", %{
|
||||||
|
"status" => "oolong",
|
||||||
|
"expires_at" => expires_at
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"id" => fourth_id} = json_response(conn_four, 200)
|
||||||
|
assert activity = Activity.get_by_id(fourth_id)
|
||||||
|
assert expiration = ActivityExpiration.get_by_activity_id(fourth_id)
|
||||||
|
assert expiration.scheduled_at == expires_at
|
||||||
end
|
end
|
||||||
|
|
||||||
test "replying to a status", %{conn: conn} do
|
test "replying to a status", %{conn: conn} do
|
||||||
|
|
Loading…
Reference in a new issue