diff --git a/app/lib/activitypub/activity/like.rb b/app/lib/activitypub/activity/like.rb index ebfac9a868..5e34cc9dc5 100644 --- a/app/lib/activitypub/activity/like.rb +++ b/app/lib/activitypub/activity/like.rb @@ -7,7 +7,7 @@ class ActivityPub::Activity::Like < ActivityPub::Activity def perform @original_status = status_from_uri(object_uri) - return if @original_status.nil? || delete_arrived_first?(@json['id']) || reject_favourite? + return if @original_status.nil? || delete_arrived_first?(@json['id']) || block_domain? || reject_favourite? if shortcode.nil? || !Setting.enable_emoji_reaction process_favourite @@ -34,19 +34,11 @@ class ActivityPub::Activity::Like < ActivityPub::Activity def process_emoji_reaction return if !@original_status.account.local? && !Setting.receive_other_servers_emoji_reaction + # custom emoji + emoji = nil if emoji_tag.present? - return if emoji_tag['id'].blank? || emoji_tag['name'].blank? || emoji_tag['icon'].blank? || emoji_tag['icon']['url'].blank? - - image_url = emoji_tag['icon']['url'] - uri = emoji_tag['id'] - domain = URI.split(uri)[2] - - emoji = CustomEmoji.find_or_create_by!(shortcode: shortcode, domain: domain) do |emoji_data| - emoji_data.uri = uri - emoji_data.image_remote_url = image_url - end - - Trends.statuses.register(@original_status) + emoji = process_emoji(emoji_tag) + return if emoji.nil? end reaction = nil @@ -58,6 +50,7 @@ class ActivityPub::Activity::Like < ActivityPub::Activity reaction = @original_status.emoji_reactions.create!(account: @account, name: shortcode, custom_emoji: emoji, uri: @json['id']) end + Trends.statuses.register(@original_status) write_stream(reaction) if @original_status.account.local? @@ -95,6 +88,45 @@ class ActivityPub::Activity::Like < ActivityPub::Activity end end + def process_emoji(tag) + custom_emoji_parser = ActivityPub::Parser::CustomEmojiParser.new(tag) + + return if custom_emoji_parser.shortcode.blank? || custom_emoji_parser.image_remote_url.blank? + + emoji = CustomEmoji.find_by(shortcode: custom_emoji_parser.shortcode, domain: @account.domain) + + return unless emoji.nil? || custom_emoji_parser.image_remote_url != emoji.image_remote_url || (custom_emoji_parser.updated_at && custom_emoji_parser.updated_at >= emoji.updated_at) + + domain = emoji_tag['domain'] || URI.split(custom_emoji_parser.uri)[2] || @account.domain + domain = nil if domain == Rails.configuration.x.local_domain || domain == Rails.configuration.x.web_domain + + return if domain.present? && skip_download?(domain) + + begin + emoji ||= CustomEmoji.new( + domain: domain, + shortcode: custom_emoji_parser.shortcode, + uri: custom_emoji_parser.uri, + is_sensitive: custom_emoji_parser.is_sensitive, + license: custom_emoji_parser.license + ) + emoji.image_remote_url = custom_emoji_parser.image_remote_url + emoji.save + rescue Seahorse::Client::NetworkingError => e + Rails.logger.warn "Error storing emoji: #{e}" + end + + emoji + end + + def skip_download?(domain) + DomainBlock.reject_media?(domain) + end + + def block_domain? + DomainBlock.blocked?(@account.domain) + end + def misskey_favourite? misskey_shortcode = @json['_misskey_reaction']&.delete(':') diff --git a/app/lib/activitypub/parser/custom_emoji_parser.rb b/app/lib/activitypub/parser/custom_emoji_parser.rb index 481199c72d..e217b5ec96 100644 --- a/app/lib/activitypub/parser/custom_emoji_parser.rb +++ b/app/lib/activitypub/parser/custom_emoji_parser.rb @@ -30,6 +30,6 @@ class ActivityPub::Parser::CustomEmojiParser end def license - @json['license'] + @json['license'] || @json['licence'] end end diff --git a/app/serializers/activitypub/emoji_serializer.rb b/app/serializers/activitypub/emoji_serializer.rb index 4dc38f3ea6..98525d3131 100644 --- a/app/serializers/activitypub/emoji_serializer.rb +++ b/app/serializers/activitypub/emoji_serializer.rb @@ -5,7 +5,9 @@ class ActivityPub::EmojiSerializer < ActivityPub::Serializer context_extensions :emoji - attributes :id, :type, :name, :updated + attributes :id, :type, :domain, :name, :is_sensitive, :updated + + attribute :license, if: -> { object.license.present? } has_one :icon, serializer: ActivityPub::ImageSerializer @@ -17,6 +19,10 @@ class ActivityPub::EmojiSerializer < ActivityPub::Serializer 'Emoji' end + def domain + object.domain.presence || Rails.configuration.x.local_domain + end + def icon object.image end diff --git a/spec/lib/activitypub/activity/like_spec.rb b/spec/lib/activitypub/activity/like_spec.rb index 51493f7bc8..bdeda34f12 100644 --- a/spec/lib/activitypub/activity/like_spec.rb +++ b/spec/lib/activitypub/activity/like_spec.rb @@ -29,6 +29,225 @@ RSpec.describe ActivityPub::Activity::Like do end end + describe '#perform when receive emoji reaction' do + subject do + described_class.new(json, sender).perform + EmojiReaction.where(status: status) + end + + before do + stub_request(:get, 'http://example.com/emoji.png').to_return(body: attachment_fixture('emojo.png')) + end + + let(:json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Like', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: ActivityPub::TagManager.instance.uri_for(status), + content: content, + tag: tag, + }.with_indifferent_access + end + let(:content) { nil } + let(:tag) { nil } + + context 'with unicode emoji' do + let(:content) { '😀' } + + it 'create emoji reaction' do + expect(subject.count).to eq 1 + expect(subject.first.name).to eq '😀' + expect(subject.first.account).to eq sender + expect(sender.favourited?(status)).to be false + end + end + + context 'with custom emoji' do + let(:content) { ':tinking:' } + let(:tag) do + { + id: 'https://example.com/aaa', + type: 'Emoji', + icon: { + url: 'http://example.com/emoji.png', + }, + name: 'tinking', + license: 'Everyone but Ohagi', + } + end + + it 'create emoji reaction' do + expect(subject.count).to eq 1 + expect(subject.first.name).to eq 'tinking' + expect(subject.first.account).to eq sender + expect(subject.first.custom_emoji).to_not be_nil + expect(subject.first.custom_emoji.shortcode).to eq 'tinking' + expect(subject.first.custom_emoji.domain).to eq 'example.com' + expect(sender.favourited?(status)).to be false + end + + it 'custom emoji license is saved' do + expect(subject.first.custom_emoji.license).to eq 'Everyone but Ohagi' + end + end + + context 'with custom emoji and custom domain' do + let(:content) { ':tinking:' } + let(:tag) do + { + id: 'https://example.com/aaa', + type: 'Emoji', + domain: 'post.kmycode.net', + icon: { + url: 'http://example.com/emoji.png', + }, + name: 'tinking', + } + end + + it 'create emoji reaction' do + expect(subject.count).to eq 1 + expect(subject.first.name).to eq 'tinking' + expect(subject.first.account).to eq sender + expect(subject.first.custom_emoji).to_not be_nil + expect(subject.first.custom_emoji.shortcode).to eq 'tinking' + expect(subject.first.custom_emoji.domain).to eq 'post.kmycode.net' + expect(sender.favourited?(status)).to be false + end + end + + context 'with custom emoji but invalid id' do + let(:content) { ':tinking:' } + let(:tag) do + { + id: 'aaa', + type: 'Emoji', + icon: { + url: 'http://example.com/emoji.png', + }, + name: 'tinking', + } + end + + it 'create emoji reaction' do + expect(subject.count).to eq 1 + expect(subject.first.name).to eq 'tinking' + expect(subject.first.account).to eq sender + expect(subject.first.custom_emoji).to_not be_nil + expect(subject.first.custom_emoji.shortcode).to eq 'tinking' + expect(subject.first.custom_emoji.domain).to eq 'example.com' + expect(sender.favourited?(status)).to be false + end + end + + context 'with custom emoji but local domain' do + let(:content) { ':tinking:' } + let(:tag) do + { + id: 'aaa', + type: 'Emoji', + domain: Rails.configuration.x.local_domain, + icon: { + url: 'http://example.com/emoji.png', + }, + name: 'tinking', + } + end + + it 'create emoji reaction' do + expect(subject.count).to eq 1 + expect(subject.first.name).to eq 'tinking' + expect(subject.first.account).to eq sender + expect(subject.first.custom_emoji).to_not be_nil + expect(subject.first.custom_emoji.shortcode).to eq 'tinking' + expect(subject.first.custom_emoji.domain).to be_nil + expect(sender.favourited?(status)).to be false + end + end + + context 'with unicode emoji and reject_media enabled' do + let(:content) { '😀' } + + before do + Fabricate(:domain_block, domain: 'example.com', severity: :noop, reject_media: true) + end + + it 'create emoji reaction' do + expect(subject.count).to eq 1 + expect(subject.first.name).to eq '😀' + expect(subject.first.account).to eq sender + expect(sender.favourited?(status)).to be false + end + end + + context 'with custom emoji and reject_media enabled' do + let(:content) { ':tinking:' } + let(:tag) do + { + id: 'https://example.com/aaa', + type: 'Emoji', + icon: { + url: 'http://example.com/emoji.png', + }, + name: 'tinking', + } + end + + before do + Fabricate(:domain_block, domain: 'example.com', severity: :noop, reject_media: true) + end + + it 'create emoji reaction' do + expect(subject.count).to eq 0 + expect(sender.favourited?(status)).to be false + end + end + + context 'when emoji reaction is disabled' do + let(:content) { '😀' } + + before do + Form::AdminSettings.new(enable_emoji_reaction: false).save + end + + it 'create emoji reaction' do + expect(subject.count).to eq 0 + expect(sender.favourited?(status)).to be true + end + end + + context 'when emoji reaction between other servers is disabled' do + let(:recipient) { Fabricate(:account, domain: 'narrow.com', uri: 'https://narrow.com/') } + let(:content) { '😀' } + + before do + Form::AdminSettings.new(receive_other_servers_emoji_reaction: false).save + end + + it 'create emoji reaction' do + expect(subject.count).to eq 0 + expect(sender.favourited?(status)).to be false + end + end + + context 'when emoji reaction between other servers is disabled but that status is local' do + let(:content) { '😀' } + + before do + Form::AdminSettings.new(receive_other_servers_emoji_reaction: false).save + end + + it 'create emoji reaction' do + expect(subject.count).to eq 1 + expect(subject.first.name).to eq '😀' + expect(subject.first.account).to eq sender + expect(sender.favourited?(status)).to be false + end + end + end + describe '#perform when domain_block' do subject { described_class.new(json, sender) } @@ -50,7 +269,7 @@ RSpec.describe ActivityPub::Activity::Like do subject.perform end - it 'does not create a favourite from sender to status', pending: 'considering spec' do + it 'does not create a favourite from sender to status' do expect(sender.favourited?(status)).to be false end end