mirror of
https://github.com/mastodon/mastodon.git
synced 2026-09-28 00:08:40 -05:00
Add support for Link objects in attachment (#36104)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
@@ -50,6 +50,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||
@tags = []
|
||||
@mentions = []
|
||||
@tagged_objects = []
|
||||
@links = []
|
||||
@unresolved_mentions = []
|
||||
@unresolved_collections = []
|
||||
@silenced_account_ids = []
|
||||
@@ -82,7 +83,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||
|
||||
def distribute
|
||||
# Spread out crawling randomly to avoid DDoSing the link
|
||||
LinkCrawlWorker.perform_in(rand(DISTRIBUTE_DELAY), @status.id)
|
||||
LinkCrawlWorker.perform_in(rand(DISTRIBUTE_DELAY), @status.id, @links.first)
|
||||
|
||||
# Distribute into home and list feeds and notify mentioned accounts
|
||||
::DistributionWorker.perform_async(@status.id, { 'silenced_account_ids' => @silenced_account_ids }) if @options[:override_timestamps] || @status.within_realtime_window?
|
||||
@@ -297,6 +298,12 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||
media_attachments = []
|
||||
|
||||
as_array(@object['attachment']).each do |attachment|
|
||||
if attachment['href'].present?
|
||||
preview_card_parser = ActivityPub::Parser::PreviewCardParser.new(attachment)
|
||||
@links << preview_card_parser.url if preview_card_parser.url.present?
|
||||
next
|
||||
end
|
||||
|
||||
media_attachment_parser = ActivityPub::Parser::MediaAttachmentParser.new(attachment)
|
||||
|
||||
next if media_attachment_parser.remote_url.blank? || media_attachments.size >= Status::MEDIA_ATTACHMENTS_LIMIT
|
||||
|
||||
21
app/lib/activitypub/parser/preview_card_parser.rb
Normal file
21
app/lib/activitypub/parser/preview_card_parser.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::Parser::PreviewCardParser
|
||||
include JsonLdHelper
|
||||
|
||||
def initialize(json)
|
||||
@json = json
|
||||
end
|
||||
|
||||
# @param [PreviewCard] previous_record
|
||||
def significantly_changes?(previous_record)
|
||||
url != previous_record.url
|
||||
end
|
||||
|
||||
def url
|
||||
url = Addressable::URI.parse(@json['href'])&.normalize&.to_s
|
||||
url unless unsupported_uri_scheme?(url)
|
||||
rescue Addressable::URI::InvalidURIError
|
||||
nil
|
||||
end
|
||||
end
|
||||
@@ -135,7 +135,7 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
||||
end
|
||||
|
||||
def virtual_attachments
|
||||
object.ordered_media_attachments
|
||||
object.ordered_media_attachments + [object.preview_card].compact
|
||||
end
|
||||
|
||||
def virtual_tags
|
||||
@@ -248,6 +248,18 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
||||
}
|
||||
end
|
||||
|
||||
class PreviewCardSerializer < ActivityPub::Serializer
|
||||
attributes :type, :href
|
||||
|
||||
def type
|
||||
'Link'
|
||||
end
|
||||
|
||||
def href
|
||||
object.original_url.presence || object.url
|
||||
end
|
||||
end
|
||||
|
||||
class MediaAttachmentSerializer < ActivityPub::Serializer
|
||||
context_extensions :blurhash, :focal_point
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
||||
@quote_changed = false
|
||||
@request_id = request_id
|
||||
@quote = nil
|
||||
@next_media_attachments = []
|
||||
@next_links = []
|
||||
|
||||
return @status if !expected_type? || already_updated_more_recently?
|
||||
|
||||
@@ -87,9 +89,14 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
||||
def update_media_attachments!
|
||||
previous_media_attachments = @status.media_attachments.to_a
|
||||
previous_media_attachments_ids = @status.ordered_media_attachment_ids || previous_media_attachments.map(&:id)
|
||||
@next_media_attachments = []
|
||||
|
||||
as_array(@json['attachment']).each do |attachment|
|
||||
if attachment['href'].present?
|
||||
preview_card_parser = ActivityPub::Parser::PreviewCardParser.new(attachment)
|
||||
@next_links << preview_card_parser.url if preview_card_parser.url.present?
|
||||
next
|
||||
end
|
||||
|
||||
media_attachment_parser = ActivityPub::Parser::MediaAttachmentParser.new(attachment)
|
||||
|
||||
next if media_attachment_parser.remote_url.blank? || @next_media_attachments.size > Status::MEDIA_ATTACHMENTS_LIMIT
|
||||
@@ -385,7 +392,8 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
||||
|
||||
def update_counts!
|
||||
likes = @status_parser.favourites_count
|
||||
shares = @status_parser.reblogs_count
|
||||
shares = @status_parser.reblogs_count
|
||||
|
||||
return if likes.nil? && shares.nil?
|
||||
|
||||
@status.status_stat.tap do |status_stat|
|
||||
@@ -438,7 +446,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
||||
|
||||
def reset_preview_card!
|
||||
@status.reset_preview_card!
|
||||
LinkCrawlWorker.perform_in(rand(CRAWL_DELAY), @status.id)
|
||||
LinkCrawlWorker.perform_in(rand(CRAWL_DELAY), @status.id, @next_links.first)
|
||||
end
|
||||
|
||||
def broadcast_updates!
|
||||
|
||||
@@ -15,9 +15,9 @@ class FetchLinkCardService < BaseService
|
||||
)
|
||||
}iox
|
||||
|
||||
def call(status)
|
||||
def call(status, original_url = nil)
|
||||
@status = status
|
||||
@original_url = parse_urls
|
||||
@original_url = original_url || parse_urls
|
||||
|
||||
return if @original_url.nil? || @status.with_preview_card? || @status.with_media? || @status.quote.present?
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ class LinkCrawlWorker
|
||||
|
||||
sidekiq_options queue: 'pull', retry: 0
|
||||
|
||||
def perform(status_id)
|
||||
FetchLinkCardService.new.call(Status.find(status_id))
|
||||
def perform(status_id, url = nil)
|
||||
FetchLinkCardService.new.call(Status.find(status_id), url)
|
||||
rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordNotUnique
|
||||
true
|
||||
end
|
||||
|
||||
@@ -277,24 +277,16 @@ RSpec.describe ActivityPub::Activity::Create do
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a standalone' do
|
||||
context 'with a standalone post' do
|
||||
let(:object_json) { build_object }
|
||||
|
||||
it 'creates status' do
|
||||
it 'creates a status with expected text and direct privacy' do
|
||||
expect { subject.perform }.to change(sender.statuses, :count).by(1)
|
||||
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.text).to eq 'Lorem ipsum'
|
||||
end
|
||||
|
||||
it 'missing to/cc defaults to direct privacy' do
|
||||
expect { subject.perform }.to change(sender.statuses, :count).by(1)
|
||||
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.visibility).to eq 'direct'
|
||||
end
|
||||
end
|
||||
@@ -333,6 +325,55 @@ RSpec.describe ActivityPub::Activity::Create do
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a public status mentioning multiple links' do
|
||||
let(:object_json) do
|
||||
build_object(
|
||||
to: 'as:Public',
|
||||
content: 'This is a test <a href="https://joinmastodon.org">https://joinmastodon.org</a> <a href="https://activitypub.rocks/">https://activitypub.rocks/</a>'
|
||||
)
|
||||
end
|
||||
|
||||
it 'creates a status and schedules link fetching job for first link' do
|
||||
expect { subject.perform }
|
||||
.to change(sender.statuses, :count).by(1)
|
||||
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
|
||||
expect(LinkCrawlWorker).to have_enqueued_sidekiq_job(status.id, nil)
|
||||
|
||||
expect(status.visibility).to eq 'public'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a public status specifying a link attachment (FEP-8967)' do
|
||||
let(:object_json) do
|
||||
build_object(
|
||||
to: 'as:Public',
|
||||
content: 'This is a test <a href="https://joinmastodon.org">https://joinmastodon.org</a> <a href="https://activitypub.rocks/">https://activitypub.rocks/</a>',
|
||||
attachment: [
|
||||
{
|
||||
href: 'https://activitypub.rocks/',
|
||||
},
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
it 'creates a status and schedules link fetching job for specified link' do
|
||||
expect { subject.perform }
|
||||
.to change(sender.statuses, :count).by(1)
|
||||
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
|
||||
expect(LinkCrawlWorker).to have_enqueued_sidekiq_job(status.id, 'https://activitypub.rocks/')
|
||||
|
||||
expect(status.visibility).to eq 'public'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when public with Public' do
|
||||
let(:object_json) do
|
||||
build_object(
|
||||
|
||||
@@ -107,4 +107,23 @@ RSpec.describe ActivityPub::NoteSerializer do
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a preview card' do
|
||||
let(:preview_card) { Fabricate(:preview_card) }
|
||||
|
||||
before do
|
||||
PreviewCardsStatus.create(status: parent, preview_card: preview_card)
|
||||
end
|
||||
|
||||
it 'has the expected shape (using FEP-8967)' do
|
||||
expect(subject).to include({
|
||||
'type' => 'Note',
|
||||
'attachment' => contain_exactly(
|
||||
a_hash_including(
|
||||
'href' => preview_card.url
|
||||
)
|
||||
),
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user