impl.ex 3.79 KB
Newer Older
Maksim's avatar
Maksim committed
1 2 3 4 5 6 7 8
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 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
9
  alias Pleroma.Notification
Maksim's avatar
Maksim committed
10
  alias Pleroma.Object
11 12
  alias Pleroma.Repo
  alias Pleroma.User
Maksim's avatar
Maksim committed
13
  alias Pleroma.Web.Metadata.Utils
14
  alias Pleroma.Web.Push.Subscription
Maksim's avatar
Maksim committed
15 16 17 18 19 20 21

  require Logger
  import Ecto.Query

  @types ["Create", "Follow", "Announce", "Like"]

  @doc "Performs sending notifications for user subscriptions"
rinpatch's avatar
rinpatch committed
22
  @spec perform(Notification.t()) :: list(any) | :error
23
  def perform(
24 25 26 27
        %{
          activity: %{data: %{"type" => activity_type}, id: activity_id} = activity,
          user_id: user_id
        } = notif
lain's avatar
lain committed
28
      )
Maksim's avatar
Maksim committed
29 30 31 32 33 34
      when activity_type in @types do
    actor = User.get_cached_by_ap_id(notif.activity.data["actor"])

    type = Activity.mastodon_notification_type(notif.activity)
    gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key)
    avatar_url = User.avatar_url(actor)
35
    object = Object.normalize(activity)
Maksim's avatar
Maksim committed
36 37 38 39 40 41

    for subscription <- fetch_subsriptions(user_id),
        get_in(subscription.data, ["alerts", type]) do
      %{
        title: format_title(notif),
        access_token: subscription.token.token,
42
        body: format_body(notif, actor, object),
Maksim's avatar
Maksim committed
43 44 45
        notification_id: notif.id,
        notification_type: type,
        icon: avatar_url,
46 47 48 49
        preferred_locale: "en",
        pleroma: %{
          activity_id: activity_id
        }
Maksim's avatar
Maksim committed
50 51 52 53 54 55
      }
      |> Jason.encode!()
      |> push_message(build_sub(subscription), gcm_api_key, subscription)
    end
  end

56
  def perform(_) do
Maksim's avatar
Maksim committed
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
    Logger.warn("Unknown notification type")
    :error
  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: code}} when 400 <= code and code < 500 ->
        Logger.debug("Removing subscription record")
        Repo.delete!(subscription)
        :ok

      {:ok, %{status_code: code}} when 200 <= code and code < 300 ->
        :ok

      {:ok, %{status_code: code}} ->
        Logger.error("Web Push Notification failed with code: #{code}")
        :error

      _ ->
        Logger.error("Web Push Notification failed with unknown error")
        :error
    end
  end

  @doc "Gets user subscriptions"
  def fetch_subsriptions(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 format_body(
101 102 103
        %{activity: %{data: %{"type" => "Create"}}},
        actor,
        %{data: %{"content" => content}}
Maksim's avatar
Maksim committed
104 105 106 107 108
      ) do
    "@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}"
  end

  def format_body(
109 110 111
        %{activity: %{data: %{"type" => "Announce"}}},
        actor,
        %{data: %{"content" => content}}
Maksim's avatar
Maksim committed
112 113 114 115 116 117
      ) do
    "@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}"
  end

  def format_body(
        %{activity: %{data: %{"type" => type}}},
118 119
        actor,
        _object
Maksim's avatar
Maksim committed
120 121 122 123 124 125 126 127
      )
      when type in ["Follow", "Like"] do
    case type do
      "Follow" -> "@#{actor.nickname} has followed you"
      "Like" -> "@#{actor.nickname} has favorited your post"
    end
  end

128 129 130 131
  def format_title(%{activity: %{data: %{"directMessage" => true}}}) do
    "New Direct Message"
  end

Maksim's avatar
Maksim committed
132 133 134 135 136 137 138 139 140
  def format_title(%{activity: %{data: %{"type" => type}}}) do
    case type do
      "Create" -> "New Mention"
      "Follow" -> "New Follower"
      "Announce" -> "New Repeat"
      "Like" -> "New Favorite"
    end
  end
end