From 74dd325112ddccdad6be2d3191ffb991bdafd1be Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 10 Oct 2023 18:23:31 +0200 Subject: [PATCH 1/6] Fix duplicate reports being sent when reporting some remote posts (port to v4.2.1) (#27356) --- CHANGELOG.md | 1 + app/services/report_service.rb | 2 +- spec/services/report_service_spec.rb | 21 ++++++++++++++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eebe5c85d..f9303f0115 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ All notable changes to this project will be documented in this file. ### Fixed +- Fix duplicate reports being sent when reporting some remote posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27355)) - Fix clicking on already-opened thread post scrolling to the top of the thread ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27331), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27338), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27350)) - Fix some remote posts getting truncated ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27307)) - Fix some cases of infinite scroll code trying to fetch inaccessible posts in a loop ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27286)) diff --git a/app/services/report_service.rb b/app/services/report_service.rb index 22d788d543..b4015d1cbf 100644 --- a/app/services/report_service.rb +++ b/app/services/report_service.rb @@ -57,7 +57,7 @@ class ReportService < BaseService def forward_to_replied_to! # Send report to servers to which the account was replying to, so they also have a chance to act - inbox_urls = Account.remote.where(domain: forward_to_domains).where(id: Status.where(id: reported_status_ids).where.not(in_reply_to_account_id: nil).select(:in_reply_to_account_id)).inboxes - [@target_account.inbox_url] + inbox_urls = Account.remote.where(domain: forward_to_domains).where(id: Status.where(id: reported_status_ids).where.not(in_reply_to_account_id: nil).select(:in_reply_to_account_id)).inboxes - [@target_account.inbox_url, @target_account.shared_inbox_url] inbox_urls.each do |inbox_url| ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url) diff --git a/spec/services/report_service_spec.rb b/spec/services/report_service_spec.rb index 616368bf48..d3bcd5d31c 100644 --- a/spec/services/report_service_spec.rb +++ b/spec/services/report_service_spec.rb @@ -36,7 +36,7 @@ RSpec.describe ReportService, type: :service do expect(report.uri).to_not be_nil end - context 'when reporting a reply' do + context 'when reporting a reply on a different remote server' do let(:remote_thread_account) { Fabricate(:account, domain: 'foo.com', protocol: :activitypub, inbox_url: 'http://foo.com/inbox') } let(:reported_status) { Fabricate(:status, account: remote_account, thread: Fabricate(:status, account: remote_thread_account)) } @@ -67,6 +67,25 @@ RSpec.describe ReportService, type: :service do end end end + + context 'when reporting a reply on the same remote server as the person being replied-to' do + let(:remote_thread_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') } + let(:reported_status) { Fabricate(:status, account: remote_account, thread: Fabricate(:status, account: remote_thread_account)) } + + context 'when forward_to_domains includes both the replied-to domain and the origin domain' do + it 'sends ActivityPub payload only once' do + subject.call(source_account, remote_account, status_ids: [reported_status.id], forward: forward, forward_to_domains: [remote_account.domain]) + expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once + end + end + + context 'when forward_to_domains does not include the replied-to domain' do + it 'sends ActivityPub payload only once' do + subject.call(source_account, remote_account, status_ids: [reported_status.id], forward: forward) + expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once + end + end + end end context 'when forward is false' do From b896ab294b44652e5b75ea0234797fe79b32921d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KMY=EF=BC=88=E9=9B=AA=E3=81=82=E3=81=99=E3=81=8B=EF=BC=89?= Date: Fri, 13 Oct 2023 18:15:47 +0900 Subject: [PATCH 2/6] Fix when unfollow a tag, my post also disappears from the home timeline (#27391) --- app/lib/feed_manager.rb | 1 + spec/lib/feed_manager_spec.rb | 38 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 7d530906c6..ff2ad7a01d 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/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) } From c2f0960e3f898849be528bc9f7c6aec27fa5adfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KMY=EF=BC=88=E9=9B=AA=E3=81=82=E3=81=99=E3=81=8B=EF=BC=89?= Date: Sat, 14 Oct 2023 07:56:46 +0900 Subject: [PATCH 3/6] =?UTF-8?q?Change:=20#52=20=E8=B3=BC=E8=AA=AD=E6=8B=92?= =?UTF-8?q?=E5=90=A6=E8=A8=AD=E5=AE=9A=E9=A0=85=E7=9B=AE=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E8=A8=AD=E5=AE=9A=E7=94=BB=E9=9D=A2=E3=81=AB=E7=A7=BB?= =?UTF-8?q?=E5=8B=95=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/settings/privacy_controller.rb | 2 +- app/controllers/settings/privacy_extra_controller.rb | 2 +- app/views/settings/privacy/show.html.haml | 3 --- app/views/settings/privacy_extra/show.html.haml | 6 ++++++ 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/controllers/settings/privacy_controller.rb b/app/controllers/settings/privacy_controller.rb index e5c4e5a905..1102c89fad 100644 --- a/app/controllers/settings/privacy_controller.rb +++ b/app/controllers/settings/privacy_controller.rb @@ -18,7 +18,7 @@ class Settings::PrivacyController < Settings::BaseController private def account_params - params.require(:account).permit(:discoverable, :unlocked, :indexable, :show_collections, :dissubscribable, settings: UserSettings.keys) + params.require(:account).permit(:discoverable, :unlocked, :indexable, :show_collections, settings: UserSettings.keys) end def set_account diff --git a/app/controllers/settings/privacy_extra_controller.rb b/app/controllers/settings/privacy_extra_controller.rb index 54cedf2c4b..85364ec35d 100644 --- a/app/controllers/settings/privacy_extra_controller.rb +++ b/app/controllers/settings/privacy_extra_controller.rb @@ -18,7 +18,7 @@ class Settings::PrivacyExtraController < Settings::BaseController private def account_params - params.require(:account).permit(settings: UserSettings.keys) + params.require(:account).permit(:dissubscribable, settings: UserSettings.keys) end def set_account diff --git a/app/views/settings/privacy/show.html.haml b/app/views/settings/privacy/show.html.haml index 619429392e..9656ada005 100644 --- a/app/views/settings/privacy/show.html.haml +++ b/app/views/settings/privacy/show.html.haml @@ -24,9 +24,6 @@ .fields-group = ff.input :noai, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_noai'), hint: I18n.t('simple_form.hints.defaults.setting_noai') - .fields-group - = f.input :dissubscribable, as: :boolean, wrapper: :with_label, kmyblue: true, hint: t('simple_form.hints.defaults.dissubscribable') - .fields-group = f.input :unlocked, as: :boolean, wrapper: :with_label diff --git a/app/views/settings/privacy_extra/show.html.haml b/app/views/settings/privacy_extra/show.html.haml index c9ccd03013..dd07719bcb 100644 --- a/app/views/settings/privacy_extra/show.html.haml +++ b/app/views/settings/privacy_extra/show.html.haml @@ -21,6 +21,12 @@ .fields-group = ff.input :link_preview, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_link_preview'), hint: I18n.t('simple_form.hints.defaults.setting_link_preview') + .fields-group + = f.input :dissubscribable, as: :boolean, wrapper: :with_label, kmyblue: true, hint: t('simple_form.hints.defaults.dissubscribable') + + .fields-group + = ff.input :allow_quote, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_allow_quote'), hint: false + %h4= t 'privacy_extra.stop_deliver' %p.lead= t('privacy_extra.stop_deliver_hint_html') From dd46f62ba64210c56f323d9e58dd974e046dae76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KMY=EF=BC=88=E9=9B=AA=E3=81=82=E3=81=99=E3=81=8B=EF=BC=89?= Date: Sun, 15 Oct 2023 11:36:33 +0900 Subject: [PATCH 4/6] Merge pull request from GHSA-jw42-6m49-65x8 --- app/services/emoji_react_service.rb | 3 + spec/services/emoji_react_service_spec.rb | 139 ++++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 spec/services/emoji_react_service_spec.rb diff --git a/app/services/emoji_react_service.rb b/app/services/emoji_react_service.rb index 9a166a3413..768fc7ac5c 100644 --- a/app/services/emoji_react_service.rb +++ b/app/services/emoji_react_service.rb @@ -22,7 +22,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/spec/services/emoji_react_service_spec.rb b/spec/services/emoji_react_service_spec.rb new file mode 100644 index 0000000000..62d81d57c5 --- /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') } + + 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') } + + 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') } + + 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 From 5ac46b4dc69367fc9644ab198aa9062ff0426c6e Mon Sep 17 00:00:00 2001 From: KMY Date: Sun, 15 Oct 2023 11:39:52 +0900 Subject: [PATCH 5/6] Bump version to 5.5 LTS --- lib/mastodon/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 3e443ad6c2..63ddbb0a5b 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -9,7 +9,7 @@ module Mastodon end def kmyblue_minor - 4 + 5 end def kmyblue_flag From e7e70b21d2ebefdb7d9ee9e599cc9ad5f751d327 Mon Sep 17 00:00:00 2001 From: KMY Date: Sun, 15 Oct 2023 12:21:39 +0900 Subject: [PATCH 6/6] =?UTF-8?q?Fix:=20=E3=82=B9=E3=82=BF=E3=83=B3=E3=83=97?= =?UTF-8?q?=E3=81=AE=E3=83=86=E3=82=B9=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/emoji_reaction.rb | 8 ++++++++ spec/fabricators/emoji_reaction_fabricator.rb | 7 +++++++ spec/services/emoji_react_service_spec.rb | 6 +++--- 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 spec/fabricators/emoji_reaction_fabricator.rb diff --git a/app/models/emoji_reaction.rb b/app/models/emoji_reaction.rb index ce0a7dfb2e..f97679347c 100644 --- a/app/models/emoji_reaction.rb +++ b/app/models/emoji_reaction.rb @@ -43,6 +43,14 @@ class EmojiReaction < ApplicationRecord custom_emoji? && !custom_emoji.local? end + def sign? + true + end + + def object_type + :emoji_reaction + end + private def refresh_cache diff --git a/spec/fabricators/emoji_reaction_fabricator.rb b/spec/fabricators/emoji_reaction_fabricator.rb new file mode 100644 index 0000000000..2605a9f232 --- /dev/null +++ b/spec/fabricators/emoji_reaction_fabricator.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +Fabricator(:emoji_reaction) do + account { Fabricate.build(:account) } + status { Fabricate.build(:status) } + name '😀' +end diff --git a/spec/services/emoji_react_service_spec.rb b/spec/services/emoji_react_service_spec.rb index 62d81d57c5..629a7818d4 100644 --- a/spec/services/emoji_react_service_spec.rb +++ b/spec/services/emoji_react_service_spec.rb @@ -99,7 +99,7 @@ RSpec.describe EmojiReactService, type: :service do context 'with custom emoji of remote' do let(:name) { 'ohagi@foo.bar' } - let!(:custom_emoji) { Fabricate(:custom_emoji, shortcode: 'ohagi', domain: '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) } @@ -113,7 +113,7 @@ RSpec.describe EmojiReactService, type: :service do context 'with custom emoji of remote without existing one' do let(:name) { 'ohagi@foo.bar' } - before { Fabricate(:custom_emoji, shortcode: 'ohagi', domain: '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 @@ -122,7 +122,7 @@ RSpec.describe EmojiReactService, type: :service do 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') } + 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)