Change e-mail notifications to only be sent when recipient is offline (#17984)
* Change e-mail notifications to only be sent when recipient is offline Change the default for follow and mention notifications back on * Add preference to always send e-mail notifications * Change wording
This commit is contained in:
		| @@ -54,7 +54,8 @@ class Settings::PreferencesController < Settings::BaseController | ||||
|       :setting_use_pending_items, | ||||
|       :setting_trends, | ||||
|       :setting_crop_images, | ||||
|       notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account trending_tag), | ||||
|       :setting_always_send_emails, | ||||
|       notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account trending_tag appeal), | ||||
|       interactions: %i(must_be_follower must_be_following must_be_following_dm) | ||||
|     ) | ||||
|   end | ||||
|   | ||||
| @@ -38,6 +38,7 @@ class UserSettingsDecorator | ||||
|     user.settings['use_pending_items']   = use_pending_items_preference if change?('setting_use_pending_items') | ||||
|     user.settings['trends']              = trends_preference if change?('setting_trends') | ||||
|     user.settings['crop_images']         = crop_images_preference if change?('setting_crop_images') | ||||
|     user.settings['always_send_emails']  = always_send_emails_preference if change?('setting_always_send_emails') | ||||
|   end | ||||
|  | ||||
|   def merged_notification_emails | ||||
| @@ -132,6 +133,10 @@ class UserSettingsDecorator | ||||
|     boolean_cast_setting 'setting_crop_images' | ||||
|   end | ||||
|  | ||||
|   def always_send_emails_preference | ||||
|     boolean_cast_setting 'setting_always_send_emails' | ||||
|   end | ||||
|  | ||||
|   def boolean_cast_setting(key) | ||||
|     ActiveModel::Type::Boolean.new.cast(settings[key]) | ||||
|   end | ||||
|   | ||||
| @@ -130,7 +130,7 @@ class User < ApplicationRecord | ||||
|            :reduce_motion, :system_font_ui, :noindex, :theme, :display_media, | ||||
|            :expand_spoilers, :default_language, :aggregate_reblogs, :show_application, | ||||
|            :advanced_layout, :use_blurhash, :use_pending_items, :trends, :crop_images, | ||||
|            :disable_swiping, | ||||
|            :disable_swiping, :always_send_emails, | ||||
|            to: :settings, prefix: :setting, allow_nil: false | ||||
|  | ||||
|   attr_reader :invite_code | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class NotifyService < BaseService | ||||
|   include Redisable | ||||
|  | ||||
|   def call(recipient, type, activity) | ||||
|     @recipient    = recipient | ||||
|     @activity     = activity | ||||
| @@ -8,10 +10,15 @@ class NotifyService < BaseService | ||||
|  | ||||
|     return if recipient.user.nil? || blocked? | ||||
|  | ||||
|     create_notification! | ||||
|     @notification.save! | ||||
|  | ||||
|     # It's possible the underlying activity has been deleted | ||||
|     # between the save call and now | ||||
|     return if @notification.activity.nil? | ||||
|  | ||||
|     push_notification! | ||||
|     push_to_conversation! if direct_message? | ||||
|     send_email! if email_enabled? | ||||
|     send_email! if email_needed? | ||||
|   rescue ActiveRecord::RecordInvalid | ||||
|     nil | ||||
|   end | ||||
| @@ -92,8 +99,8 @@ class NotifyService < BaseService | ||||
|   end | ||||
|  | ||||
|   def blocked? | ||||
|     blocked   = @recipient.suspended?                            # Skip if the recipient account is suspended anyway | ||||
|     blocked ||= from_self? && @notification.type != :poll        # Skip for interactions with self | ||||
|     blocked   = @recipient.suspended? | ||||
|     blocked ||= from_self? && @notification.type != :poll | ||||
|  | ||||
|     return blocked if message? && from_staff? | ||||
|  | ||||
| @@ -117,38 +124,52 @@ class NotifyService < BaseService | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def create_notification! | ||||
|     @notification.save! | ||||
|   def push_notification! | ||||
|     push_to_streaming_api! if subscribed_to_streaming_api? | ||||
|     push_to_web_push_subscriptions! | ||||
|   end | ||||
|  | ||||
|   def push_notification! | ||||
|     return if @notification.activity.nil? | ||||
|   def push_to_streaming_api! | ||||
|     redis.publish("timeline:#{@recipient.id}:notifications", Oj.dump(event: :notification, payload: InlineRenderer.render(@notification, @recipient, :notification))) | ||||
|   end | ||||
|  | ||||
|     Redis.current.publish("timeline:#{@recipient.id}:notifications", Oj.dump(event: :notification, payload: InlineRenderer.render(@notification, @recipient, :notification))) | ||||
|     send_push_notifications! | ||||
|   def subscribed_to_streaming_api? | ||||
|     redis.exists?("subscribed:timeline:#{@recipient.id}") || redis.exists?("subscribed:timeline:#{@recipient.id}:notifications") | ||||
|   end | ||||
|  | ||||
|   def push_to_conversation! | ||||
|     return if @notification.activity.nil? | ||||
|     AccountConversation.add_status(@recipient, @notification.target_status) | ||||
|   end | ||||
|  | ||||
|   def send_push_notifications! | ||||
|     subscriptions_ids = ::Web::PushSubscription.where(user_id: @recipient.user.id) | ||||
|                                                .select { |subscription| subscription.pushable?(@notification) } | ||||
|                                                .map(&:id) | ||||
|   def push_to_web_push_subscriptions! | ||||
|     ::Web::PushNotificationWorker.push_bulk(web_push_subscriptions.select { |subscription| subscription.pushable?(@notification) }) { |subscription| [subscription.id, @notification.id] } | ||||
|   end | ||||
|  | ||||
|     ::Web::PushNotificationWorker.push_bulk(subscriptions_ids) do |subscription_id| | ||||
|       [subscription_id, @notification.id] | ||||
|     end | ||||
|   def web_push_subscriptions | ||||
|     @web_push_subscriptions ||= ::Web::PushSubscription.where(user_id: @recipient.user.id).to_a | ||||
|   end | ||||
|  | ||||
|   def subscribed_to_web_push? | ||||
|     web_push_subscriptions.any? | ||||
|   end | ||||
|  | ||||
|   def send_email! | ||||
|     return if @notification.activity.nil? | ||||
|     NotificationMailer.public_send(@notification.type, @recipient, @notification).deliver_later(wait: 2.minutes) | ||||
|     NotificationMailer.public_send(@notification.type, @recipient, @notification).deliver_later(wait: 2.minutes) if NotificationMailer.respond_to?(@notification.type) | ||||
|   end | ||||
|  | ||||
|   def email_enabled? | ||||
|   def email_needed? | ||||
|     (!recipient_online? || always_send_emails?) && send_email_for_notification_type? | ||||
|   end | ||||
|  | ||||
|   def recipient_online? | ||||
|     subscribed_to_streaming_api? || subscribed_to_web_push? | ||||
|   end | ||||
|  | ||||
|   def always_send_emails? | ||||
|     @recipient.user.settings.always_send_emails | ||||
|   end | ||||
|  | ||||
|   def send_email_for_notification_type? | ||||
|     @recipient.user.settings.notification_emails[@notification.type.to_s] | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -25,6 +25,9 @@ | ||||
|         = ff.input :pending_account, as: :boolean, wrapper: :with_label | ||||
|         = ff.input :trending_tag, as: :boolean, wrapper: :with_label | ||||
|  | ||||
|   .fields-group | ||||
|     = f.input :setting_always_send_emails, as: :boolean, wrapper: :with_label | ||||
|  | ||||
|   .fields-group | ||||
|     = f.simple_fields_for :notification_emails, hash_to_object(current_user.settings.notification_emails) do |ff| | ||||
|       = ff.input :digest, as: :boolean, wrapper: :with_label | ||||
|   | ||||
| @@ -49,6 +49,7 @@ en: | ||||
|         phrase: Will be matched regardless of casing in text or content warning of a post | ||||
|         scopes: Which APIs the application will be allowed to access. If you select a top-level scope, you don't need to select individual ones. | ||||
|         setting_aggregate_reblogs: Do not show new boosts for posts that have been recently boosted (only affects newly-received boosts) | ||||
|         setting_always_send_emails: Normally e-mail notifications won't be sent when you are actively using Mastodon | ||||
|         setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click | ||||
|         setting_display_media_default: Hide media marked as sensitive | ||||
|         setting_display_media_hide_all: Always hide media | ||||
| @@ -151,6 +152,7 @@ en: | ||||
|         phrase: Keyword or phrase | ||||
|         setting_advanced_layout: Enable advanced web interface | ||||
|         setting_aggregate_reblogs: Group boosts in timelines | ||||
|         setting_always_send_emails: Always send e-mail notifications | ||||
|         setting_auto_play_gif: Auto-play animated GIFs | ||||
|         setting_boost_modal: Show confirmation dialog before boosting | ||||
|         setting_crop_images: Crop images in non-expanded posts to 16x9 | ||||
|   | ||||
| @@ -38,16 +38,17 @@ defaults: &defaults | ||||
|   trendable_by_default: false | ||||
|   crop_images: true | ||||
|   notification_emails: | ||||
|     follow: false | ||||
|     follow: true | ||||
|     reblog: false | ||||
|     favourite: false | ||||
|     mention: false | ||||
|     mention: true | ||||
|     follow_request: true | ||||
|     digest: true | ||||
|     report: true | ||||
|     pending_account: true | ||||
|     trending_tag: true | ||||
|     appeal: true | ||||
|   always_send_emails: false | ||||
|   interactions: | ||||
|     must_be_follower: false | ||||
|     must_be_following: false | ||||
|   | ||||
		Reference in New Issue
	
	Block a user