Revamp notification policy options (#31343)
This commit is contained in:
parent
e29c401f77
commit
cbdd8edf68
16 changed files with 442 additions and 109 deletions
|
@ -8,12 +8,12 @@ class Api::V1::Notifications::PoliciesController < Api::BaseController
|
|||
before_action :set_policy
|
||||
|
||||
def show
|
||||
render json: @policy, serializer: REST::NotificationPolicySerializer
|
||||
render json: @policy, serializer: REST::V1::NotificationPolicySerializer
|
||||
end
|
||||
|
||||
def update
|
||||
@policy.update!(resource_params)
|
||||
render json: @policy, serializer: REST::NotificationPolicySerializer
|
||||
render json: @policy, serializer: REST::V1::NotificationPolicySerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
|
38
app/controllers/api/v2/notifications/policies_controller.rb
Normal file
38
app/controllers/api/v2/notifications/policies_controller.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V2::Notifications::PoliciesController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:notifications' }, only: :show
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:notifications' }, only: :update
|
||||
|
||||
before_action :require_user!
|
||||
before_action :set_policy
|
||||
|
||||
def show
|
||||
render json: @policy, serializer: REST::NotificationPolicySerializer
|
||||
end
|
||||
|
||||
def update
|
||||
@policy.update!(resource_params)
|
||||
render json: @policy, serializer: REST::NotificationPolicySerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_policy
|
||||
@policy = NotificationPolicy.find_or_initialize_by(account: current_account)
|
||||
|
||||
with_read_replica do
|
||||
@policy.summarize!
|
||||
end
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.permit(
|
||||
:for_not_following,
|
||||
:for_not_followers,
|
||||
:for_new_accounts,
|
||||
:for_private_mentions,
|
||||
:for_limited_accounts
|
||||
)
|
||||
end
|
||||
end
|
|
@ -4,17 +4,25 @@
|
|||
#
|
||||
# Table name: notification_policies
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# account_id :bigint(8) not null
|
||||
# filter_not_following :boolean default(FALSE), not null
|
||||
# filter_not_followers :boolean default(FALSE), not null
|
||||
# filter_new_accounts :boolean default(FALSE), not null
|
||||
# filter_private_mentions :boolean default(TRUE), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# id :bigint(8) not null, primary key
|
||||
# account_id :bigint(8) not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# for_not_following :integer default("accept"), not null
|
||||
# for_not_followers :integer default("accept"), not null
|
||||
# for_new_accounts :integer default("accept"), not null
|
||||
# for_private_mentions :integer default("filter"), not null
|
||||
# for_limited_accounts :integer default("filter"), not null
|
||||
#
|
||||
|
||||
class NotificationPolicy < ApplicationRecord
|
||||
self.ignored_columns += %w(
|
||||
filter_not_following
|
||||
filter_not_followers
|
||||
filter_new_accounts
|
||||
filter_private_mentions
|
||||
)
|
||||
|
||||
belongs_to :account
|
||||
|
||||
has_many :notification_requests, primary_key: :account_id, foreign_key: :account_id, dependent: nil, inverse_of: false
|
||||
|
@ -23,11 +31,34 @@ class NotificationPolicy < ApplicationRecord
|
|||
|
||||
MAX_MEANINGFUL_COUNT = 100
|
||||
|
||||
enum :for_not_following, { accept: 0, filter: 1, drop: 2 }, suffix: :not_following
|
||||
enum :for_not_followers, { accept: 0, filter: 1, drop: 2 }, suffix: :not_followers
|
||||
enum :for_new_accounts, { accept: 0, filter: 1, drop: 2 }, suffix: :new_accounts
|
||||
enum :for_private_mentions, { accept: 0, filter: 1, drop: 2 }, suffix: :private_mentions
|
||||
enum :for_limited_accounts, { accept: 0, filter: 1, drop: 2 }, suffix: :limited_accounts
|
||||
|
||||
def summarize!
|
||||
@pending_requests_count = pending_notification_requests.first
|
||||
@pending_notifications_count = pending_notification_requests.last
|
||||
end
|
||||
|
||||
# Compat helpers with V1
|
||||
def filter_not_following=(value)
|
||||
self.for_not_following = value ? :filter : :accept
|
||||
end
|
||||
|
||||
def filter_not_followers=(value)
|
||||
self.for_not_followers = value ? :filter : :accept
|
||||
end
|
||||
|
||||
def filter_new_accounts=(value)
|
||||
self.for_new_accounts = value ? :filter : :accept
|
||||
end
|
||||
|
||||
def filter_private_mentions=(value)
|
||||
self.for_private_mentions = value ? :filter : :accept
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pending_notification_requests
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
class REST::NotificationPolicySerializer < ActiveModel::Serializer
|
||||
# Please update `app/javascript/mastodon/api_types/notification_policies.ts` when making changes to the attributes
|
||||
|
||||
attributes :filter_not_following,
|
||||
:filter_not_followers,
|
||||
:filter_new_accounts,
|
||||
:filter_private_mentions,
|
||||
attributes :for_not_following,
|
||||
:for_not_followers,
|
||||
:for_new_accounts,
|
||||
:for_private_mentions,
|
||||
:for_limited_accounts,
|
||||
:summary
|
||||
|
||||
def summary
|
||||
|
|
32
app/serializers/rest/v1/notification_policy_serializer.rb
Normal file
32
app/serializers/rest/v1/notification_policy_serializer.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::V1::NotificationPolicySerializer < ActiveModel::Serializer
|
||||
attributes :filter_not_following,
|
||||
:filter_not_followers,
|
||||
:filter_new_accounts,
|
||||
:filter_private_mentions,
|
||||
:summary
|
||||
|
||||
def summary
|
||||
{
|
||||
pending_requests_count: object.pending_requests_count.to_i,
|
||||
pending_notifications_count: object.pending_notifications_count.to_i,
|
||||
}
|
||||
end
|
||||
|
||||
def filter_not_following
|
||||
!object.accept_not_following?
|
||||
end
|
||||
|
||||
def filter_not_followers
|
||||
!object.accept_not_followers?
|
||||
end
|
||||
|
||||
def filter_new_accounts
|
||||
!object.accept_new_accounts?
|
||||
end
|
||||
|
||||
def filter_private_mentions
|
||||
!object.accept_private_mentions?
|
||||
end
|
||||
end
|
|
@ -16,59 +16,7 @@ class NotifyService < BaseService
|
|||
severed_relationships
|
||||
).freeze
|
||||
|
||||
class DismissCondition
|
||||
def initialize(notification)
|
||||
@recipient = notification.account
|
||||
@sender = notification.from_account
|
||||
@notification = notification
|
||||
end
|
||||
|
||||
def dismiss?
|
||||
blocked = @recipient.unavailable?
|
||||
blocked ||= from_self? && %i(poll severed_relationships moderation_warning).exclude?(@notification.type)
|
||||
|
||||
return blocked if message? && from_staff?
|
||||
|
||||
blocked ||= domain_blocking?
|
||||
blocked ||= @recipient.blocking?(@sender)
|
||||
blocked ||= @recipient.muting_notifications?(@sender)
|
||||
blocked ||= conversation_muted?
|
||||
blocked ||= blocked_mention? if message?
|
||||
blocked
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def blocked_mention?
|
||||
FeedManager.instance.filter?(:mentions, @notification.target_status, @recipient)
|
||||
end
|
||||
|
||||
def message?
|
||||
@notification.type == :mention
|
||||
end
|
||||
|
||||
def from_staff?
|
||||
@sender.local? && @sender.user.present? && @sender.user_role&.overrides?(@recipient.user_role) && @sender.user_role&.highlighted? && @sender.user_role&.can?(*UserRole::Flags::CATEGORIES[:moderation])
|
||||
end
|
||||
|
||||
def from_self?
|
||||
@recipient.id == @sender.id
|
||||
end
|
||||
|
||||
def domain_blocking?
|
||||
@recipient.domain_blocking?(@sender.domain) && !following_sender?
|
||||
end
|
||||
|
||||
def conversation_muted?
|
||||
@notification.target_status && @recipient.muting_conversation?(@notification.target_status.conversation)
|
||||
end
|
||||
|
||||
def following_sender?
|
||||
@recipient.following?(@sender)
|
||||
end
|
||||
end
|
||||
|
||||
class FilterCondition
|
||||
class BaseCondition
|
||||
NEW_ACCOUNT_THRESHOLD = 30.days.freeze
|
||||
|
||||
NEW_FOLLOWER_THRESHOLD = 3.days.freeze
|
||||
|
@ -82,39 +30,16 @@ class NotifyService < BaseService
|
|||
).freeze
|
||||
|
||||
def initialize(notification)
|
||||
@notification = notification
|
||||
@recipient = notification.account
|
||||
@sender = notification.from_account
|
||||
@notification = notification
|
||||
@policy = NotificationPolicy.find_or_initialize_by(account: @recipient)
|
||||
end
|
||||
|
||||
def filter?
|
||||
return false unless Notification::PROPERTIES[@notification.type][:filterable]
|
||||
return false if override_for_sender?
|
||||
|
||||
from_limited? ||
|
||||
filtered_by_not_following_policy? ||
|
||||
filtered_by_not_followers_policy? ||
|
||||
filtered_by_new_accounts_policy? ||
|
||||
filtered_by_private_mentions_policy?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filtered_by_not_following_policy?
|
||||
@policy.filter_not_following? && not_following?
|
||||
end
|
||||
|
||||
def filtered_by_not_followers_policy?
|
||||
@policy.filter_not_followers? && not_follower?
|
||||
end
|
||||
|
||||
def filtered_by_new_accounts_policy?
|
||||
@policy.filter_new_accounts? && new_account?
|
||||
end
|
||||
|
||||
def filtered_by_private_mentions_policy?
|
||||
@policy.filter_private_mentions? && not_following? && private_mention_not_in_response?
|
||||
def filterable_type?
|
||||
Notification::PROPERTIES[@notification.type][:filterable]
|
||||
end
|
||||
|
||||
def not_following?
|
||||
|
@ -174,6 +99,112 @@ class NotifyService < BaseService
|
|||
end
|
||||
end
|
||||
|
||||
class DropCondition < BaseCondition
|
||||
def drop?
|
||||
blocked = @recipient.unavailable?
|
||||
blocked ||= from_self? && %i(poll severed_relationships moderation_warning).exclude?(@notification.type)
|
||||
|
||||
return blocked if message? && from_staff?
|
||||
|
||||
blocked ||= domain_blocking?
|
||||
blocked ||= @recipient.blocking?(@sender)
|
||||
blocked ||= @recipient.muting_notifications?(@sender)
|
||||
blocked ||= conversation_muted?
|
||||
blocked ||= blocked_mention? if message?
|
||||
|
||||
return true if blocked
|
||||
return false unless filterable_type?
|
||||
return false if override_for_sender?
|
||||
|
||||
blocked_by_limited_accounts_policy? ||
|
||||
blocked_by_not_following_policy? ||
|
||||
blocked_by_not_followers_policy? ||
|
||||
blocked_by_new_accounts_policy? ||
|
||||
blocked_by_private_mentions_policy?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def blocked_mention?
|
||||
FeedManager.instance.filter?(:mentions, @notification.target_status, @recipient)
|
||||
end
|
||||
|
||||
def message?
|
||||
@notification.type == :mention
|
||||
end
|
||||
|
||||
def from_staff?
|
||||
@sender.local? && @sender.user.present? && @sender.user_role&.overrides?(@recipient.user_role) && @sender.user_role&.highlighted? && @sender.user_role&.can?(*UserRole::Flags::CATEGORIES[:moderation])
|
||||
end
|
||||
|
||||
def from_self?
|
||||
@recipient.id == @sender.id
|
||||
end
|
||||
|
||||
def domain_blocking?
|
||||
@recipient.domain_blocking?(@sender.domain) && not_following?
|
||||
end
|
||||
|
||||
def conversation_muted?
|
||||
@notification.target_status && @recipient.muting_conversation?(@notification.target_status.conversation)
|
||||
end
|
||||
|
||||
def blocked_by_not_following_policy?
|
||||
@policy.drop_not_following? && not_following?
|
||||
end
|
||||
|
||||
def blocked_by_not_followers_policy?
|
||||
@policy.drop_not_followers? && not_follower?
|
||||
end
|
||||
|
||||
def blocked_by_new_accounts_policy?
|
||||
@policy.drop_new_accounts? && new_account? && not_following?
|
||||
end
|
||||
|
||||
def blocked_by_private_mentions_policy?
|
||||
@policy.drop_private_mentions? && not_following? && private_mention_not_in_response?
|
||||
end
|
||||
|
||||
def blocked_by_limited_accounts_policy?
|
||||
@policy.drop_limited_accounts? && @sender.silenced? && not_following?
|
||||
end
|
||||
end
|
||||
|
||||
class FilterCondition < BaseCondition
|
||||
def filter?
|
||||
return false unless filterable_type?
|
||||
return false if override_for_sender?
|
||||
|
||||
filtered_by_limited_accounts_policy? ||
|
||||
filtered_by_not_following_policy? ||
|
||||
filtered_by_not_followers_policy? ||
|
||||
filtered_by_new_accounts_policy? ||
|
||||
filtered_by_private_mentions_policy?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filtered_by_not_following_policy?
|
||||
@policy.filter_not_following? && not_following?
|
||||
end
|
||||
|
||||
def filtered_by_not_followers_policy?
|
||||
@policy.filter_not_followers? && not_follower?
|
||||
end
|
||||
|
||||
def filtered_by_new_accounts_policy?
|
||||
@policy.filter_new_accounts? && new_account? && not_following?
|
||||
end
|
||||
|
||||
def filtered_by_private_mentions_policy?
|
||||
@policy.filter_private_mentions? && not_following? && private_mention_not_in_response?
|
||||
end
|
||||
|
||||
def filtered_by_limited_accounts_policy?
|
||||
@policy.filter_limited_accounts? && @sender.silenced? && not_following?
|
||||
end
|
||||
end
|
||||
|
||||
def call(recipient, type, activity)
|
||||
return if recipient.user.nil?
|
||||
|
||||
|
@ -182,7 +213,7 @@ class NotifyService < BaseService
|
|||
@notification = Notification.new(account: @recipient, type: type, activity: @activity)
|
||||
|
||||
# For certain conditions we don't need to create a notification at all
|
||||
return if dismiss?
|
||||
return if drop?
|
||||
|
||||
@notification.filtered = filter?
|
||||
@notification.group_key = notification_group_key
|
||||
|
@ -222,8 +253,8 @@ class NotifyService < BaseService
|
|||
"#{type_prefix}-#{hour_bucket}"
|
||||
end
|
||||
|
||||
def dismiss?
|
||||
DismissCondition.new(@notification).dismiss?
|
||||
def drop?
|
||||
DropCondition.new(@notification).drop?
|
||||
end
|
||||
|
||||
def filter?
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue