diff options
Diffstat (limited to 'lib/pleroma/web/push/impl.ex')
| -rw-r--r-- | lib/pleroma/web/push/impl.ex | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex new file mode 100644 index 0000000..3c5f007 --- /dev/null +++ b/lib/pleroma/web/push/impl.ex @@ -0,0 +1,205 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Push.Impl do + @moduledoc "The module represents implementation push web notification" + + alias Pleroma.Activity + alias Pleroma.Notification + alias Pleroma.Object + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.Metadata.Utils + alias Pleroma.Web.Push.Subscription + + require Logger + import Ecto.Query + + @types ["Create", "Follow", "Announce", "Like", "Move", "EmojiReact", "Update"] + + @doc "Performs sending notifications for user subscriptions" + @spec perform(Notification.t()) :: list(any) | :error | {:error, :unknown_type} + def perform( + %{ + activity: %{data: %{"type" => activity_type}} = activity, + user: %User{id: user_id} + } = notification + ) + when activity_type in @types do + actor = User.get_cached_by_ap_id(notification.activity.data["actor"]) + + mastodon_type = notification.type + gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key) + avatar_url = User.avatar_url(actor) + object = Object.normalize(activity, fetch: false) + user = User.get_cached_by_id(user_id) + direct_conversation_id = Activity.direct_conversation_id(activity, user) + + for subscription <- fetch_subscriptions(user_id), + Subscription.enabled?(subscription, mastodon_type) do + %{ + access_token: subscription.token.token, + notification_id: notification.id, + notification_type: mastodon_type, + icon: avatar_url, + preferred_locale: "en", + pleroma: %{ + activity_id: notification.activity.id, + direct_conversation_id: direct_conversation_id + } + } + |> Map.merge(build_content(notification, actor, object, mastodon_type)) + |> Jason.encode!() + |> push_message(build_sub(subscription), gcm_api_key, subscription) + end + |> (&{:ok, &1}).() + end + + def perform(_) do + Logger.warn("Unknown notification type") + {:error, :unknown_type} + end + + @doc "Push message to web" + def push_message(body, sub, api_key, subscription) do + case WebPushEncryption.send_web_push(body, sub, api_key) do + {:ok, %{status: code}} when code in 400..499 -> + Logger.debug("Removing subscription record") + Repo.delete!(subscription) + :ok + + {:ok, %{status: code}} when code in 200..299 -> + :ok + + {:ok, %{status: code}} -> + Logger.error("Web Push Notification failed with code: #{code}") + :error + + error -> + Logger.error("Web Push Notification failed with #{inspect(error)}") + :error + end + end + + @doc "Gets user subscriptions" + def fetch_subscriptions(user_id) do + Subscription + |> where(user_id: ^user_id) + |> preload(:token) + |> Repo.all() + end + + def build_sub(subscription) do + %{ + keys: %{ + p256dh: subscription.key_p256dh, + auth: subscription.key_auth + }, + endpoint: subscription.endpoint + } + end + + def build_content(notification, actor, object, mastodon_type \\ nil) + + def build_content( + %{ + user: %{notification_settings: %{hide_notification_contents: true}} + } = notification, + _actor, + _object, + mastodon_type + ) do + %{body: format_title(notification, mastodon_type)} + end + + def build_content(notification, actor, object, mastodon_type) do + mastodon_type = mastodon_type || notification.type + + %{ + title: format_title(notification, mastodon_type), + body: format_body(notification, actor, object, mastodon_type) + } + end + + def format_body(activity, actor, object, mastodon_type \\ nil) + + def format_body(_activity, actor, %{data: %{"type" => "ChatMessage"} = data}, _) do + case data["content"] do + nil -> "@#{actor.nickname}: (Attachment)" + content -> "@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}" + end + end + + def format_body( + %{activity: %{data: %{"type" => "Create"}}}, + actor, + %{data: %{"content" => content}}, + _mastodon_type + ) do + "@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}" + end + + def format_body( + %{activity: %{data: %{"type" => "Announce"}}}, + actor, + %{data: %{"content" => content}}, + _mastodon_type + ) do + "@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}" + end + + def format_body( + %{activity: %{data: %{"type" => "EmojiReact", "content" => content}}}, + actor, + _object, + _mastodon_type + ) do + "@#{actor.nickname} reacted with #{content}" + end + + def format_body( + %{activity: %{data: %{"type" => type}}} = notification, + actor, + _object, + mastodon_type + ) + when type in ["Follow", "Like"] do + mastodon_type = mastodon_type || notification.type + + case mastodon_type do + "follow" -> "@#{actor.nickname} has followed you" + "follow_request" -> "@#{actor.nickname} has requested to follow you" + "favourite" -> "@#{actor.nickname} has favorited your post" + end + end + + def format_body( + %{activity: %{data: %{"type" => "Update"}}}, + actor, + _object, + _mastodon_type + ) do + "@#{actor.nickname} edited a status" + end + + def format_title(activity, mastodon_type \\ nil) + + def format_title(%{activity: %{data: %{"directMessage" => true}}}, _mastodon_type) do + "New Direct Message" + end + + def format_title(%{type: type}, mastodon_type) do + case mastodon_type || type do + "mention" -> "New Mention" + "follow" -> "New Follower" + "follow_request" -> "New Follow Request" + "reblog" -> "New Repeat" + "favourite" -> "New Favorite" + "update" -> "New Update" + "pleroma:chat_mention" -> "New Chat Message" + "pleroma:emoji_reaction" -> "New Reaction" + type -> "New #{String.capitalize(type || "event")}" + end + end +end |
