diff --git a/config/config.exs b/config/config.exs
index f7455cf97..2d93cd0dd 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -553,7 +553,8 @@ config :pleroma, Oban,
remote_fetcher: 2,
attachments_cleanup: 5,
new_users_digest: 1,
- mute_expire: 5
+ mute_expire: 5,
+ poll_expiration_notify: 1
],
plugins: [Oban.Plugins.Pruner],
crontab: [
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index dd7a1c824..8ee9f71c1 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -368,7 +368,7 @@ defmodule Pleroma.Notification do
end
def create_notifications(%Activity{data: %{"type" => type}} = activity, options)
- when type in ["Follow", "Like", "Announce", "Move", "EmojiReact", "Flag"] do
+ when type in ["Follow", "Like", "Announce", "Move", "EmojiReact", "Flag", "ClosePoll"] do
do_create_notifications(activity, options)
end
@@ -418,9 +418,11 @@ defmodule Pleroma.Notification do
"EmojiReaction" ->
"pleroma:emoji_reaction"
+ "ClosePoll" ->
+ "poll"
+
"Create" ->
- activity
- |> type_from_activity_object()
+ type_from_activity_object(activity)
t ->
raise "No notification type for activity type #{t}"
@@ -471,7 +473,16 @@ defmodule Pleroma.Notification do
def get_notified_from_activity(activity, local_only \\ true)
def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
- when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReact", "Flag"] do
+ when type in [
+ "Create",
+ "Like",
+ "Announce",
+ "Follow",
+ "Move",
+ "EmojiReact",
+ "Flag",
+ "ClosePoll"
+ ] do
potential_receiver_ap_ids = get_potential_receiver_ap_ids(activity)
potential_receivers =
@@ -511,6 +522,10 @@ defmodule Pleroma.Notification do
User.all_superusers() |> Enum.map(fn user -> user.ap_id end)
end
+ def get_potential_receiver_ap_ids(%{data: %{"type" => "ClosePoll"}}) do
+ []
+ end
+
def get_potential_receiver_ap_ids(activity) do
[]
|> Utils.maybe_notify_to_recipients(activity)
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index e99f6fd83..bb579083c 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -65,6 +65,15 @@ defmodule Pleroma.Web.ActivityPub.Builder do
end
end
+ def close_poll(_activity, object) do
+ {:ok,
+ %{
+ "id" => Utils.generate_activity_id(),
+ "type" => "ClosePoll",
+ "object" => object.data["id"]
+ }, []}
+ end
+
@spec undo(User.t(), Activity.t()) :: {:ok, map(), keyword()}
def undo(actor, object) do
{:ok,
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index bd0a2a8dc..aa1d64323 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -21,6 +21,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.ClosePollValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
@@ -151,6 +152,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
end
end
+ def validate(%{"type" => "ClosePoll"} = object, meta) do
+ with {:ok, object} <-
+ object
+ |> ClosePollValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
def validate(%{"type" => type} = object, meta) when type in ~w[Audio Video] do
with {:ok, object} <-
object
diff --git a/lib/pleroma/web/activity_pub/object_validators/close_poll_validator.ex b/lib/pleroma/web/activity_pub/object_validators/close_poll_validator.ex
new file mode 100644
index 000000000..c3325948e
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/close_poll_validator.ex
@@ -0,0 +1,41 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.ClosePollValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+
+ import Ecto.Changeset
+
+ @primary_key false
+
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:type, :string)
+ field(:object, ObjectValidators.ObjectID)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data()
+ |> validate_data()
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> changeset(data)
+ end
+
+ def changeset(struct, data) do
+ struct
+ |> cast(data, __schema__(:fields))
+ end
+
+ def validate_data(data_cng) do
+ data_cng
+ |> validate_inclusion(:type, ["ClosePoll"])
+ |> validate_required([:id, :type, :object])
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 4d8fb721e..d8ef04a62 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -245,6 +245,14 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
# Tasks this handles:
+ # - Set up notification on close poll
+ def handle(%{data: %{"type" => "ClosePoll"}} = object, meta) do
+ Notification.create_notifications(object)
+
+ {:ok, object, meta}
+ end
+
+ # Tasks this handles:
# - Delete and unpins the create activity
# - Replace object with Tombstone
# - Set up notification
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
index e59254791..4b5dc1261 100644
--- a/lib/pleroma/web/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -264,6 +264,18 @@ defmodule Pleroma.Web.CommonAPI do
end
end
+ # postprocess the close poll
+ #
+ def close_poll(activity_id) do
+ with %Activity{} = activity <- Activity.get_by_id(activity_id),
+ %Object{} = object <- Object.normalize(activity),
+ {:ok, poll, _} <- Builder.close_poll(activity, object),
+ meta <- [local: true, do_not_federate: true],
+ {:ok, activity, _} <- Pipeline.common_pipeline(poll, meta) do
+ {:ok, activity}
+ end
+ end
+
def react_with_emoji(id, user, emoji) do
with %Activity{} = activity <- Activity.get_by_id(id),
object <- Object.normalize(activity),
@@ -411,6 +423,25 @@ defmodule Pleroma.Web.CommonAPI do
end
end
+ def postprocess(%Activity{} = activity) do
+ case Object.normalize(activity) do
+ %Object{data: %{"type" => "Question", "closed" => closed_at}} ->
+ Pleroma.Workers.PollExpirationNotify.enqueue(%{
+ activity_id: activity.id,
+ closed_at:
+ Timex.shift(
+ Timex.parse!(closed_at, "{ISO:Extended:Z}"),
+ minutes: 1
+ )
+ })
+
+ _ ->
+ activity
+ end
+
+ activity
+ end
+
def pin(id, %{ap_id: user_ap_id} = user) do
with %Activity{
actor: ^user_ap_id,
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index 9e3a584f0..374f18602 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -164,7 +164,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn, _) do
params = Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id])
- with {:ok, activity} <- CommonAPI.post(user, params) do
+ with {:ok, activity} <- CommonAPI.post(user, params),
+ _ <- CommonAPI.postprocess(activity) do
try_render(conn, "show.json",
activity: activity,
for: user,
diff --git a/lib/pleroma/workers/poll_expiration_notify.ex b/lib/pleroma/workers/poll_expiration_notify.ex
new file mode 100644
index 000000000..2adf6e940
--- /dev/null
+++ b/lib/pleroma/workers/poll_expiration_notify.ex
@@ -0,0 +1,25 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.PollExpirationNotify do
+ @moduledoc false
+
+ use Oban.Worker, queue: :poll_expiration_notify, max_attempts: 1
+
+ def enqueue(args) do
+ {scheduled_at, args} = Map.pop(args, :closed_at)
+
+ args
+ |> __MODULE__.new(scheduled_at: scheduled_at)
+ |> Oban.insert()
+ end
+
+ @impl true
+ def perform(%Oban.Job{args: %{"activity_id" => activity_id}}) do
+ Pleroma.Web.CommonAPI.close_poll(activity_id)
+ :ok
+ end
+
+ def perform(_), do: :ok
+end