diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb
index 13919b400d..91995f8421 100644
--- a/app/controllers/api/v1/notifications_controller.rb
+++ b/app/controllers/api/v1/notifications_controller.rb
@@ -16,7 +16,7 @@ class Api::V1::NotificationsController < Api::BaseController
@relationships = StatusRelationshipsPresenter.new(target_statuses_from_notifications, current_user&.account_id)
end
- render json: @notifications, each_serializer: REST::NotificationSerializer, relationships: @relationships
+ render json: @notifications, each_serializer: REST::NotificationSerializer, relationships: @relationships, supported_notification_types: params[:supported_types]
end
def unread_count
@@ -29,7 +29,7 @@ class Api::V1::NotificationsController < Api::BaseController
def show
@notification = current_account.notifications.without_suspended.find(params[:id])
- render json: @notification, serializer: REST::NotificationSerializer
+ render json: @notification, serializer: REST::NotificationSerializer, supported_notification_types: params[:supported_types]
end
def clear
@@ -89,6 +89,8 @@ class Api::V1::NotificationsController < Api::BaseController
end
def pagination_params(core_params)
- params.slice(:limit, :account_id, :types, :exclude_types, :include_filtered).permit(:limit, :account_id, :include_filtered, types: [], exclude_types: []).merge(core_params)
+ params.slice(:limit, :account_id, :types, :exclude_types, :include_filtered, :supported_types)
+ .permit(:limit, :account_id, :include_filtered, types: [], exclude_types: [], supported_types: [])
+ .merge(core_params)
end
end
diff --git a/app/controllers/api/v2/notifications_controller.rb b/app/controllers/api/v2/notifications_controller.rb
index 848c361cfc..f54cd230b7 100644
--- a/app/controllers/api/v2/notifications_controller.rb
+++ b/app/controllers/api/v2/notifications_controller.rb
@@ -33,7 +33,7 @@ class Api::V2::NotificationsController < Api::BaseController
'app.notification_grouping.expand_accounts_param' => expand_accounts_param
)
- render json: @presenter, serializer: REST::DedupNotificationGroupSerializer, relationships: @relationships, expand_accounts: expand_accounts_param
+ render json: @presenter, serializer: REST::DedupNotificationGroupSerializer, relationships: @relationships, expand_accounts: expand_accounts_param, supported_notification_types: params[:supported_types]
end
end
@@ -48,7 +48,7 @@ class Api::V2::NotificationsController < Api::BaseController
def show
@notification = current_account.notifications.without_suspended.by_group_key(params[:group_key]).take!
presenter = GroupedNotificationsPresenter.new(NotificationGroup.from_notifications([@notification]))
- render json: presenter, serializer: REST::DedupNotificationGroupSerializer
+ render json: presenter, serializer: REST::DedupNotificationGroupSerializer, supported_notification_types: params[:supported_types]
end
def clear
@@ -138,7 +138,9 @@ class Api::V2::NotificationsController < Api::BaseController
end
def pagination_params(core_params)
- params.slice(:limit, :include_filtered, :types, :exclude_types, :grouped_types).permit(:limit, :include_filtered, types: [], exclude_types: [], grouped_types: []).merge(core_params)
+ params.slice(:limit, :include_filtered, :types, :exclude_types, :grouped_types, :supported_types)
+ .permit(:limit, :include_filtered, types: [], exclude_types: [], grouped_types: [], supported_types: [])
+ .merge(core_params)
end
def expand_accounts_param
diff --git a/app/lib/text_formatter.rb b/app/lib/text_formatter.rb
index 10f007ff37..597135314b 100644
--- a/app/lib/text_formatter.rb
+++ b/app/lib/text_formatter.rb
@@ -76,6 +76,15 @@ class TextFormatter
rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
h(url)
end
+
+ def link_to_mention(account, with_domain: false)
+ url = ActivityPub::TagManager.instance.url_for(account)
+ display_username = with_domain ? account.pretty_acct : account.username
+
+ <<~HTML.squish
+ @#{h(display_username)}
+ HTML
+ end
end
private
@@ -136,12 +145,7 @@ class TextFormatter
return "@#{h(entity[:screen_name])}" if account.nil?
- url = ActivityPub::TagManager.instance.url_for(account)
- display_username = same_username_hits&.positive? || with_domains? ? account.pretty_acct : account.username
-
- <<~HTML.squish
- @#{h(display_username)}
- HTML
+ TextFormatter.link_to_mention(account, with_domain: same_username_hits&.positive? || with_domains?)
end
def entity_cache
diff --git a/app/models/notification.rb b/app/models/notification.rb
index a54788dd2e..9a1a366869 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -37,54 +37,71 @@ class Notification < ApplicationRecord
PROPERTIES = {
mention: {
filterable: true,
+ baseline: true,
}.freeze,
status: {
filterable: false,
+ baseline: true,
}.freeze,
reblog: {
filterable: true,
+ baseline: true,
}.freeze,
follow: {
filterable: true,
+ baseline: true,
}.freeze,
follow_request: {
filterable: true,
+ baseline: true,
}.freeze,
favourite: {
filterable: true,
+ baseline: true,
}.freeze,
poll: {
filterable: false,
+ baseline: true,
}.freeze,
update: {
filterable: false,
+ baseline: true,
}.freeze,
severed_relationships: {
filterable: false,
+ baseline: false,
}.freeze,
moderation_warning: {
filterable: false,
+ baseline: false,
}.freeze,
annual_report: {
filterable: false,
+ baseline: true,
}.freeze,
'admin.sign_up': {
filterable: false,
+ baseline: false,
}.freeze,
'admin.report': {
filterable: false,
+ baseline: false,
}.freeze,
quote: {
filterable: true,
+ baseline: true,
}.freeze,
quoted_update: {
filterable: false,
+ baseline: true,
}.freeze,
added_to_collection: {
filterable: true,
+ baseline: false,
}.freeze,
collection_update: {
filterable: false,
+ baseline: false,
}.freeze,
}.freeze
diff --git a/app/serializers/concerns/notification_fallback_concern.rb b/app/serializers/concerns/notification_fallback_concern.rb
new file mode 100644
index 0000000000..a3fbf4f569
--- /dev/null
+++ b/app/serializers/concerns/notification_fallback_concern.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+module NotificationFallbackConcern
+ extend ActiveSupport::Concern
+
+ def fallback
+ {
+ title: fallback_title,
+ summary: fallback_summary,
+ description: nil,
+ }
+ end
+
+ def needs_fallback?
+ return false if instance_options[:supported_notification_types].nil?
+ return false if Notification::PROPERTIES.dig(object.type, :baseline) || instance_options[:supported_notification_types].include?(object.type.to_s)
+
+ # In rare cases, a notification might be missing its activity, in which case we can't do much
+ case object.type
+ when :severed_relationships
+ object.account_relationship_severance_event.present?
+ when :'admin.report'
+ object.report.present?
+ when :added_to_collection, :collection_update
+ object.target_collection.present?
+ else
+ true
+ end
+ end
+
+ def fallback_title
+ account = object.is_a?(NotificationGroup) ? object.sample_accounts.first : object.from_account
+
+ case object.type
+ when :severed_relationships
+ I18n.t(
+ 'notification_fallbacks.severed_relationships.title',
+ name: object.account_relationship_severance_event.target_name
+ )
+ when :moderation_warning
+ I18n.t('notification_fallbacks.moderation_warning.title')
+ when :'admin.sign_up'
+ count = object.is_a?(NotificationGroup) ? object.sample_accounts.count : 1
+ if count > 1
+ I18n.t(
+ 'notification_fallbacks.admin_sign_up.title_and_others_html',
+ name: TextFormatter.link_to_mention(account),
+ count: count - 1
+ )
+ else
+ I18n.t(
+ 'notification_fallbacks.admin_sign_up.title_html',
+ name: TextFormatter.link_to_mention(account)
+ )
+ end
+ when :'admin.report'
+ I18n.t(
+ 'notification_fallbacks.admin_report.title_html',
+ name: account.remote? ? account.domain : TextFormatter.link_to_mention(account),
+ target: TextFormatter.link_to_mention(object.report.target_account)
+ )
+ when :added_to_collection
+ I18n.t(
+ 'notification_fallbacks.added_to_collection.title_html',
+ name: TextFormatter.link_to_mention(account)
+ )
+ when :collection_update
+ I18n.t(
+ 'notification_fallbacks.collection_update.title_html',
+ name: account
+ )
+ end
+ end
+
+ def fallback_summary
+ case object.type
+ when :severed_relationships
+ I18n.t(
+ 'notification_fallbacks.severed_relationships.summary_html',
+ from: Rails.configuration.x.local_domain,
+ target: object.account_relationship_severance_event.target_name,
+ link: link_to(I18n.t('notification_fallbacks.generic.sign_in'), severed_relationships_url)
+ )
+ when :moderation_warning
+ I18n.t(
+ 'notification_fallbacks.moderation_warning.summary_html',
+ link: link_to(I18n.t('notification_fallbacks.generic.sign_in'), disputes_strike_url(object.account_warning.id))
+ )
+ when :'admin.sign_up', :'admin.report', :added_to_collection, :collection_update
+ I18n.t(
+ 'notification_fallbacks.generic.summary_html',
+ link: link_to(I18n.t('notification_fallbacks.generic.sign_in'), root_url)
+ )
+ end
+ end
+end
diff --git a/app/serializers/rest/notification_group_serializer.rb b/app/serializers/rest/notification_group_serializer.rb
index 3f95d78fc4..b47ff41cec 100644
--- a/app/serializers/rest/notification_group_serializer.rb
+++ b/app/serializers/rest/notification_group_serializer.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
class REST::NotificationGroupSerializer < ActiveModel::Serializer
+ include RoutingHelper
+ include ActionView::Helpers::UrlHelper
+ include NotificationFallbackConcern
+
# Please update app/javascript/mastodon/api_types/notifications.ts when making changes to the attributes
attributes :group_key, :notifications_count, :type, :most_recent_notification_id
@@ -8,6 +12,8 @@ class REST::NotificationGroupSerializer < ActiveModel::Serializer
attribute :page_max_id, if: :paginated?
attribute :latest_page_notification_at, if: :paginated?
+ attribute :fallback, if: :needs_fallback?
+
attribute :sample_account_ids
attribute :status_id, if: :status_type?
belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer
diff --git a/app/serializers/rest/notification_serializer.rb b/app/serializers/rest/notification_serializer.rb
index f337aab943..0fb809fcb0 100644
--- a/app/serializers/rest/notification_serializer.rb
+++ b/app/serializers/rest/notification_serializer.rb
@@ -1,11 +1,17 @@
# frozen_string_literal: true
class REST::NotificationSerializer < ActiveModel::Serializer
+ include RoutingHelper
+ include ActionView::Helpers::UrlHelper
+ include NotificationFallbackConcern
+
# Please update app/javascript/mastodon/api_types/notifications.ts when making changes to the attributes
attributes :id, :type, :created_at, :group_key
attribute :filtered, if: :filtered?
+ attribute :fallback, if: :needs_fallback?
+
belongs_to :from_account, key: :account, serializer: REST::AccountSerializer
belongs_to :target_status, key: :status, if: :status_type?, serializer: REST::StatusSerializer
belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 2c92862102..7904388db3 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1731,6 +1731,27 @@ en:
copy_account_note_text: 'This user moved from %{acct}, here were your previous notes about them:'
navigation:
toggle_menu: Toggle menu
+ notification_fallbacks:
+ added_to_collection:
+ title_html: "%{name} added you to a collection"
+ admin_report:
+ title_html: "%{name} reported %{target}"
+ admin_sign_up:
+ title_and_others_html:
+ one: "%{name} and one other signed up"
+ other: "%{name} and %{count} others signed up"
+ title_html: "%{name} signed up"
+ collection_update:
+ title_html: "%{name} updated a collection you are in"
+ generic:
+ sign_in: Sign in to the Mastodon web app
+ summary_html: You're on an app that does not support the most recent version of Mastodon. %link for full functionality.
+ moderation_warning:
+ summary_html: You're on an app that does not support the most recent version of Mastodon. %link
+ title: You have received a moderation warning.
+ severed_relationships:
+ summary_html: An admin from %{from} has suspended %{target}, which means you can no longer receive updates from them or interact with them. %{link} to retrieve a list of the lost relationships.
+ title: Lost connections with %{name}
notification_mailer:
admin:
report:
diff --git a/spec/serializers/rest/notification_group_serializer_spec.rb b/spec/serializers/rest/notification_group_serializer_spec.rb
index 01fd8ce0a1..bd9bbe0fc7 100644
--- a/spec/serializers/rest/notification_group_serializer_spec.rb
+++ b/spec/serializers/rest/notification_group_serializer_spec.rb
@@ -6,10 +6,16 @@ RSpec.describe REST::NotificationGroupSerializer do
subject do
serialized_record_json(
notification_group,
- described_class
+ described_class,
+ options: {
+ scope: current_user,
+ scope_name: :current_user,
+ supported_notification_types: [],
+ }
)
end
+ let(:current_user) { Fabricate(:user) }
let(:notification_group) { NotificationGroup.new pagination_data: { latest_notification_at: 3.days.ago }, notification: Fabricate(:notification), sample_accounts: [] }
context 'when latest_page_notification_at is populated' do
@@ -20,4 +26,28 @@ RSpec.describe REST::NotificationGroupSerializer do
)
end
end
+
+ shared_examples 'with fallback notifications' do |type, fabricators|
+ let(:activities) { fabricators.map { |fabricator| Fabricate(fabricator) } }
+ let(:notifications) { activities.map { |activity| Fabricate(:notification, type:, activity:, account: current_user.account) } }
+ let(:notification_group) { NotificationGroup.new(notification: notifications.last, sample_accounts: notifications.map(&:from_account)) }
+
+ it 'renders correctly' do
+ expect(subject)
+ .to include(
+ 'type' => type,
+ 'fallback' => include(
+ 'title' => anything,
+ 'summary' => anything
+ )
+ )
+ end
+ end
+
+ it_behaves_like 'with fallback notifications', 'severed_relationships', [:account_relationship_severance_event]
+ it_behaves_like 'with fallback notifications', 'moderation_warning', [:account_warning]
+ it_behaves_like 'with fallback notifications', 'admin.report', [:report]
+ it_behaves_like 'with fallback notifications', 'admin.report', [:report, :report]
+ it_behaves_like 'with fallback notifications', 'added_to_collection', [:collection_item]
+ it_behaves_like 'with fallback notifications', 'collection_update', [:collection]
end
diff --git a/spec/serializers/rest/notification_serializer_spec.rb b/spec/serializers/rest/notification_serializer_spec.rb
index b833bcb6e1..377efdb93b 100644
--- a/spec/serializers/rest/notification_serializer_spec.rb
+++ b/spec/serializers/rest/notification_serializer_spec.rb
@@ -6,10 +6,16 @@ RSpec.describe REST::NotificationSerializer do
subject do
serialized_record_json(
notification,
- described_class
+ described_class,
+ options: {
+ scope: current_user,
+ scope_name: :current_user,
+ supported_notification_types: [],
+ }
)
end
+ let(:current_user) { Fabricate(:user) }
let(:notification) { Fabricate :notification }
context 'when created_at is populated' do
@@ -20,4 +26,25 @@ RSpec.describe REST::NotificationSerializer do
)
end
end
+
+ shared_examples 'with fallback notifications' do |type, fabricator|
+ let(:notification) { Fabricate(:notification, type:, activity: Fabricate(fabricator), account: current_user.account) }
+
+ it 'renders correctly' do
+ expect(subject)
+ .to include(
+ 'type' => type,
+ 'fallback' => include(
+ 'title' => anything,
+ 'summary' => anything
+ )
+ )
+ end
+ end
+
+ it_behaves_like 'with fallback notifications', 'severed_relationships', :account_relationship_severance_event
+ it_behaves_like 'with fallback notifications', 'moderation_warning', :account_warning
+ it_behaves_like 'with fallback notifications', 'admin.report', :report
+ it_behaves_like 'with fallback notifications', 'added_to_collection', :collection_item
+ it_behaves_like 'with fallback notifications', 'collection_update', :collection
end