Add: Webでの引用表示 (#50)
* Add compacted component * 引用表示の間にコンテナをはさみ、不要なコードを削除 * 引用APIを作成、ついでにブロック状況を引用APIに反映 * テスト修正など * 引用をキャッシュに登録 * `quote_id`が`quote_of_id`になったのをSerializerに反映 * Fix test * 引用をフィルターの対象に含める設定+エラー修正 * ストリーミングの存在しないプロパティ削除によるエラーを修正 * Fix lint * 他のサーバーから来た引用付き投稿を処理 * Fix test * フィルター設定時エラーの調整 * 画像つき投稿のスタイルを調整 * 画像つき投稿の最大高さを調整 * 引用禁止・非表示の設定を追加 * ブロック対応 * マイグレーションコード調整 * 引用設定の翻訳を作成 * Lint修正 * 参照1つの場合は引用に変換する設定を削除 * 不要になったテストを削除 * ブロック設定追加、バグ修正 * 他サーバーへ引用送信・受け入れ
This commit is contained in:
parent
3c649aa74d
commit
44b739a39a
53 changed files with 1362 additions and 120 deletions
8
spec/fabricators/status_reference_fabricator.rb
Normal file
8
spec/fabricators/status_reference_fabricator.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Fabricator(:status_reference) do
|
||||
status { Fabricate.build(:status) }
|
||||
target_status { Fabricate.build(:status) }
|
||||
attribute_type 'BT'
|
||||
quote false
|
||||
end
|
|
@ -1089,6 +1089,97 @@ RSpec.describe ActivityPub::Activity::Create do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with references' do
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
let!(:target_status) { Fabricate(:status, account: Fabricate(:account, domain: nil)) }
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
references: {
|
||||
id: 'target_status',
|
||||
type: 'Collection',
|
||||
first: {
|
||||
type: 'CollectionPage',
|
||||
next: nil,
|
||||
partOf: 'target_status',
|
||||
items: [
|
||||
ActivityPub::TagManager.instance.uri_for(target_status),
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.quote).to be_nil
|
||||
expect(status.references.pluck(:id)).to eq [target_status.id]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with quote' do
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
let!(:target_status) { Fabricate(:status, account: Fabricate(:account, domain: nil)) }
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
quote: ActivityPub::TagManager.instance.uri_for(target_status),
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.references.pluck(:id)).to eq [target_status.id]
|
||||
expect(status.quote).to_not be_nil
|
||||
expect(status.quote.id).to eq target_status.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with references and quote' do
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
let!(:target_status) { Fabricate(:status, account: Fabricate(:account, domain: nil)) }
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
quote: ActivityPub::TagManager.instance.uri_for(target_status),
|
||||
references: {
|
||||
id: 'target_status',
|
||||
type: 'Collection',
|
||||
first: {
|
||||
type: 'CollectionPage',
|
||||
next: nil,
|
||||
partOf: 'target_status',
|
||||
items: [
|
||||
ActivityPub::TagManager.instance.uri_for(target_status),
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.references.pluck(:id)).to eq [target_status.id]
|
||||
expect(status.quote).to_not be_nil
|
||||
expect(status.quote.id).to eq target_status.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with language' do
|
||||
let(:to) { 'https://www.w3.org/ns/activitystreams#Public' }
|
||||
let(:object_json) do
|
||||
|
@ -1274,6 +1365,53 @@ RSpec.describe ActivityPub::Activity::Create do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when sender quotes to local status' do
|
||||
subject { described_class.new(json, sender, delivery: true) }
|
||||
|
||||
let!(:local_status) { Fabricate(:status) }
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
quote: ActivityPub::TagManager.instance.uri_for(local_status),
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.text).to eq 'Lorem ipsum'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sender quotes to non-local status' do
|
||||
subject { described_class.new(json, sender, delivery: true) }
|
||||
|
||||
let!(:remote_status) { Fabricate(:status, uri: 'https://foo.bar/among', account: Fabricate(:account, domain: 'foo.bar', uri: 'https://foo.bar/account')) }
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
quote: ActivityPub::TagManager.instance.uri_for(remote_status),
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
expect(sender.statuses.count).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sender targets a local user' do
|
||||
subject { described_class.new(json, sender, delivery: true) }
|
||||
|
||||
|
|
|
@ -8,10 +8,11 @@ describe StatusReachFinder do
|
|||
subject { described_class.new(status) }
|
||||
|
||||
let(:parent_status) { nil }
|
||||
let(:quoted_status) { nil }
|
||||
let(:visibility) { :public }
|
||||
let(:searchability) { :public }
|
||||
let(:alice) { Fabricate(:account, username: 'alice') }
|
||||
let(:status) { Fabricate(:status, account: alice, thread: parent_status, visibility: visibility, searchability: searchability) }
|
||||
let(:status) { Fabricate(:status, account: alice, thread: parent_status, quote_of_id: quoted_status&.id, visibility: visibility, searchability: searchability) }
|
||||
|
||||
context 'with a simple case' do
|
||||
let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') }
|
||||
|
@ -165,6 +166,15 @@ describe StatusReachFinder do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is a quote to a remote account' do
|
||||
let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') }
|
||||
let(:quoted_status) { Fabricate(:status, account: bob) }
|
||||
|
||||
it 'includes the inbox of the quoted-to account' do
|
||||
expect(subject.inboxes).to include 'https://foo.bar/inbox'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with extended domain block' do
|
||||
|
|
|
@ -217,6 +217,39 @@ RSpec.describe Status do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#quote' do
|
||||
let(:target_status) { Fabricate(:status) }
|
||||
let(:quote) { true }
|
||||
|
||||
before do
|
||||
Fabricate(:status_reference, status: subject, target_status: target_status, quote: quote)
|
||||
end
|
||||
|
||||
context 'when quoting single' do
|
||||
it 'get quote' do
|
||||
expect(subject.quote).to_not be_nil
|
||||
expect(subject.quote.id).to eq target_status.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'when multiple quotes' do
|
||||
it 'get quote' do
|
||||
target2 = Fabricate(:status)
|
||||
Fabricate(:status_reference, status: subject, quote: quote)
|
||||
expect(subject.quote).to_not be_nil
|
||||
expect([target_status.id, target2.id].include?(subject.quote.id)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no quote but reference' do
|
||||
let(:quote) { false }
|
||||
|
||||
it 'get quote' do
|
||||
expect(subject.quote).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#content' do
|
||||
it 'returns the text of the status if it is not a reblog' do
|
||||
expect(subject.content).to eql subject.text
|
||||
|
@ -324,6 +357,38 @@ RSpec.describe Status do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.blocks_map' do
|
||||
subject { described_class.blocks_map([status.account.id], account) }
|
||||
|
||||
let(:status) { Fabricate(:status) }
|
||||
let(:account) { Fabricate(:account) }
|
||||
|
||||
it 'returns a hash' do
|
||||
expect(subject).to be_a Hash
|
||||
end
|
||||
|
||||
it 'contains true value' do
|
||||
account.block!(status.account)
|
||||
expect(subject[status.account.id]).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe '.domain_blocks_map' do
|
||||
subject { described_class.domain_blocks_map([status.account.domain], account) }
|
||||
|
||||
let(:status) { Fabricate(:status, account: Fabricate(:account, domain: 'foo.bar', uri: 'https://foo.bar/status')) }
|
||||
let(:account) { Fabricate(:account) }
|
||||
|
||||
it 'returns a hash' do
|
||||
expect(subject).to be_a Hash
|
||||
end
|
||||
|
||||
it 'contains true value' do
|
||||
account.block_domain!(status.account.domain)
|
||||
expect(subject[status.account.domain]).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe '.favourites_map' do
|
||||
subject { described_class.favourites_map([status], account) }
|
||||
|
||||
|
|
|
@ -167,6 +167,48 @@ RSpec.describe StatusPolicy, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with the permission of emoji_reaction?' do
|
||||
permissions :emoji_reaction? do
|
||||
it 'grants access when viewer is not blocked' do
|
||||
follow = Fabricate(:follow)
|
||||
status.account = follow.target_account
|
||||
|
||||
expect(subject).to permit(follow.account, status)
|
||||
end
|
||||
|
||||
it 'denies when viewer is blocked' do
|
||||
block = Fabricate(:block)
|
||||
status.account = block.target_account
|
||||
|
||||
expect(subject).to_not permit(block.account, status)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the permission of quote?' do
|
||||
permissions :quote? do
|
||||
it 'grants access when viewer is not blocked' do
|
||||
follow = Fabricate(:follow)
|
||||
status.account = follow.target_account
|
||||
|
||||
expect(subject).to permit(follow.account, status)
|
||||
end
|
||||
|
||||
it 'denies when viewer is blocked' do
|
||||
block = Fabricate(:block)
|
||||
status.account = block.target_account
|
||||
|
||||
expect(subject).to_not permit(block.account, status)
|
||||
end
|
||||
|
||||
it 'denies when private visibility' do
|
||||
status.visibility = :private
|
||||
|
||||
expect(subject).to_not permit(Fabricate(:account), status)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the permission of update?' do
|
||||
permissions :update? do
|
||||
it 'grants access if owner' do
|
||||
|
|
|
@ -15,12 +15,10 @@ describe ActivityPub::NoteSerializer do
|
|||
let!(:reply_by_account_visibility_direct) { Fabricate(:status, account: account, thread: parent, visibility: :direct) }
|
||||
let!(:referred) { nil }
|
||||
let!(:referred2) { nil }
|
||||
let(:convert_to_quote) { false }
|
||||
|
||||
before(:each) do
|
||||
parent.references << referred if referred.present?
|
||||
parent.references << referred2 if referred2.present?
|
||||
account.user&.settings&.[]=('single_ref_to_quote', true) if convert_to_quote
|
||||
@serialization = ActiveModelSerializers::SerializableResource.new(parent, serializer: described_class, adapter: ActivityPub::Adapter)
|
||||
end
|
||||
|
||||
|
@ -64,28 +62,4 @@ describe ActivityPub::NoteSerializer do
|
|||
expect(subject['references']['first']['items']).to include referred.uri
|
||||
end
|
||||
end
|
||||
|
||||
context 'when has quote and convert setting' do
|
||||
let(:referred) { Fabricate(:status) }
|
||||
let(:convert_to_quote) { true }
|
||||
|
||||
it 'has as quote' do
|
||||
expect(subject['quoteUri']).to_not be_nil
|
||||
expect(subject['quoteUri']).to eq referred.uri
|
||||
expect(subject['_misskey_quote']).to eq referred.uri
|
||||
expect(subject['references']['first']['items']).to include referred.uri
|
||||
end
|
||||
end
|
||||
|
||||
context 'when has multiple references and convert setting' do
|
||||
let(:referred) { Fabricate(:status) }
|
||||
let(:referred2) { Fabricate(:status) }
|
||||
let(:convert_to_quote) { true }
|
||||
|
||||
it 'has as quote' do
|
||||
expect(subject['quoteUri']).to be_nil
|
||||
expect(subject['references']['first']['items']).to include referred.uri
|
||||
expect(subject['references']['first']['items']).to include referred2.uri
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,6 +10,7 @@ RSpec.describe ProcessReferencesService, type: :service do
|
|||
let(:status) { Fabricate(:status, account: account, text: text, visibility: visibility) }
|
||||
let(:target_status) { Fabricate(:status, account: Fabricate(:user).account, visibility: target_status_visibility) }
|
||||
let(:target_status_uri) { ActivityPub::TagManager.instance.uri_for(target_status) }
|
||||
let(:quote_urls) { nil }
|
||||
|
||||
def notify?(target_status_id = nil)
|
||||
target_status_id ||= target_status.id
|
||||
|
@ -18,7 +19,7 @@ RSpec.describe ProcessReferencesService, type: :service do
|
|||
|
||||
describe 'posting new status' do
|
||||
subject do
|
||||
described_class.new.call(status, reference_parameters, urls: urls, fetch_remote: fetch_remote)
|
||||
described_class.new.call(status, reference_parameters, urls: urls, fetch_remote: fetch_remote, quote_urls: quote_urls)
|
||||
status.reference_objects.pluck(:target_status_id, :attribute_type)
|
||||
end
|
||||
|
||||
|
@ -35,6 +36,10 @@ RSpec.describe ProcessReferencesService, type: :service do
|
|||
expect(subject.pluck(1)).to include 'RT'
|
||||
expect(notify?).to be true
|
||||
end
|
||||
|
||||
it 'not quote' do
|
||||
expect(status.quote).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when multiple references' do
|
||||
|
@ -86,6 +91,48 @@ RSpec.describe ProcessReferencesService, type: :service do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with quote as parameter only' do
|
||||
let(:text) { 'Hello' }
|
||||
let(:quote_urls) { [ActivityPub::TagManager.instance.uri_for(target_status)] }
|
||||
|
||||
it 'post status' do
|
||||
expect(subject.size).to eq 1
|
||||
expect(subject.pluck(0)).to include target_status.id
|
||||
expect(subject.pluck(1)).to include 'QT'
|
||||
expect(status.quote).to_not be_nil
|
||||
expect(status.quote.id).to eq target_status.id
|
||||
expect(notify?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'with quote as parameter and embed' do
|
||||
let(:text) { "Hello QT #{target_status_uri}" }
|
||||
let(:quote_urls) { [ActivityPub::TagManager.instance.uri_for(target_status)] }
|
||||
|
||||
it 'post status' do
|
||||
expect(subject.size).to eq 1
|
||||
expect(subject.pluck(0)).to include target_status.id
|
||||
expect(subject.pluck(1)).to include 'QT'
|
||||
expect(status.quote).to_not be_nil
|
||||
expect(status.quote.id).to eq target_status.id
|
||||
expect(notify?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'with quote as parameter but embed is not quote' do
|
||||
let(:text) { "Hello RE #{target_status_uri}" }
|
||||
let(:quote_urls) { [ActivityPub::TagManager.instance.uri_for(target_status)] }
|
||||
|
||||
it 'post status' do
|
||||
expect(subject.size).to eq 1
|
||||
expect(subject.pluck(0)).to include target_status.id
|
||||
expect(subject.pluck(1)).to include 'QT'
|
||||
expect(status.quote).to_not be_nil
|
||||
expect(status.quote.id).to eq target_status.id
|
||||
expect(notify?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'with quote and reference' do
|
||||
let(:target_status2) { Fabricate(:status) }
|
||||
let(:target_status2_uri) { ActivityPub::TagManager.instance.uri_for(target_status2) }
|
||||
|
@ -240,6 +287,17 @@ RSpec.describe ProcessReferencesService, type: :service do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when remove quote' do
|
||||
let(:text) { "QT #{target_status_uri}" }
|
||||
let(:new_text) { 'Hello' }
|
||||
|
||||
it 'post status' do
|
||||
expect(subject.size).to eq 0
|
||||
expect(status.quote).to be_nil
|
||||
expect(notify?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when change reference' do
|
||||
let(:text) { "BT #{target_status_uri}" }
|
||||
let(:new_text) { "BT #{target_status2_uri}" }
|
||||
|
@ -250,5 +308,43 @@ RSpec.describe ProcessReferencesService, type: :service do
|
|||
expect(notify?(target_status2.id)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when change quote' do
|
||||
let(:text) { "QT #{target_status_uri}" }
|
||||
let(:new_text) { "QT #{target_status2_uri}" }
|
||||
|
||||
it 'post status' do
|
||||
expect(subject.size).to eq 1
|
||||
expect(subject).to include target_status2.id
|
||||
expect(status.quote).to_not be_nil
|
||||
expect(status.quote.id).to eq target_status2.id
|
||||
expect(notify?(target_status2.id)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when change quote to reference', pending: 'Will fix later' do
|
||||
let(:text) { "QT #{target_status_uri}" }
|
||||
let(:new_text) { "RT #{target_status_uri}" }
|
||||
|
||||
it 'post status' do
|
||||
expect(subject.size).to eq 1
|
||||
expect(subject).to include target_status.id
|
||||
expect(status.quote).to be_nil
|
||||
expect(notify?(target_status.id)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when change reference to quote', pending: 'Will fix later' do
|
||||
let(:text) { "RT #{target_status_uri}" }
|
||||
let(:new_text) { "QT #{target_status_uri}" }
|
||||
|
||||
it 'post status' do
|
||||
expect(subject.size).to eq 1
|
||||
expect(subject).to include target_status.id
|
||||
expect(status.quote).to_not be_nil
|
||||
expect(status.quote.id).to eq target_status.id
|
||||
expect(notify?(target_status.id)).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue