diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 924884d39d..13b33bcbbf 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -207,6 +207,7 @@ class FeedManager # also tagged with another followed hashtag or from a followed user scope = from_tag.statuses .where(id: timeline_status_ids) + .where.not(account: into_account) .where.not(account: into_account.following) .tagged_with_none(TagFollow.where(account: into_account).pluck(:tag_id)) diff --git a/app/models/emoji_reaction.rb b/app/models/emoji_reaction.rb index a562865f3a..e9dc6a37d3 100644 --- a/app/models/emoji_reaction.rb +++ b/app/models/emoji_reaction.rb @@ -40,7 +40,11 @@ class EmojiReaction < ApplicationRecord end def sign? - status&.distributable_friend? + true + end + + def object_type + :emoji_reaction end private diff --git a/app/services/emoji_react_service.rb b/app/services/emoji_react_service.rb index c3135bdff2..a827ddc93e 100644 --- a/app/services/emoji_react_service.rb +++ b/app/services/emoji_react_service.rb @@ -23,7 +23,10 @@ class EmojiReactService < BaseService raise Mastodon::ValidationError, I18n.t('reactions.errors.duplication') unless emoji_reaction.nil? shortcode, domain = name.split('@') + domain = nil if TagManager.instance.local_domain?(domain) custom_emoji = CustomEmoji.find_by(shortcode: shortcode, domain: domain) + return if domain.present? && !EmojiReaction.exists?(status: status, custom_emoji: custom_emoji) + emoji_reaction = EmojiReaction.create!(account: account, status: status, name: shortcode, custom_emoji: custom_emoji) status.touch # rubocop:disable Rails/SkipsModelValidations diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index e1f1ed87dc..57f61bcacf 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -9,7 +9,7 @@ module Mastodon end def kmyblue_minor - 0 + 1 end def kmyblue_flag diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb index d2e5da6b92..f5a3367b2b 100644 --- a/spec/lib/feed_manager_spec.rb +++ b/spec/lib/feed_manager_spec.rb @@ -562,6 +562,44 @@ RSpec.describe FeedManager do end end + describe '#unmerge_tag_from_home' do + let(:receiver) { Fabricate(:account) } + let(:tag) { Fabricate(:tag) } + + it 'leaves a tagged status' do + status = Fabricate(:status) + status.tags << tag + described_class.instance.push_to_home(receiver, status) + + described_class.instance.unmerge_tag_from_home(tag, receiver) + + expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to_not include(status.id.to_s) + end + + it 'remains a tagged status written by receiver\'s followee' do + followee = Fabricate(:account) + receiver.follow!(followee) + + status = Fabricate(:status, account: followee) + status.tags << tag + described_class.instance.push_to_home(receiver, status) + + described_class.instance.unmerge_tag_from_home(tag, receiver) + + expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to include(status.id.to_s) + end + + it 'remains a tagged status written by receiver' do + status = Fabricate(:status, account: receiver) + status.tags << tag + described_class.instance.push_to_home(receiver, status) + + described_class.instance.unmerge_tag_from_home(tag, receiver) + + expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to include(status.id.to_s) + end + end + describe '#clear_from_home' do let(:account) { Fabricate(:account) } let(:followed_account) { Fabricate(:account) } diff --git a/spec/services/emoji_react_service_spec.rb b/spec/services/emoji_react_service_spec.rb new file mode 100644 index 0000000000..629a7818d4 --- /dev/null +++ b/spec/services/emoji_react_service_spec.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe EmojiReactService, type: :service do + subject do + described_class.new.call(sender, status, name) + EmojiReaction.where(status: status, account: sender) + end + + let(:name) { '😀' } + let(:sender) { Fabricate(:user).account } + let(:author) { Fabricate(:user).account } + let(:status) { Fabricate(:status, account: author) } + + it 'with a simple case' do + expect(subject.count).to eq 1 + expect(subject.first.name).to eq '😀' + expect(subject.first.custom_emoji_id).to be_nil + end + + context 'with name duplication on same account' do + before { Fabricate(:emoji_reaction, status: status, name: '😀') } + + it 'react with emoji' do + expect(subject.count).to eq 1 + expect(subject.first.name).to eq '😀' + end + end + + context 'when multiple reactions by same account' do + let(:name) { '😂' } + + before { Fabricate(:emoji_reaction, account: sender, status: status, name: '😀') } + + it 'react with emoji' do + expect(subject.count).to eq 2 + expect(subject.pluck(:name)).to contain_exactly('😀', '😂') + end + end + + context 'when already reacted by other account' do + let(:name) { '😂' } + + before { Fabricate(:emoji_reaction, status: status, name: '😀') } + + it 'react with emoji' do + expect(subject.count).to eq 1 + expect(subject.pluck(:name)).to contain_exactly('😂') + end + end + + context 'when already reacted same emoji by other account', :tag do + before { Fabricate(:emoji_reaction, status: status, name: '😀') } + + it 'react with emoji' do + expect(subject.count).to eq 1 + expect(subject.first.name).to eq '😀' + end + end + + context 'when over limit' do + let(:name) { '🚗' } + + before do + Fabricate(:emoji_reaction, status: status, account: sender, name: '😀') + Fabricate(:emoji_reaction, status: status, account: sender, name: '😎') + Fabricate(:emoji_reaction, status: status, account: sender, name: '🐟') + end + + it 'react with emoji' do + expect { subject.count }.to raise_error Mastodon::ValidationError + + reactions = EmojiReaction.where(status: status, account: sender).pluck(:name) + expect(reactions.size).to eq 3 + expect(reactions).to contain_exactly('😀', '😎', '🐟') + end + end + + context 'with custom emoji of local' do + let(:name) { 'ohagi' } + let!(:custom_emoji) { Fabricate(:custom_emoji, shortcode: 'ohagi') } + + it 'react with emoji' do + expect(subject.count).to eq 1 + expect(subject.first.name).to eq 'ohagi' + expect(subject.first.custom_emoji.id).to eq custom_emoji.id + end + end + + context 'with custom emoji but not existing' do + let(:name) { 'ohagi' } + + it 'react with emoji' do + expect { subject.count }.to raise_error ActiveRecord::RecordInvalid + expect(EmojiReaction.exists?(status: status, account: sender, name: 'ohagi')).to be false + end + end + + context 'with custom emoji of remote' do + let(:name) { 'ohagi@foo.bar' } + let!(:custom_emoji) { Fabricate(:custom_emoji, shortcode: 'ohagi', domain: 'foo.bar', uri: 'https://foo.bar/emoji/ohagi') } + + before { Fabricate(:emoji_reaction, status: status, name: 'ohagi', custom_emoji: custom_emoji) } + + it 'react with emoji' do + expect(subject.count).to eq 1 + expect(subject.first.name).to eq 'ohagi' + expect(subject.first.custom_emoji.id).to eq custom_emoji.id + end + end + + context 'with custom emoji of remote without existing one' do + let(:name) { 'ohagi@foo.bar' } + + before { Fabricate(:custom_emoji, shortcode: 'ohagi', domain: 'foo.bar', uri: 'https://foo.bar/emoji/ohagi') } + + it 'react with emoji' do + expect(subject.count).to eq 0 + end + end + + context 'with custom emoji of remote but local has same name emoji' do + let(:name) { 'ohagi@foo.bar' } + let!(:custom_emoji) { Fabricate(:custom_emoji, shortcode: 'ohagi', domain: 'foo.bar', uri: 'https://foo.bar/emoji/ohagi') } + + before do + Fabricate(:custom_emoji, shortcode: 'ohagi', domain: nil) + Fabricate(:emoji_reaction, status: status, name: 'ohagi', custom_emoji: custom_emoji) + end + + it 'react with emoji' do + expect(subject.count).to eq 1 + expect(subject.first.name).to eq 'ohagi' + expect(subject.first.custom_emoji.id).to eq custom_emoji.id + expect(subject.first.custom_emoji.domain).to eq 'foo.bar' + end + end +end