logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://anongit.hacktivis.me/git/pleroma.git/

impl.ex (6672B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Web.Push.Impl do
  5. @moduledoc "The module represents implementation push web notification"
  6. alias Pleroma.Activity
  7. alias Pleroma.Notification
  8. alias Pleroma.Object
  9. alias Pleroma.Repo
  10. alias Pleroma.User
  11. alias Pleroma.Web.Metadata.Utils
  12. alias Pleroma.Web.Push.Subscription
  13. require Logger
  14. import Ecto.Query
  15. @body_chars 140
  16. @types ["Create", "Follow", "Announce", "Like", "Move", "EmojiReact", "Update"]
  17. @doc "Builds webpush notification payloads for the subscriptions enabled by the receiving user"
  18. @spec build(Notification.t()) ::
  19. list(%{content: map(), subscription: Subscription.t()}) | []
  20. def build(
  21. %{
  22. activity: %{data: %{"type" => activity_type}} = activity,
  23. user_id: user_id
  24. } = notification
  25. )
  26. when activity_type in @types do
  27. notification_actor = User.get_cached_by_ap_id(notification.activity.data["actor"])
  28. avatar_url = User.avatar_url(notification_actor)
  29. object = Object.normalize(activity, fetch: false)
  30. user = User.get_cached_by_id(user_id)
  31. direct_conversation_id = Activity.direct_conversation_id(activity, user)
  32. subscriptions = fetch_subscriptions(user_id)
  33. subscriptions
  34. |> Enum.filter(&Subscription.enabled?(&1, notification.type))
  35. |> Enum.map(fn subscription ->
  36. payload =
  37. %{
  38. access_token: subscription.token.token,
  39. notification_id: notification.id,
  40. notification_type: notification.type,
  41. icon: avatar_url,
  42. preferred_locale: "en",
  43. pleroma: %{
  44. activity_id: notification.activity.id,
  45. direct_conversation_id: direct_conversation_id
  46. }
  47. }
  48. |> Map.merge(build_content(notification, notification_actor, object))
  49. |> Jason.encode!()
  50. %{payload: payload, subscription: subscription}
  51. end)
  52. end
  53. def build(notif) do
  54. Logger.warning("WebPush: unknown activity type: #{inspect(notif)}")
  55. []
  56. end
  57. @doc "Deliver push notification to the provided webpush subscription"
  58. @spec deliver(%{payload: String.t(), subscription: Subscription.t()}) :: :ok | :error
  59. def deliver(%{payload: payload, subscription: subscription}) do
  60. gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key)
  61. formatted_subscription = build_sub(subscription)
  62. case WebPushEncryption.send_web_push(payload, formatted_subscription, gcm_api_key) do
  63. {:ok, %{status: code}} when code in 200..299 ->
  64. :ok
  65. {:ok, %{status: code}} when code in 400..499 ->
  66. Logger.debug("Removing subscription record")
  67. Repo.delete!(subscription)
  68. :ok
  69. {:ok, %{status: code}} ->
  70. Logger.error("Web Push Notification failed with code: #{code}")
  71. :error
  72. error ->
  73. Logger.error("Web Push Notification failed with #{inspect(error)}")
  74. :error
  75. end
  76. end
  77. @doc "Gets user subscriptions"
  78. def fetch_subscriptions(user_id) do
  79. Subscription
  80. |> where(user_id: ^user_id)
  81. |> preload(:token)
  82. |> Repo.all()
  83. end
  84. def build_sub(subscription) do
  85. %{
  86. keys: %{
  87. p256dh: subscription.key_p256dh,
  88. auth: subscription.key_auth
  89. },
  90. endpoint: subscription.endpoint
  91. }
  92. end
  93. def build_content(
  94. %{
  95. user: %{notification_settings: %{hide_notification_contents: true}}
  96. } = notification,
  97. _user,
  98. _object
  99. ) do
  100. %{body: format_title(notification)}
  101. end
  102. def build_content(notification, user, object) do
  103. %{
  104. title: format_title(notification),
  105. body: format_body(notification, user, object)
  106. }
  107. end
  108. @spec format_body(Notification.t(), User.t(), Object.t()) :: String.t()
  109. def format_body(_notification, user, %{data: %{"type" => "ChatMessage"} = object}) do
  110. case object["content"] do
  111. nil -> "@#{user.nickname}: (Attachment)"
  112. content -> "@#{user.nickname}: #{Utils.scrub_html_and_truncate(content, @body_chars)}"
  113. end
  114. end
  115. def format_body(
  116. %{type: "poll"} = _notification,
  117. _user,
  118. %{data: %{"content" => content} = data} = _object
  119. ) do
  120. options = Map.get(data, "anyOf") || Map.get(data, "oneOf")
  121. content_text = content <> "\n"
  122. options_text = Enum.map_join(options, "\n", fn x -> "○ #{x["name"]}" end)
  123. [content_text, options_text]
  124. |> Enum.join("\n")
  125. |> Utils.scrub_html_and_truncate(@body_chars)
  126. end
  127. def format_body(
  128. %{activity: %{data: %{"type" => "Create"}}},
  129. user,
  130. %{data: %{"content" => content}}
  131. ) do
  132. "@#{user.nickname}: #{Utils.scrub_html_and_truncate(content, @body_chars)}"
  133. end
  134. def format_body(
  135. %{activity: %{data: %{"type" => "Announce"}}},
  136. user,
  137. %{data: %{"content" => content}}
  138. ) do
  139. "@#{user.nickname} repeated: #{Utils.scrub_html_and_truncate(content, @body_chars)}"
  140. end
  141. def format_body(
  142. %{activity: %{data: %{"type" => "EmojiReact", "content" => content}}},
  143. user,
  144. _object
  145. ) do
  146. "@#{user.nickname} reacted with #{content}"
  147. end
  148. def format_body(
  149. %{activity: %{data: %{"type" => type}}} = notification,
  150. user,
  151. _object
  152. )
  153. when type in ["Follow", "Like"] do
  154. case notification.type do
  155. "follow" -> "@#{user.nickname} has followed you"
  156. "follow_request" -> "@#{user.nickname} has requested to follow you"
  157. "favourite" -> "@#{user.nickname} has favorited your post"
  158. end
  159. end
  160. def format_body(
  161. %{activity: %{data: %{"type" => "Update"}}},
  162. user,
  163. _object
  164. ) do
  165. "@#{user.nickname} edited a status"
  166. end
  167. @spec format_title(Notification.t()) :: String.t()
  168. def format_title(%{activity: %{data: %{"directMessage" => true}}}) do
  169. "New Direct Message"
  170. end
  171. def format_title(%{type: "mention"}), do: "New Mention"
  172. def format_title(%{type: "status"}), do: "New Status"
  173. def format_title(%{type: "follow"}), do: "New Follower"
  174. def format_title(%{type: "follow_request"}), do: "New Follow Request"
  175. def format_title(%{type: "reblog"}), do: "New Repeat"
  176. def format_title(%{type: "favourite"}), do: "New Favorite"
  177. def format_title(%{type: "update"}), do: "New Update"
  178. def format_title(%{type: "pleroma:chat_mention"}), do: "New Chat Message"
  179. def format_title(%{type: "pleroma:emoji_reaction"}), do: "New Reaction"
  180. def format_title(%{type: "poll"}), do: "Poll Results"
  181. def format_title(%{type: type}), do: "New #{String.capitalize(type || "event")}"
  182. end