* Fix: #162 フォローしていないアカウントへのメンションのNGワードが編集で有効にならない問題 * Fix: 他のサーバーから来た編集についても適用
This commit is contained in:
parent
5497e2ae5d
commit
4f37ede886
6 changed files with 315 additions and 9 deletions
|
@ -5,6 +5,8 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
||||||
include Redisable
|
include Redisable
|
||||||
include Lockable
|
include Lockable
|
||||||
|
|
||||||
|
class AbortError < ::StandardError; end
|
||||||
|
|
||||||
def call(status, activity_json, object_json, request_id: nil)
|
def call(status, activity_json, object_json, request_id: nil)
|
||||||
raise ArgumentError, 'Status has unsaved changes' if status.changed?
|
raise ArgumentError, 'Status has unsaved changes' if status.changed?
|
||||||
|
|
||||||
|
@ -30,6 +32,9 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
||||||
handle_implicit_update!
|
handle_implicit_update!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@status
|
||||||
|
rescue AbortError
|
||||||
|
@status.reload
|
||||||
@status
|
@status
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -46,6 +51,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
||||||
update_poll!
|
update_poll!
|
||||||
update_immediate_attributes!
|
update_immediate_attributes!
|
||||||
update_metadata!
|
update_metadata!
|
||||||
|
validate_status_mentions!
|
||||||
create_edits!
|
create_edits!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -160,6 +166,15 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
||||||
!Admin::NgWord.reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}") && !Admin::NgWord.hashtag_reject?(@raw_tags.size)
|
!Admin::NgWord.reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}") && !Admin::NgWord.hashtag_reject?(@raw_tags.size)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_status_mentions!
|
||||||
|
raise AbortError if mention_to_stranger? && Admin::NgWord.stranger_mention_reject?("#{@status.spoiler_text}\n#{@status.text}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def mention_to_stranger?
|
||||||
|
@status.mentions.map(&:account).to_a.any? { |mentioned_account| mentioned_account.id != @status.account.id && !mentioned_account.following?(@status.account) } ||
|
||||||
|
(@status.thread.present? && @status.thread.account.id != @status.account.id && !@status.thread.account.following?(@status.account))
|
||||||
|
end
|
||||||
|
|
||||||
def update_immediate_attributes!
|
def update_immediate_attributes!
|
||||||
@status.text = @status_parser.text || ''
|
@status.text = @status_parser.text || ''
|
||||||
@status.spoiler_text = @status_parser.spoiler_text || ''
|
@status.spoiler_text = @status_parser.spoiler_text || ''
|
||||||
|
|
|
@ -214,7 +214,7 @@ class PostStatusService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def mention_to_stranger?
|
def mention_to_stranger?
|
||||||
@status.mentions.map(&:account).to_a.any? { |mentioned_account| mentioned_account.id != @account && !mentioned_account.following?(@account) } ||
|
@status.mentions.map(&:account).to_a.any? { |mentioned_account| mentioned_account.id != @account.id && !mentioned_account.following?(@account) } ||
|
||||||
(@in_reply_to && @in_reply_to.account.id != @account.id && !@in_reply_to.account.following?(@account))
|
(@in_reply_to && @in_reply_to.account.id != @account.id && !@in_reply_to.account.following?(@account))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -35,10 +35,13 @@ class UpdateStatusService < BaseService
|
||||||
update_poll! if @options.key?(:poll)
|
update_poll! if @options.key?(:poll)
|
||||||
update_immediate_attributes!
|
update_immediate_attributes!
|
||||||
create_edit! unless @options[:no_history]
|
create_edit! unless @options[:no_history]
|
||||||
|
|
||||||
|
reset_preview_card!
|
||||||
|
process_mentions_service.call(@status)
|
||||||
|
validate_status_mentions!
|
||||||
end
|
end
|
||||||
|
|
||||||
queue_poll_notifications!
|
queue_poll_notifications!
|
||||||
reset_preview_card!
|
|
||||||
update_metadata!
|
update_metadata!
|
||||||
update_references!
|
update_references!
|
||||||
broadcast_updates!
|
broadcast_updates!
|
||||||
|
@ -81,6 +84,15 @@ class UpdateStatusService < BaseService
|
||||||
raise Mastodon::ValidationError, I18n.t('statuses.too_many_hashtags') if Admin::NgWord.hashtag_reject_with_extractor?(@options[:text])
|
raise Mastodon::ValidationError, I18n.t('statuses.too_many_hashtags') if Admin::NgWord.hashtag_reject_with_extractor?(@options[:text])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_status_mentions!
|
||||||
|
raise Mastodon::ValidationError, I18n.t('statuses.contains_ng_words') if mention_to_stranger? && Setting.stranger_mention_from_local_ng && Admin::NgWord.stranger_mention_reject?("#{@options[:spoiler_text]}\n#{@options[:text]}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def mention_to_stranger?
|
||||||
|
@status.mentions.map(&:account).to_a.any? { |mentioned_account| mentioned_account.id != @status.account.id && !mentioned_account.following?(@status.account) } ||
|
||||||
|
(@status.thread.present? && @status.thread.account.id != @status.account.id && !@status.thread.account.following?(@status.account))
|
||||||
|
end
|
||||||
|
|
||||||
def validate_media!
|
def validate_media!
|
||||||
return [] if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)
|
return [] if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)
|
||||||
|
|
||||||
|
@ -167,7 +179,6 @@ class UpdateStatusService < BaseService
|
||||||
|
|
||||||
def update_metadata!
|
def update_metadata!
|
||||||
ProcessHashtagsService.new.call(@status)
|
ProcessHashtagsService.new.call(@status)
|
||||||
process_mentions_service.call(@status)
|
|
||||||
|
|
||||||
@status.update(limited_scope: :circle) if process_mentions_service.mentions?
|
@status.update(limited_scope: :circle) if process_mentions_service.mentions?
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,19 +9,24 @@ end
|
||||||
RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do
|
RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do
|
||||||
subject { described_class.new }
|
subject { described_class.new }
|
||||||
|
|
||||||
let!(:status) { Fabricate(:status, text: 'Hello world', account: Fabricate(:account, domain: 'example.com')) }
|
let(:thread) { nil }
|
||||||
|
let!(:status) { Fabricate(:status, text: 'Hello world', account: Fabricate(:account, domain: 'example.com'), thread: thread) }
|
||||||
|
let(:json_tags) do
|
||||||
|
[
|
||||||
|
{ type: 'Hashtag', name: 'hoge' },
|
||||||
|
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:content) { 'Hello universe' }
|
||||||
let(:payload) do
|
let(:payload) do
|
||||||
{
|
{
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
id: 'foo',
|
id: 'foo',
|
||||||
type: 'Note',
|
type: 'Note',
|
||||||
summary: 'Show more',
|
summary: 'Show more',
|
||||||
content: 'Hello universe',
|
content: content,
|
||||||
updated: '2021-09-08T22:39:25Z',
|
updated: '2021-09-08T22:39:25Z',
|
||||||
tag: [
|
tag: json_tags,
|
||||||
{ type: 'Hashtag', name: 'hoge' },
|
|
||||||
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
let(:json) { Oj.load(Oj.dump(payload)) }
|
let(:json) { Oj.load(Oj.dump(payload)) }
|
||||||
|
@ -462,5 +467,161 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do
|
||||||
subject.call(status, json, json)
|
subject.call(status, json, json)
|
||||||
expect(status.reload.edited_at.to_s).to eq '2021-09-08 22:39:25 UTC'
|
expect(status.reload.edited_at.to_s).to eq '2021-09-08 22:39:25 UTC'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'ng word is set' do
|
||||||
|
let(:json_tags) { [] }
|
||||||
|
|
||||||
|
context 'when hit ng words' do
|
||||||
|
let(:content) { 'ng word test' }
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(ng_words: 'test').save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to_not eq content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when not hit ng words' do
|
||||||
|
let(:content) { 'ng word aiueo' }
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(ng_words: 'test').save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to eq content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when hit ng words for mention' do
|
||||||
|
let(:json_tags) do
|
||||||
|
[
|
||||||
|
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:content) { 'ng word test' }
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to_not eq content
|
||||||
|
expect(status.mentioned_accounts.pluck(:id)).to_not include alice.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when hit ng words for mention but local posts are not checked' do
|
||||||
|
let(:json_tags) do
|
||||||
|
[
|
||||||
|
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:content) { 'ng word test' }
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '0').save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to_not eq content
|
||||||
|
expect(status.mentioned_accounts.pluck(:id)).to_not include alice.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when hit ng words for mention to follower' do
|
||||||
|
let(:json_tags) do
|
||||||
|
[
|
||||||
|
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:content) { 'ng word test' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
alice.follow!(status.account)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test').save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to eq content
|
||||||
|
expect(status.mentioned_accounts.pluck(:id)).to include alice.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when hit ng words for reply' do
|
||||||
|
let(:json_tags) do
|
||||||
|
[
|
||||||
|
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:content) { 'ng word test' }
|
||||||
|
let(:thread) { Fabricate(:status, account: alice) }
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test').save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to_not eq content
|
||||||
|
expect(status.mentioned_accounts.pluck(:id)).to_not include alice.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when hit ng words for reply to follower' do
|
||||||
|
let(:json_tags) do
|
||||||
|
[
|
||||||
|
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:content) { 'ng word test' }
|
||||||
|
let(:thread) { Fabricate(:status, account: alice) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
alice.follow!(status.account)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test').save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to eq content
|
||||||
|
expect(status.mentioned_accounts.pluck(:id)).to include alice.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when using hashtag under limit' do
|
||||||
|
let(:json_tags) do
|
||||||
|
[
|
||||||
|
{ type: 'Hashtag', name: 'a' },
|
||||||
|
{ type: 'Hashtag', name: 'b' },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:content) { 'ohagi is good' }
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(post_hash_tags_max: 2).save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to eq content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when using hashtag over limit' do
|
||||||
|
let(:json_tags) do
|
||||||
|
[
|
||||||
|
{ type: 'Hashtag', name: 'a' },
|
||||||
|
{ type: 'Hashtag', name: 'b' },
|
||||||
|
{ type: 'Hashtag', name: 'c' },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:content) { 'ohagi is good' }
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(post_hash_tags_max: 2).save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to_not eq content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -510,6 +510,26 @@ RSpec.describe PostStatusService, type: :service do
|
||||||
expect(status).to be_persisted
|
expect(status).to be_persisted
|
||||||
expect(status.text).to eq text
|
expect(status.text).to eq text
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'using hashtag under limit' do
|
||||||
|
account = Fabricate(:account)
|
||||||
|
text = '#a #b'
|
||||||
|
Form::AdminSettings.new(post_hash_tags_max: 2).save
|
||||||
|
|
||||||
|
status = subject.call(account, text: text)
|
||||||
|
|
||||||
|
expect(status).to be_persisted
|
||||||
|
expect(status.tags.count).to eq 2
|
||||||
|
expect(status.text).to eq text
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'using hashtag over limit' do
|
||||||
|
account = Fabricate(:account)
|
||||||
|
text = '#a #b #c'
|
||||||
|
Form::AdminSettings.new(post_hash_tags_max: 2).save
|
||||||
|
|
||||||
|
expect { subject.call(account, text: text) }.to raise_error Mastodon::ValidationError
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_status_with_options(**options)
|
def create_status_with_options(**options)
|
||||||
|
|
|
@ -218,4 +218,103 @@ RSpec.describe UpdateStatusService, type: :service do
|
||||||
subject.call(status, status.account_id, text: 'Bar')
|
subject.call(status, status.account_id, text: 'Bar')
|
||||||
expect(ActivityPub::DistributionWorker).to have_received(:perform_async)
|
expect(ActivityPub::DistributionWorker).to have_received(:perform_async)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'ng word is set' do
|
||||||
|
let(:account) { Fabricate(:account) }
|
||||||
|
let(:status) { PostStatusService.new.call(account, text: 'ohagi') }
|
||||||
|
|
||||||
|
it 'hit ng words' do
|
||||||
|
text = 'ng word test'
|
||||||
|
Form::AdminSettings.new(ng_words: 'test').save
|
||||||
|
|
||||||
|
expect { subject.call(status, status.account_id, text: text) }.to raise_error(Mastodon::ValidationError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'not hit ng words' do
|
||||||
|
text = 'ng word aiueo'
|
||||||
|
Form::AdminSettings.new(ng_words: 'test').save
|
||||||
|
|
||||||
|
status2 = subject.call(status, status.account_id, text: text)
|
||||||
|
|
||||||
|
expect(status2).to be_persisted
|
||||||
|
expect(status2.text).to eq text
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'hit ng words for mention' do
|
||||||
|
Fabricate(:account, username: 'ohagi', domain: nil)
|
||||||
|
text = 'ng word test @ohagi'
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save
|
||||||
|
|
||||||
|
expect { subject.call(status, status.account_id, text: text) }.to raise_error(Mastodon::ValidationError)
|
||||||
|
expect(status.reload.text).to_not eq text
|
||||||
|
expect(status.mentioned_accounts.pluck(:username)).to_not include 'ohagi'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'hit ng words for mention but local posts are not checked' do
|
||||||
|
Fabricate(:account, username: 'ohagi', domain: nil)
|
||||||
|
text = 'ng word test @ohagi'
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '0').save
|
||||||
|
|
||||||
|
status2 = subject.call(status, status.account_id, text: text)
|
||||||
|
|
||||||
|
expect(status2).to be_persisted
|
||||||
|
expect(status2.text).to eq text
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'hit ng words for mention to follower' do
|
||||||
|
mentioned = Fabricate(:account, username: 'ohagi', domain: nil)
|
||||||
|
mentioned.follow!(account)
|
||||||
|
text = 'ng word test @ohagi'
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save
|
||||||
|
|
||||||
|
status2 = subject.call(status, status.account_id, text: text)
|
||||||
|
|
||||||
|
expect(status2).to be_persisted
|
||||||
|
expect(status2.text).to eq text
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'hit ng words for reply' do
|
||||||
|
text = 'ng word test'
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save
|
||||||
|
|
||||||
|
status = PostStatusService.new.call(account, text: 'hello', thread: Fabricate(:status))
|
||||||
|
|
||||||
|
expect { subject.call(status, status.account_id, text: text) }.to raise_error(Mastodon::ValidationError)
|
||||||
|
expect(status.reload.text).to_not eq text
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'hit ng words for reply to follower' do
|
||||||
|
mentioned = Fabricate(:account, username: 'ohagi', domain: nil)
|
||||||
|
mentioned.follow!(account)
|
||||||
|
text = 'ng word test'
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save
|
||||||
|
|
||||||
|
status = PostStatusService.new.call(account, text: 'hello', thread: Fabricate(:status, account: mentioned))
|
||||||
|
|
||||||
|
status = subject.call(status, status.account_id, text: text)
|
||||||
|
|
||||||
|
expect(status).to be_persisted
|
||||||
|
expect(status.text).to eq text
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'using hashtag under limit' do
|
||||||
|
text = '#a #b'
|
||||||
|
Form::AdminSettings.new(post_hash_tags_max: 2).save
|
||||||
|
|
||||||
|
subject.call(status, status.account_id, text: text)
|
||||||
|
|
||||||
|
expect(status.reload.tags.count).to eq 2
|
||||||
|
expect(status.text).to eq text
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'using hashtag over limit' do
|
||||||
|
text = '#a #b #c'
|
||||||
|
Form::AdminSettings.new(post_hash_tags_max: 2).save
|
||||||
|
|
||||||
|
expect { subject.call(status, status.account_id, text: text) }.to raise_error Mastodon::ValidationError
|
||||||
|
|
||||||
|
expect(status.reload.tags.count).to eq 0
|
||||||
|
expect(status.text).to_not eq text
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue