notify_service.rb 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. # frozen_string_literal: true
  2. class NotifyService < BaseService
  3. include Redisable
  4. # TODO: the severed_relationships and annual_report types probably warrants email notifications
  5. NON_EMAIL_TYPES = %i(
  6. admin.report
  7. admin.sign_up
  8. update
  9. poll
  10. status
  11. moderation_warning
  12. severed_relationships
  13. annual_report
  14. ).freeze
  15. class BaseCondition
  16. NEW_ACCOUNT_THRESHOLD = 30.days.freeze
  17. NEW_FOLLOWER_THRESHOLD = 3.days.freeze
  18. NON_FILTERABLE_TYPES = %i(
  19. admin.sign_up
  20. admin.report
  21. poll
  22. update
  23. account_warning
  24. annual_report
  25. ).freeze
  26. def initialize(notification)
  27. @recipient = notification.account
  28. @sender = notification.from_account
  29. @notification = notification
  30. @policy = NotificationPolicy.find_or_initialize_by(account: @recipient)
  31. end
  32. private
  33. def filterable_type?
  34. Notification::PROPERTIES[@notification.type][:filterable]
  35. end
  36. def not_following?
  37. !@recipient.following?(@sender)
  38. end
  39. def not_follower?
  40. follow = Follow.find_by(account: @sender, target_account: @recipient)
  41. follow.nil? || follow.created_at > NEW_FOLLOWER_THRESHOLD.ago
  42. end
  43. def new_account?
  44. @sender.created_at > NEW_ACCOUNT_THRESHOLD.ago
  45. end
  46. def override_for_sender?
  47. NotificationPermission.exists?(account: @recipient, from_account: @sender)
  48. end
  49. def from_limited?
  50. @sender.silenced? && not_following?
  51. end
  52. def private_mention_not_in_response?
  53. @notification.type == :mention && @notification.target_status.direct_visibility? && !response_to_recipient?
  54. end
  55. def response_to_recipient?
  56. return false if @notification.target_status.in_reply_to_id.nil?
  57. statuses_that_mention_sender.positive?
  58. end
  59. def statuses_that_mention_sender
  60. # This queries private mentions from the recipient to the sender up in the thread.
  61. # This allows up to 100 messages that do not match in the thread, allowing conversations
  62. # involving multiple people.
  63. Status.count_by_sql([<<-SQL.squish, id: @notification.target_status.in_reply_to_id, recipient_id: @recipient.id, sender_id: @sender.id, depth_limit: 100])
  64. WITH RECURSIVE ancestors(id, in_reply_to_id, mention_id, path, depth) AS (
  65. SELECT s.id, s.in_reply_to_id, m.id, ARRAY[s.id], 0
  66. FROM statuses s
  67. LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
  68. WHERE s.id = :id
  69. UNION ALL
  70. SELECT s.id, s.in_reply_to_id, m.id, ancestors.path || s.id, ancestors.depth + 1
  71. FROM ancestors
  72. JOIN statuses s ON s.id = ancestors.in_reply_to_id
  73. /* early exit if we already have a mention matching our requirements */
  74. LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id AND s.account_id = :recipient_id
  75. WHERE ancestors.mention_id IS NULL AND NOT s.id = ANY(path) AND ancestors.depth < :depth_limit
  76. )
  77. SELECT COUNT(*)
  78. FROM ancestors
  79. JOIN statuses s ON s.id = ancestors.id
  80. WHERE ancestors.mention_id IS NOT NULL AND s.account_id = :recipient_id AND s.visibility = 3
  81. SQL
  82. end
  83. end
  84. class DropCondition < BaseCondition
  85. def drop?
  86. blocked = @recipient.unavailable?
  87. blocked ||= from_self? && %i(poll severed_relationships moderation_warning annual_report).exclude?(@notification.type)
  88. return blocked if message? && from_staff?
  89. blocked ||= domain_blocking?
  90. blocked ||= @recipient.blocking?(@sender)
  91. blocked ||= @recipient.muting_notifications?(@sender)
  92. blocked ||= conversation_muted?
  93. blocked ||= blocked_mention? if message?
  94. return true if blocked
  95. return false unless filterable_type?
  96. return false if override_for_sender?
  97. blocked_by_limited_accounts_policy? ||
  98. blocked_by_not_following_policy? ||
  99. blocked_by_not_followers_policy? ||
  100. blocked_by_new_accounts_policy? ||
  101. blocked_by_private_mentions_policy?
  102. end
  103. private
  104. def blocked_mention?
  105. FeedManager.instance.filter?(:mentions, @notification.target_status, @recipient)
  106. end
  107. def message?
  108. @notification.type == :mention
  109. end
  110. def from_staff?
  111. @sender.local? && @sender.user.present? && @sender.user_role&.overrides?(@recipient.user_role) && @sender.user_role&.highlighted? && @sender.user_role&.can?(*UserRole::Flags::CATEGORIES[:moderation])
  112. end
  113. def from_self?
  114. @recipient.id == @sender.id
  115. end
  116. def domain_blocking?
  117. @recipient.domain_blocking?(@sender.domain) && not_following?
  118. end
  119. def conversation_muted?
  120. @notification.target_status && @recipient.muting_conversation?(@notification.target_status.conversation)
  121. end
  122. def blocked_by_not_following_policy?
  123. @policy.drop_not_following? && not_following?
  124. end
  125. def blocked_by_not_followers_policy?
  126. @policy.drop_not_followers? && not_follower?
  127. end
  128. def blocked_by_new_accounts_policy?
  129. @policy.drop_new_accounts? && new_account? && not_following?
  130. end
  131. def blocked_by_private_mentions_policy?
  132. @policy.drop_private_mentions? && not_following? && private_mention_not_in_response?
  133. end
  134. def blocked_by_limited_accounts_policy?
  135. @policy.drop_limited_accounts? && @sender.silenced? && not_following?
  136. end
  137. end
  138. class FilterCondition < BaseCondition
  139. def filter?
  140. return false unless filterable_type?
  141. return false if override_for_sender?
  142. filtered_by_limited_accounts_policy? ||
  143. filtered_by_not_following_policy? ||
  144. filtered_by_not_followers_policy? ||
  145. filtered_by_new_accounts_policy? ||
  146. filtered_by_private_mentions_policy?
  147. end
  148. private
  149. def filtered_by_not_following_policy?
  150. @policy.filter_not_following? && not_following?
  151. end
  152. def filtered_by_not_followers_policy?
  153. @policy.filter_not_followers? && not_follower?
  154. end
  155. def filtered_by_new_accounts_policy?
  156. @policy.filter_new_accounts? && new_account? && not_following?
  157. end
  158. def filtered_by_private_mentions_policy?
  159. @policy.filter_private_mentions? && not_following? && private_mention_not_in_response?
  160. end
  161. def filtered_by_limited_accounts_policy?
  162. @policy.filter_limited_accounts? && @sender.silenced? && not_following?
  163. end
  164. end
  165. def call(recipient, type, activity)
  166. return if recipient.user.nil?
  167. @recipient = recipient
  168. @activity = activity
  169. @notification = Notification.new(account: @recipient, type: type, activity: @activity)
  170. # For certain conditions we don't need to create a notification at all
  171. return if drop?
  172. @notification.filtered = filter?
  173. @notification.set_group_key!
  174. @notification.save!
  175. # It's possible the underlying activity has been deleted
  176. # between the save call and now
  177. return if @notification.activity.nil?
  178. if @notification.filtered?
  179. update_notification_request!
  180. else
  181. push_notification!
  182. push_to_conversation! if direct_message?
  183. send_email! if email_needed?
  184. end
  185. rescue ActiveRecord::RecordInvalid
  186. nil
  187. end
  188. private
  189. def drop?
  190. DropCondition.new(@notification).drop?
  191. end
  192. def filter?
  193. FilterCondition.new(@notification).filter?
  194. end
  195. def update_notification_request!
  196. return unless @notification.type == :mention
  197. notification_request = NotificationRequest.find_or_initialize_by(account_id: @recipient.id, from_account_id: @notification.from_account_id)
  198. notification_request.last_status_id = @notification.target_status.id
  199. notification_request.save
  200. end
  201. def push_notification!
  202. push_to_streaming_api! if subscribed_to_streaming_api?
  203. push_to_web_push_subscriptions!
  204. end
  205. def push_to_streaming_api!
  206. redis.publish("timeline:#{@recipient.id}:notifications", Oj.dump(event: :notification, payload: InlineRenderer.render(@notification, @recipient, :notification)))
  207. end
  208. def subscribed_to_streaming_api?
  209. redis.exists?("subscribed:timeline:#{@recipient.id}") || redis.exists?("subscribed:timeline:#{@recipient.id}:notifications")
  210. end
  211. def push_to_conversation!
  212. AccountConversation.add_status(@recipient, @notification.target_status)
  213. end
  214. def direct_message?
  215. @notification.type == :mention && @notification.target_status.direct_visibility?
  216. end
  217. def push_to_web_push_subscriptions!
  218. ::Web::PushNotificationWorker.push_bulk(web_push_subscriptions.select { |subscription| subscription.pushable?(@notification) }) { |subscription| [subscription.id, @notification.id] }
  219. end
  220. def web_push_subscriptions
  221. @web_push_subscriptions ||= ::Web::PushSubscription.where(user_id: @recipient.user.id).to_a
  222. end
  223. def subscribed_to_web_push?
  224. web_push_subscriptions.any?
  225. end
  226. def send_email!
  227. return unless NotificationMailer.respond_to?(@notification.type)
  228. NotificationMailer
  229. .with(recipient: @recipient, notification: @notification)
  230. .public_send(@notification.type)
  231. .deliver_later(wait: 2.minutes)
  232. end
  233. def email_needed?
  234. (!recipient_online? || always_send_emails?) && send_email_for_notification_type?
  235. end
  236. def recipient_online?
  237. subscribed_to_streaming_api? || subscribed_to_web_push?
  238. end
  239. def always_send_emails?
  240. @recipient.user.settings.always_send_emails
  241. end
  242. def send_email_for_notification_type?
  243. NON_EMAIL_TYPES.exclude?(@notification.type) && @recipient.user.settings["notification_emails.#{@notification.type}"]
  244. end
  245. end