parent
4fcb28e081
commit
9c493c20b9
@ -31,7 +31,8 @@ class Api::V1::Notifications::PoliciesController < Api::BaseController
|
||||
:filter_not_following,
|
||||
:filter_not_followers,
|
||||
:filter_new_accounts,
|
||||
:filter_private_mentions
|
||||
:filter_private_mentions,
|
||||
:filter_bots
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@ -32,7 +32,8 @@ class Api::V2::Notifications::PoliciesController < Api::BaseController
|
||||
:for_not_followers,
|
||||
:for_new_accounts,
|
||||
:for_private_mentions,
|
||||
:for_limited_accounts
|
||||
:for_limited_accounts,
|
||||
:for_bots
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@ -8,6 +8,7 @@ export interface NotificationPolicyJSON {
|
||||
for_new_accounts: NotificationPolicyValue;
|
||||
for_private_mentions: NotificationPolicyValue;
|
||||
for_limited_accounts: NotificationPolicyValue;
|
||||
for_bots: NotificationPolicyValue;
|
||||
summary: {
|
||||
pending_requests_count: number;
|
||||
pending_notifications_count: number;
|
||||
|
||||
@ -88,6 +88,13 @@ export const PolicyControls: React.FC = () => {
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
const handleFilterBots = useCallback(
|
||||
(value: string) => {
|
||||
changeFilter(dispatch, 'for_bots', value);
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
if (!notificationPolicy) return null;
|
||||
|
||||
const options = [
|
||||
@ -209,6 +216,24 @@ export const PolicyControls: React.FC = () => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<SelectWithLabel
|
||||
value={notificationPolicy.for_bots}
|
||||
onChange={handleFilterBots}
|
||||
options={options}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='notifications.policy.filter_bots_title'
|
||||
defaultMessage='Bots'
|
||||
/>
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='notifications.policy.filter_bots_hint'
|
||||
defaultMessage='Accounts marked as automated'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@ -48,6 +48,9 @@ export const IgnoreNotificationsModal = ({ filterType }) => {
|
||||
case 'for_limited_accounts':
|
||||
title = <FormattedMessage id='ignore_notifications_modal.limited_accounts_title' defaultMessage='Ignore notifications from moderated accounts?' />;
|
||||
break;
|
||||
case 'for_bots':
|
||||
title = <FormattedMessage id='ignore_notifications_modal.bots_title' defaultMessage='Ignore notifications from bots?' />;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -780,6 +780,7 @@
|
||||
"home.pending_critical_update.link": "See updates",
|
||||
"home.pending_critical_update.title": "Critical security update available!",
|
||||
"home.show_announcements": "Show announcements",
|
||||
"ignore_notifications_modal.bots_title": "Ignore notifications from bots?",
|
||||
"ignore_notifications_modal.disclaimer": "Mastodon cannot inform users that you've ignored their notifications. Ignoring notifications will not stop the messages themselves from being sent.",
|
||||
"ignore_notifications_modal.filter_instead": "Filter instead",
|
||||
"ignore_notifications_modal.filter_to_act_users": "You'll still be able to accept, reject, or report users",
|
||||
@ -1042,6 +1043,8 @@
|
||||
"notifications.policy.drop": "Ignore",
|
||||
"notifications.policy.drop_hint": "Send to the void, never to be seen again",
|
||||
"notifications.policy.filter": "Filter",
|
||||
"notifications.policy.filter_bots_hint": "Accounts marked as automated",
|
||||
"notifications.policy.filter_bots_title": "Bots",
|
||||
"notifications.policy.filter_hint": "Send to filtered notifications inbox",
|
||||
"notifications.policy.filter_limited_accounts_hint": "Limited by server moderators",
|
||||
"notifications.policy.filter_limited_accounts_title": "Moderated accounts",
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
# Table name: notification_policies
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# for_bots :integer default("accept"), not null
|
||||
# for_limited_accounts :integer default("filter"), not null
|
||||
# for_new_accounts :integer default("accept"), not null
|
||||
# for_not_followers :integer default("accept"), not null
|
||||
@ -36,6 +37,7 @@ class NotificationPolicy < ApplicationRecord
|
||||
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
|
||||
enum :for_bots, { accept: 0, filter: 1, drop: 2 }, suffix: :bots
|
||||
|
||||
def summarize!
|
||||
@pending_requests_count = pending_notification_requests.first
|
||||
@ -59,6 +61,10 @@ class NotificationPolicy < ApplicationRecord
|
||||
self.for_private_mentions = value ? :filter : :accept
|
||||
end
|
||||
|
||||
def filter_bots=(value)
|
||||
self.for_bots = value ? :filter : :accept
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pending_notification_requests
|
||||
|
||||
@ -8,6 +8,7 @@ class REST::NotificationPolicySerializer < ActiveModel::Serializer
|
||||
:for_new_accounts,
|
||||
:for_private_mentions,
|
||||
:for_limited_accounts,
|
||||
:for_bots,
|
||||
:summary
|
||||
|
||||
def summary
|
||||
|
||||
@ -5,6 +5,7 @@ class REST::V1::NotificationPolicySerializer < ActiveModel::Serializer
|
||||
:filter_not_followers,
|
||||
:filter_new_accounts,
|
||||
:filter_private_mentions,
|
||||
:filter_bots,
|
||||
:summary
|
||||
|
||||
def summary
|
||||
@ -29,4 +30,8 @@ class REST::V1::NotificationPolicySerializer < ActiveModel::Serializer
|
||||
def filter_private_mentions
|
||||
!object.accept_private_mentions?
|
||||
end
|
||||
|
||||
def filter_bots
|
||||
!object.accept_bots?
|
||||
end
|
||||
end
|
||||
|
||||
@ -97,6 +97,10 @@ class NotifyService < BaseService
|
||||
WHERE ancestors.mention_id IS NOT NULL AND s.account_id = :recipient_id AND s.visibility = 3
|
||||
SQL
|
||||
end
|
||||
|
||||
def from_bot?
|
||||
@sender.bot?
|
||||
end
|
||||
end
|
||||
|
||||
class DropCondition < BaseCondition
|
||||
@ -120,7 +124,8 @@ class NotifyService < BaseService
|
||||
blocked_by_not_following_policy? ||
|
||||
blocked_by_not_followers_policy? ||
|
||||
blocked_by_new_accounts_policy? ||
|
||||
blocked_by_private_mentions_policy?
|
||||
blocked_by_private_mentions_policy? ||
|
||||
blocked_by_bots_policy?
|
||||
end
|
||||
|
||||
private
|
||||
@ -160,6 +165,10 @@ class NotifyService < BaseService
|
||||
def blocked_by_limited_accounts_policy?
|
||||
@policy.drop_limited_accounts? && (@options[:silenced] || @sender.silenced?) && not_following?
|
||||
end
|
||||
|
||||
def blocked_by_bots_policy?
|
||||
@policy.drop_bots? && from_bot?
|
||||
end
|
||||
end
|
||||
|
||||
class FilterCondition < BaseCondition
|
||||
@ -172,7 +181,8 @@ class NotifyService < BaseService
|
||||
filtered_by_not_following_policy? ||
|
||||
filtered_by_not_followers_policy? ||
|
||||
filtered_by_new_accounts_policy? ||
|
||||
filtered_by_private_mentions_policy?
|
||||
filtered_by_private_mentions_policy? ||
|
||||
filtered_by_bots_policy?
|
||||
end
|
||||
|
||||
private
|
||||
@ -196,6 +206,10 @@ class NotifyService < BaseService
|
||||
def filtered_by_limited_accounts_policy?
|
||||
@policy.filter_limited_accounts? && (@options[:silenced] || @sender.silenced?) && not_following?
|
||||
end
|
||||
|
||||
def filtered_by_bots_policy?
|
||||
@policy.filter_bots? && from_bot?
|
||||
end
|
||||
end
|
||||
|
||||
def call(recipient, type, activity, **options)
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddForBotsToNotificationPolicies < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
add_column :notification_policies, :for_bots, :integer, default: 0, null: false
|
||||
end
|
||||
end
|
||||
@ -818,6 +818,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_05_05_155103) do
|
||||
create_table "notification_policies", force: :cascade do |t|
|
||||
t.bigint "account_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.integer "for_bots", default: 0, null: false
|
||||
t.integer "for_limited_accounts", default: 1, null: false
|
||||
t.integer "for_new_accounts", default: 0, null: false
|
||||
t.integer "for_not_followers", default: 0, null: false
|
||||
|
||||
@ -28,4 +28,33 @@ RSpec.describe NotificationPolicy do
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'for_bots attribute' do
|
||||
subject { Fabricate(:notification_policy) }
|
||||
|
||||
it 'defaults to accept' do
|
||||
expect(subject.for_bots).to eq 'accept'
|
||||
end
|
||||
|
||||
it 'can be set to filter' do
|
||||
subject.update!(for_bots: 'filter')
|
||||
expect(subject.reload.for_bots).to eq 'filter'
|
||||
end
|
||||
|
||||
it 'can be set to drop' do
|
||||
subject.update!(for_bots: 'drop')
|
||||
expect(subject.reload.for_bots).to eq 'drop'
|
||||
end
|
||||
|
||||
it 'can be set to accept' do
|
||||
subject.update!(for_bots: 'accept')
|
||||
expect(subject.reload.for_bots).to eq 'accept'
|
||||
end
|
||||
|
||||
it 'validates input' do
|
||||
expect do
|
||||
subject.update!(for_bots: 'block')
|
||||
end.to raise_error(ArgumentError, "'block' is not a valid for_bots")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -33,6 +33,7 @@ RSpec.describe 'Policies' do
|
||||
filter_not_followers: false,
|
||||
filter_new_accounts: false,
|
||||
filter_private_mentions: true,
|
||||
filter_bots: false,
|
||||
summary: a_hash_including(
|
||||
pending_requests_count: 1,
|
||||
pending_notifications_count: 0
|
||||
@ -63,11 +64,17 @@ RSpec.describe 'Policies' do
|
||||
filter_not_followers: false,
|
||||
filter_new_accounts: false,
|
||||
filter_private_mentions: true,
|
||||
filter_bots: false,
|
||||
summary: a_hash_including(
|
||||
pending_requests_count: 0,
|
||||
pending_notifications_count: 0
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
it 'updates filter_bots' do
|
||||
put '/api/v1/notifications/policy', headers: headers, params: { filter_bots: true }
|
||||
expect(response.parsed_body).to include(filter_bots: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -34,6 +34,7 @@ RSpec.describe 'Policies' do
|
||||
for_new_accounts: 'accept',
|
||||
for_private_mentions: 'filter',
|
||||
for_limited_accounts: 'filter',
|
||||
for_bots: 'accept',
|
||||
summary: a_hash_including(
|
||||
pending_requests_count: 1,
|
||||
pending_notifications_count: 0
|
||||
@ -66,6 +67,7 @@ RSpec.describe 'Policies' do
|
||||
for_new_accounts: 'accept',
|
||||
for_private_mentions: 'filter',
|
||||
for_limited_accounts: 'drop',
|
||||
for_bots: 'accept',
|
||||
summary: a_hash_including(
|
||||
pending_requests_count: 0,
|
||||
pending_notifications_count: 0
|
||||
@ -73,4 +75,13 @@ RSpec.describe 'Policies' do
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'updating bots policy' do
|
||||
it 'accepts for_bots parameter' do
|
||||
put '/api/v2/notifications/policy', headers: headers, params: { for_bots: 'filter' }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.parsed_body).to include(for_bots: 'filter')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -272,6 +272,61 @@ RSpec.describe NotifyService do
|
||||
expect(subject.drop?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'with bot policies' do
|
||||
let(:bot_sender) { Fabricate(:account, bot: true) }
|
||||
let(:human_sender) { Fabricate(:account, bot: false) }
|
||||
let(:original_status) { Fabricate(:status) }
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
|
||||
def reblog_notification(from)
|
||||
activity = Fabricate(:status, account: from, reblog: original_status)
|
||||
Fabricate(:notification, type: :reblog, activity: activity, from_account: from, account: recipient)
|
||||
end
|
||||
|
||||
before do
|
||||
recipient.create_notification_policy!(
|
||||
for_not_following: :accept,
|
||||
for_not_followers: :accept,
|
||||
for_new_accounts: :accept,
|
||||
for_private_mentions: :accept,
|
||||
for_limited_accounts: :accept,
|
||||
for_bots: bots_policy
|
||||
)
|
||||
end
|
||||
|
||||
context 'when recipient is dropping bots' do
|
||||
let(:bots_policy) { :drop }
|
||||
|
||||
it 'drops bot reblogs' do
|
||||
notification = reblog_notification(bot_sender)
|
||||
expect(described_class.new(notification).drop?).to be true
|
||||
end
|
||||
|
||||
it 'keeps human reblogs' do
|
||||
notification = reblog_notification(human_sender)
|
||||
expect(described_class.new(notification).drop?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when recipient is filtering bots' do
|
||||
let(:bots_policy) { :filter }
|
||||
|
||||
it 'does not drop bot reblogs' do
|
||||
notification = reblog_notification(bot_sender)
|
||||
expect(described_class.new(notification).drop?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when recipient is accepting bots' do
|
||||
let(:bots_policy) { :accept }
|
||||
|
||||
it 'does not drop bot reblogs' do
|
||||
notification = reblog_notification(bot_sender)
|
||||
expect(described_class.new(notification).drop?).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -518,6 +573,61 @@ RSpec.describe NotifyService do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with bot policies' do
|
||||
let(:bot_sender) { Fabricate(:account, bot: true) }
|
||||
let(:human_sender) { Fabricate(:account, bot: false) }
|
||||
let(:original_status) { Fabricate(:status) }
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
|
||||
def reblog_notification(from)
|
||||
activity = Fabricate(:status, account: from, reblog: original_status)
|
||||
Fabricate(:notification, type: :reblog, activity: activity, from_account: from, account: recipient)
|
||||
end
|
||||
|
||||
before do
|
||||
recipient.create_notification_policy!(
|
||||
for_not_following: :accept,
|
||||
for_not_followers: :accept,
|
||||
for_new_accounts: :accept,
|
||||
for_private_mentions: :accept,
|
||||
for_limited_accounts: :accept,
|
||||
for_bots: bots_policy
|
||||
)
|
||||
end
|
||||
|
||||
context 'when recipient is dropping bots' do
|
||||
let(:bots_policy) { :drop }
|
||||
|
||||
it 'does not filter bot reblogs' do
|
||||
notification = reblog_notification(bot_sender)
|
||||
expect(described_class.new(notification).filter?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when recipient is filtering bots' do
|
||||
let(:bots_policy) { :filter }
|
||||
|
||||
it 'filters bot reblogs' do
|
||||
notification = reblog_notification(bot_sender)
|
||||
expect(described_class.new(notification).filter?).to be true
|
||||
end
|
||||
|
||||
it 'keeps human reblogs' do
|
||||
notification = reblog_notification(human_sender)
|
||||
expect(described_class.new(notification).filter?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when recipient is accepting bots' do
|
||||
let(:bots_policy) { :accept }
|
||||
|
||||
it 'does not filter bot reblogs' do
|
||||
notification = reblog_notification(bot_sender)
|
||||
expect(described_class.new(notification).filter?).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user