logo

pleroma

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

notification.ex (22398B)


  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.Notification do
  5. use Ecto.Schema
  6. alias Ecto.Multi
  7. alias Pleroma.Activity
  8. alias Pleroma.FollowingRelationship
  9. alias Pleroma.Marker
  10. alias Pleroma.Notification
  11. alias Pleroma.Object
  12. alias Pleroma.Pagination
  13. alias Pleroma.Repo
  14. alias Pleroma.ThreadMute
  15. alias Pleroma.User
  16. alias Pleroma.Web.CommonAPI
  17. alias Pleroma.Web.CommonAPI.Utils
  18. alias Pleroma.Web.Push
  19. alias Pleroma.Web.Streamer
  20. import Ecto.Query
  21. import Ecto.Changeset
  22. require Logger
  23. @type t :: %__MODULE__{}
  24. @include_muted_option :with_muted
  25. schema "notifications" do
  26. field(:seen, :boolean, default: false)
  27. # This is an enum type in the database. If you add a new notification type,
  28. # remember to add a migration to add it to the `notifications_type` enum
  29. # as well.
  30. field(:type, :string)
  31. belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
  32. belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
  33. timestamps()
  34. end
  35. def update_notification_type(user, activity) do
  36. with %__MODULE__{} = notification <-
  37. Repo.get_by(__MODULE__, user_id: user.id, activity_id: activity.id) do
  38. type =
  39. activity
  40. |> type_from_activity()
  41. notification
  42. |> changeset(%{type: type})
  43. |> Repo.update()
  44. end
  45. end
  46. @spec unread_notifications_count(User.t()) :: integer()
  47. def unread_notifications_count(%User{id: user_id}) do
  48. from(q in __MODULE__,
  49. where: q.user_id == ^user_id and q.seen == false
  50. )
  51. |> Repo.aggregate(:count, :id)
  52. end
  53. @notification_types ~w{
  54. favourite
  55. follow
  56. follow_request
  57. mention
  58. move
  59. pleroma:chat_mention
  60. pleroma:emoji_reaction
  61. pleroma:report
  62. reblog
  63. poll
  64. status
  65. }
  66. def changeset(%Notification{} = notification, attrs) do
  67. notification
  68. |> cast(attrs, [:seen, :type])
  69. |> validate_inclusion(:type, @notification_types)
  70. end
  71. @spec last_read_query(User.t()) :: Ecto.Queryable.t()
  72. def last_read_query(user) do
  73. from(q in Pleroma.Notification,
  74. where: q.user_id == ^user.id,
  75. where: q.seen == true,
  76. select: type(q.id, :string),
  77. limit: 1,
  78. order_by: fragment("? desc nulls last", q.id)
  79. )
  80. end
  81. defp for_user_query_ap_id_opts(user, opts) do
  82. ap_id_relationships =
  83. [:block] ++
  84. if opts[@include_muted_option], do: [], else: [:notification_mute]
  85. preloaded_ap_ids = User.outgoing_relationships_ap_ids(user, ap_id_relationships)
  86. exclude_blocked_opts = Map.merge(%{blocked_users_ap_ids: preloaded_ap_ids[:block]}, opts)
  87. exclude_notification_muted_opts =
  88. Map.merge(%{notification_muted_users_ap_ids: preloaded_ap_ids[:notification_mute]}, opts)
  89. {exclude_blocked_opts, exclude_notification_muted_opts}
  90. end
  91. def for_user_query(user, opts \\ %{}) do
  92. {exclude_blocked_opts, exclude_notification_muted_opts} =
  93. for_user_query_ap_id_opts(user, opts)
  94. Notification
  95. |> where(user_id: ^user.id)
  96. |> join(:inner, [n], activity in assoc(n, :activity))
  97. |> join(:left, [n, a], object in Object,
  98. on:
  99. fragment(
  100. "(?->>'id') = associated_object_id(?)",
  101. object.data,
  102. a.data
  103. )
  104. )
  105. |> join(:inner, [_n, a], u in User, on: u.ap_id == a.actor, as: :user_actor)
  106. |> preload([n, a, o], activity: {a, object: o})
  107. |> where([user_actor: user_actor], user_actor.is_active)
  108. |> exclude_notification_muted(user, exclude_notification_muted_opts)
  109. |> exclude_blocked(user, exclude_blocked_opts)
  110. |> exclude_blockers(user)
  111. |> exclude_filtered(user)
  112. |> exclude_visibility(opts)
  113. end
  114. # Excludes blocked users and non-followed domain-blocked users
  115. defp exclude_blocked(query, user, opts) do
  116. blocked_ap_ids = opts[:blocked_users_ap_ids] || User.blocked_users_ap_ids(user)
  117. query
  118. |> where([..., user_actor: user_actor], user_actor.ap_id not in ^blocked_ap_ids)
  119. |> FollowingRelationship.keep_following_or_not_domain_blocked(user)
  120. end
  121. defp exclude_blockers(query, user) do
  122. if Pleroma.Config.get([:activitypub, :blockers_visible]) == true do
  123. query
  124. else
  125. blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block])
  126. query
  127. |> where([..., user_actor: user_actor], user_actor.ap_id not in ^blocker_ap_ids)
  128. end
  129. end
  130. defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do
  131. query
  132. end
  133. defp exclude_notification_muted(query, user, opts) do
  134. notification_muted_ap_ids =
  135. opts[:notification_muted_users_ap_ids] || User.notification_muted_users_ap_ids(user)
  136. query
  137. |> where([..., user_actor: user_actor], user_actor.ap_id not in ^notification_muted_ap_ids)
  138. |> join(:left, [n, a], tm in ThreadMute,
  139. on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data),
  140. as: :thread_mute
  141. )
  142. |> where([thread_mute: thread_mute], is_nil(thread_mute.user_id))
  143. end
  144. defp exclude_filtered(query, user) do
  145. case Pleroma.Filter.compose_regex(user) do
  146. nil ->
  147. query
  148. regex ->
  149. from([_n, a, o] in query,
  150. where:
  151. fragment("not(?->>'content' ~* ?)", o.data, ^regex) or
  152. fragment("?->>'content' is null", o.data) or
  153. fragment("?->>'actor' = ?", o.data, ^user.ap_id)
  154. )
  155. end
  156. end
  157. @valid_visibilities ~w[direct unlisted public private]
  158. defp exclude_visibility(query, %{exclude_visibilities: visibility})
  159. when is_list(visibility) do
  160. if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
  161. query
  162. |> join(:left, [n, a], mutated_activity in Pleroma.Activity,
  163. on:
  164. fragment(
  165. "associated_object_id(?)",
  166. a.data
  167. ) ==
  168. fragment(
  169. "associated_object_id(?)",
  170. mutated_activity.data
  171. ) and
  172. fragment("(?->>'type' = 'Like' or ?->>'type' = 'Announce')", a.data, a.data) and
  173. fragment("?->>'type'", mutated_activity.data) == "Create",
  174. as: :mutated_activity
  175. )
  176. |> where(
  177. [n, a, mutated_activity: mutated_activity],
  178. not fragment(
  179. """
  180. CASE WHEN (?->>'type') = 'Like' or (?->>'type') = 'Announce'
  181. THEN (activity_visibility(?, ?, ?) = ANY (?))
  182. ELSE (activity_visibility(?, ?, ?) = ANY (?)) END
  183. """,
  184. a.data,
  185. a.data,
  186. mutated_activity.actor,
  187. mutated_activity.recipients,
  188. mutated_activity.data,
  189. ^visibility,
  190. a.actor,
  191. a.recipients,
  192. a.data,
  193. ^visibility
  194. )
  195. )
  196. else
  197. Logger.error("Could not exclude visibility to #{visibility}")
  198. query
  199. end
  200. end
  201. defp exclude_visibility(query, %{exclude_visibilities: visibility})
  202. when visibility in @valid_visibilities do
  203. exclude_visibility(query, [visibility])
  204. end
  205. defp exclude_visibility(query, %{exclude_visibilities: visibility})
  206. when visibility not in @valid_visibilities do
  207. Logger.error("Could not exclude visibility to #{visibility}")
  208. query
  209. end
  210. defp exclude_visibility(query, _visibility), do: query
  211. def for_user(user, opts \\ %{}) do
  212. user
  213. |> for_user_query(opts)
  214. |> Pagination.fetch_paginated(opts)
  215. end
  216. @doc """
  217. Returns notifications for user received since given date.
  218. ## Examples
  219. iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-13 11:22:33])
  220. [%Pleroma.Notification{}, %Pleroma.Notification{}]
  221. iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33])
  222. []
  223. """
  224. @spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()]
  225. def for_user_since(user, date) do
  226. from(n in for_user_query(user),
  227. where: n.updated_at > ^date
  228. )
  229. |> Repo.all()
  230. end
  231. def set_read_up_to(%{id: user_id} = user, id) do
  232. query =
  233. from(
  234. n in Notification,
  235. where: n.user_id == ^user_id,
  236. where: n.id <= ^id,
  237. where: n.seen == false,
  238. # Ideally we would preload object and activities here
  239. # but Ecto does not support preloads in update_all
  240. select: n.id
  241. )
  242. Multi.new()
  243. |> Multi.update_all(:ids, query, set: [seen: true, updated_at: NaiveDateTime.utc_now()])
  244. |> Marker.multi_set_last_read_id(user, "notifications")
  245. |> Repo.transaction()
  246. end
  247. @spec read_one(User.t(), String.t()) ::
  248. {:ok, Notification.t()} | {:error, Ecto.Changeset.t()} | nil
  249. def read_one(%User{} = user, notification_id) do
  250. with {:ok, %Notification{} = notification} <- get(user, notification_id) do
  251. Multi.new()
  252. |> Multi.update(:update, changeset(notification, %{seen: true}))
  253. |> Marker.multi_set_last_read_id(user, "notifications")
  254. |> Repo.transaction()
  255. end
  256. end
  257. def get(%{id: user_id} = _user, id) do
  258. query =
  259. from(
  260. n in Notification,
  261. where: n.id == ^id,
  262. join: activity in assoc(n, :activity),
  263. preload: [activity: activity]
  264. )
  265. notification = Repo.one(query)
  266. case notification do
  267. %{user_id: ^user_id} ->
  268. {:ok, notification}
  269. _ ->
  270. {:error, "Cannot get notification"}
  271. end
  272. end
  273. def clear(user) do
  274. from(n in Notification, where: n.user_id == ^user.id)
  275. |> Repo.delete_all()
  276. end
  277. def destroy_multiple(%{id: user_id} = _user, ids) do
  278. from(n in Notification,
  279. where: n.id in ^ids,
  280. where: n.user_id == ^user_id
  281. )
  282. |> Repo.delete_all()
  283. end
  284. def dismiss(%Pleroma.Activity{} = activity) do
  285. Notification
  286. |> where([n], n.activity_id == ^activity.id)
  287. |> Repo.delete_all()
  288. |> case do
  289. {_, notifications} -> {:ok, notifications}
  290. _ -> {:error, "Cannot dismiss notification"}
  291. end
  292. end
  293. def dismiss(%{id: user_id} = _user, id) do
  294. notification = Repo.get(Notification, id)
  295. case notification do
  296. %{user_id: ^user_id} ->
  297. Repo.delete(notification)
  298. _ ->
  299. {:error, "Cannot dismiss notification"}
  300. end
  301. end
  302. @spec create_notifications(Activity.t()) :: {:ok, [Notification.t()] | []}
  303. def create_notifications(activity)
  304. def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do
  305. object = Object.normalize(activity, fetch: false)
  306. if object && object.data["type"] == "Answer" do
  307. {:ok, []}
  308. else
  309. do_create_notifications(activity)
  310. end
  311. end
  312. def create_notifications(%Activity{data: %{"type" => type}} = activity)
  313. when type in ["Follow", "Like", "Announce", "Move", "EmojiReact", "Flag", "Update"] do
  314. do_create_notifications(activity)
  315. end
  316. def create_notifications(_), do: {:ok, []}
  317. defp do_create_notifications(%Activity{} = activity) do
  318. enabled_receivers = get_notified_from_activity(activity)
  319. enabled_subscribers = get_notified_subscribers_from_activity(activity)
  320. notifications =
  321. (Enum.map(enabled_receivers, fn user ->
  322. create_notification(activity, user)
  323. end) ++
  324. Enum.map(enabled_subscribers -- enabled_receivers, fn user ->
  325. create_notification(activity, user, type: "status")
  326. end))
  327. |> Enum.reject(&is_nil/1)
  328. {:ok, notifications}
  329. end
  330. defp type_from_activity(%{data: %{"type" => type}} = activity) do
  331. case type do
  332. "Follow" ->
  333. if Activity.follow_accepted?(activity) do
  334. "follow"
  335. else
  336. "follow_request"
  337. end
  338. "Announce" ->
  339. "reblog"
  340. "Like" ->
  341. "favourite"
  342. "Move" ->
  343. "move"
  344. "EmojiReact" ->
  345. "pleroma:emoji_reaction"
  346. "Flag" ->
  347. "pleroma:report"
  348. # Compatibility with old reactions
  349. "EmojiReaction" ->
  350. "pleroma:emoji_reaction"
  351. "Create" ->
  352. activity
  353. |> type_from_activity_object()
  354. "Update" ->
  355. "update"
  356. t ->
  357. raise "No notification type for activity type #{t}"
  358. end
  359. end
  360. defp type_from_activity_object(%{data: %{"type" => "Create", "object" => %{}}}), do: "mention"
  361. defp type_from_activity_object(%{data: %{"type" => "Create"}} = activity) do
  362. object = Object.get_by_ap_id(activity.data["object"])
  363. case object && object.data["type"] do
  364. "ChatMessage" -> "pleroma:chat_mention"
  365. _ -> "mention"
  366. end
  367. end
  368. # TODO move to sql, too.
  369. def create_notification(%Activity{} = activity, %User{} = user, opts \\ []) do
  370. type = Keyword.get(opts, :type, type_from_activity(activity))
  371. unless skip?(activity, user, opts) do
  372. {:ok, %{notification: notification}} =
  373. Multi.new()
  374. |> Multi.insert(:notification, %Notification{
  375. user_id: user.id,
  376. activity: activity,
  377. seen: mark_as_read?(activity, user),
  378. type: type
  379. })
  380. |> Marker.multi_set_last_read_id(user, "notifications")
  381. |> Repo.transaction()
  382. notification
  383. end
  384. end
  385. def create_poll_notifications(%Activity{} = activity) do
  386. with %Object{data: %{"type" => "Question", "actor" => actor} = data} <-
  387. Object.normalize(activity) do
  388. voters =
  389. case data do
  390. %{"voters" => voters} when is_list(voters) -> voters
  391. _ -> []
  392. end
  393. notifications =
  394. Enum.reduce([actor | voters], [], fn ap_id, acc ->
  395. with %User{local: true} = user <- User.get_by_ap_id(ap_id) do
  396. [create_notification(activity, user, type: "poll") | acc]
  397. else
  398. _ -> acc
  399. end
  400. end)
  401. {:ok, notifications}
  402. end
  403. end
  404. @doc """
  405. Returns a tuple with 2 elements:
  406. {notification-enabled receivers, currently disabled receivers (blocking / [thread] muting)}
  407. NOTE: might be called for FAKE Activities, see ActivityPub.Utils.get_notified_from_object/1
  408. """
  409. @spec get_notified_from_activity(Activity.t(), boolean()) :: list(User.t())
  410. def get_notified_from_activity(activity, local_only \\ true)
  411. def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
  412. when type in [
  413. "Create",
  414. "Like",
  415. "Announce",
  416. "Follow",
  417. "Move",
  418. "EmojiReact",
  419. "Flag",
  420. "Update"
  421. ] do
  422. potential_receiver_ap_ids = get_potential_receiver_ap_ids(activity)
  423. potential_receivers =
  424. User.get_users_from_set(potential_receiver_ap_ids, local_only: local_only)
  425. notification_enabled_ap_ids =
  426. potential_receiver_ap_ids
  427. |> exclude_domain_blocker_ap_ids(activity, potential_receivers)
  428. |> exclude_relationship_restricted_ap_ids(activity)
  429. |> exclude_thread_muter_ap_ids(activity)
  430. Enum.filter(potential_receivers, fn u -> u.ap_id in notification_enabled_ap_ids end)
  431. end
  432. def get_notified_from_activity(_, _local_only), do: []
  433. def get_notified_subscribers_from_activity(activity, local_only \\ true)
  434. def get_notified_subscribers_from_activity(
  435. %Activity{data: %{"type" => "Create"}} = activity,
  436. local_only
  437. ) do
  438. notification_enabled_ap_ids =
  439. []
  440. |> Utils.maybe_notify_subscribers(activity)
  441. potential_receivers =
  442. User.get_users_from_set(notification_enabled_ap_ids, local_only: local_only)
  443. Enum.filter(potential_receivers, fn u -> u.ap_id in notification_enabled_ap_ids end)
  444. end
  445. def get_notified_subscribers_from_activity(_, _), do: []
  446. # For some activities, only notify the author of the object
  447. def get_potential_receiver_ap_ids(%{data: %{"type" => type, "object" => object_id}})
  448. when type in ~w{Like Announce EmojiReact} do
  449. case Object.get_cached_by_ap_id(object_id) do
  450. %Object{data: %{"actor" => actor}} ->
  451. [actor]
  452. _ ->
  453. []
  454. end
  455. end
  456. def get_potential_receiver_ap_ids(%{data: %{"type" => "Follow", "object" => object_id}}) do
  457. [object_id]
  458. end
  459. def get_potential_receiver_ap_ids(%{data: %{"type" => "Flag", "actor" => actor}}) do
  460. (User.all_users_with_privilege(:reports_manage_reports)
  461. |> Enum.map(fn user -> user.ap_id end)) --
  462. [actor]
  463. end
  464. # Update activity: notify all who repeated this
  465. def get_potential_receiver_ap_ids(%{data: %{"type" => "Update", "actor" => actor}} = activity) do
  466. with %Object{data: %{"id" => object_id}} <- Object.normalize(activity, fetch: false) do
  467. repeaters =
  468. Activity.Queries.by_type("Announce")
  469. |> Activity.Queries.by_object_id(object_id)
  470. |> Activity.with_joined_user_actor()
  471. |> where([a, u], u.local)
  472. |> select([a, u], u.ap_id)
  473. |> Repo.all()
  474. repeaters -- [actor]
  475. end
  476. end
  477. def get_potential_receiver_ap_ids(activity) do
  478. []
  479. |> Utils.maybe_notify_to_recipients(activity)
  480. |> Utils.maybe_notify_mentioned_recipients(activity)
  481. |> Utils.maybe_notify_followers(activity)
  482. |> Enum.uniq()
  483. end
  484. @doc "Filters out AP IDs domain-blocking and not following the activity's actor"
  485. def exclude_domain_blocker_ap_ids(ap_ids, activity, preloaded_users \\ [])
  486. def exclude_domain_blocker_ap_ids([], _activity, _preloaded_users), do: []
  487. def exclude_domain_blocker_ap_ids(ap_ids, %Activity{} = activity, preloaded_users) do
  488. activity_actor_domain = activity.actor && URI.parse(activity.actor).host
  489. users =
  490. ap_ids
  491. |> Enum.map(fn ap_id ->
  492. Enum.find(preloaded_users, &(&1.ap_id == ap_id)) ||
  493. User.get_cached_by_ap_id(ap_id)
  494. end)
  495. |> Enum.filter(& &1)
  496. domain_blocker_ap_ids = for u <- users, activity_actor_domain in u.domain_blocks, do: u.ap_id
  497. domain_blocker_follower_ap_ids =
  498. if Enum.any?(domain_blocker_ap_ids) do
  499. activity
  500. |> Activity.user_actor()
  501. |> FollowingRelationship.followers_ap_ids(domain_blocker_ap_ids)
  502. else
  503. []
  504. end
  505. ap_ids
  506. |> Kernel.--(domain_blocker_ap_ids)
  507. |> Kernel.++(domain_blocker_follower_ap_ids)
  508. end
  509. @doc "Filters out AP IDs of users basing on their relationships with activity actor user"
  510. def exclude_relationship_restricted_ap_ids([], _activity), do: []
  511. def exclude_relationship_restricted_ap_ids(ap_ids, %Activity{} = activity) do
  512. relationship_restricted_ap_ids =
  513. activity
  514. |> Activity.user_actor()
  515. |> User.incoming_relationships_ungrouped_ap_ids([
  516. :block,
  517. :notification_mute
  518. ])
  519. Enum.uniq(ap_ids) -- relationship_restricted_ap_ids
  520. end
  521. @doc "Filters out AP IDs of users who mute activity thread"
  522. def exclude_thread_muter_ap_ids([], _activity), do: []
  523. def exclude_thread_muter_ap_ids(ap_ids, %Activity{} = activity) do
  524. thread_muter_ap_ids = ThreadMute.muter_ap_ids(activity.data["context"])
  525. Enum.uniq(ap_ids) -- thread_muter_ap_ids
  526. end
  527. def skip?(activity, user, opts \\ [])
  528. @spec skip?(Activity.t(), User.t(), Keyword.t()) :: boolean()
  529. def skip?(%Activity{} = activity, %User{} = user, opts) do
  530. [
  531. :self,
  532. :internal,
  533. :invisible,
  534. :block_from_strangers,
  535. :recently_followed,
  536. :filtered
  537. ]
  538. |> Enum.find(&skip?(&1, activity, user, opts))
  539. end
  540. def skip?(_activity, _user, _opts), do: false
  541. @spec skip?(atom(), Activity.t(), User.t(), Keyword.t()) :: boolean()
  542. def skip?(:self, %Activity{} = activity, %User{} = user, opts) do
  543. cond do
  544. opts[:type] == "poll" -> false
  545. activity.data["actor"] == user.ap_id -> true
  546. true -> false
  547. end
  548. end
  549. def skip?(:internal, %Activity{} = activity, _user, _opts) do
  550. actor = activity.data["actor"]
  551. user = User.get_cached_by_ap_id(actor)
  552. User.internal?(user)
  553. end
  554. def skip?(:invisible, %Activity{} = activity, _user, _opts) do
  555. actor = activity.data["actor"]
  556. user = User.get_cached_by_ap_id(actor)
  557. User.invisible?(user)
  558. end
  559. def skip?(
  560. :block_from_strangers,
  561. %Activity{} = activity,
  562. %User{notification_settings: %{block_from_strangers: true}} = user,
  563. opts
  564. ) do
  565. actor = activity.data["actor"]
  566. follower = User.get_cached_by_ap_id(actor)
  567. cond do
  568. opts[:type] == "poll" -> false
  569. user.ap_id == actor -> false
  570. !User.following?(user, follower) -> true
  571. true -> false
  572. end
  573. end
  574. # To do: consider defining recency in hours and checking FollowingRelationship with a single SQL
  575. def skip?(
  576. :recently_followed,
  577. %Activity{data: %{"type" => "Follow"}} = activity,
  578. %User{} = user,
  579. _opts
  580. ) do
  581. actor = activity.data["actor"]
  582. Notification.for_user(user)
  583. |> Enum.any?(fn
  584. %{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}} -> true
  585. _ -> false
  586. end)
  587. end
  588. def skip?(:filtered, %{data: %{"type" => type}}, _user, _opts) when type in ["Follow", "Move"],
  589. do: false
  590. def skip?(:filtered, activity, user, _opts) do
  591. object = Object.normalize(activity, fetch: false)
  592. cond do
  593. is_nil(object) ->
  594. false
  595. object.data["actor"] == user.ap_id ->
  596. false
  597. not is_nil(regex = Pleroma.Filter.compose_regex(user, :re)) ->
  598. Regex.match?(regex, object.data["content"])
  599. true ->
  600. false
  601. end
  602. end
  603. def skip?(_type, _activity, _user, _opts), do: false
  604. def mark_as_read?(activity, target_user) do
  605. user = Activity.user_actor(activity)
  606. User.mutes_user?(target_user, user) || CommonAPI.thread_muted?(activity, target_user)
  607. end
  608. def for_user_and_activity(user, activity) do
  609. from(n in __MODULE__,
  610. where: n.user_id == ^user.id,
  611. where: n.activity_id == ^activity.id
  612. )
  613. |> Repo.one()
  614. end
  615. @spec mark_context_as_read(User.t(), String.t()) :: {integer(), nil | [term()]}
  616. def mark_context_as_read(%User{id: id}, context) do
  617. from(
  618. n in Notification,
  619. join: a in assoc(n, :activity),
  620. where: n.user_id == ^id,
  621. where: n.seen == false,
  622. where: fragment("?->>'context'", a.data) == ^context
  623. )
  624. |> Repo.update_all(set: [seen: true])
  625. end
  626. @doc "Streams a list of notifications over websockets and web push"
  627. @spec stream(list(Notification.t())) :: :ok
  628. def stream(notifications) do
  629. Enum.each(notifications, fn notification ->
  630. Streamer.stream(["user", "user:notification"], notification)
  631. Push.send(notification)
  632. end)
  633. end
  634. end