From 12e12db39d13e901914446b3aa1eb8d0296b880b Mon Sep 17 00:00:00 2001 From: KMY Date: Sun, 9 Jul 2023 11:33:49 +0900 Subject: [PATCH 1/9] Change silenced account searchability to private --- app/services/post_status_service.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 2c488e6f5e..ed8f215d8d 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -76,6 +76,7 @@ class PostStatusService < BaseService @visibility = :unlisted if (@visibility&.to_sym == :public || @visibility&.to_sym == :public_unlisted || @visibility&.to_sym == :login) && @account.silenced? @visibility = :public_unlisted if @visibility&.to_sym == :public && !@options[:force_visibility] && !@options[:application]&.superapp && @account.user&.setting_public_post_to_unlisted @searchability = searchability + @searchability = :private if @account.silenced? && @searchability&.to_sym == :public @markdown = @options[:markdown] || false @scheduled_at = @options[:scheduled_at]&.to_datetime @scheduled_at = nil if scheduled_in_the_past? From 6a48f9ce345623ae0f583a1c2a00be6adaeb9e70 Mon Sep 17 00:00:00 2001 From: KMY Date: Tue, 11 Jul 2023 09:46:31 +0900 Subject: [PATCH 2/9] Turn settings display_media_expand default value true --- app/models/user_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb index 5e32e9b61d..4eb61199a9 100644 --- a/app/models/user_settings.rb +++ b/app/models/user_settings.rb @@ -47,7 +47,7 @@ class UserSettings setting :reduce_motion, default: false setting :expand_content_warnings, default: false setting :display_media, default: 'default', in: %w(default show_all hide_all) - setting :display_media_expand, default: false + setting :display_media_expand, default: true setting :auto_play, default: false end From 4c65f2721ca5c3fa3b61bd8b7dda1baa4977ebed Mon Sep 17 00:00:00 2001 From: KMY Date: Tue, 11 Jul 2023 09:47:00 +0900 Subject: [PATCH 3/9] Turn settings auto_play default value true --- app/models/user_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb index 4eb61199a9..5b167c4b91 100644 --- a/app/models/user_settings.rb +++ b/app/models/user_settings.rb @@ -48,7 +48,7 @@ class UserSettings setting :expand_content_warnings, default: false setting :display_media, default: 'default', in: %w(default show_all hide_all) setting :display_media_expand, default: true - setting :auto_play, default: false + setting :auto_play, default: true end namespace :notification_emails do From cadfbbab968713118fad6c0272df370044371fa5 Mon Sep 17 00:00:00 2001 From: KMY Date: Tue, 11 Jul 2023 18:31:28 +0900 Subject: [PATCH 4/9] Add references activitypub support --- .../activitypub/references_controller.rb | 87 +++++++++++++++++++ app/helpers/context_helper.rb | 1 + app/lib/activitypub/activity/create.rb | 4 +- app/lib/activitypub/tag_manager.rb | 6 ++ .../activitypub/note_serializer.rb | 8 +- .../activitypub/fetch_references_service.rb | 5 +- .../process_status_update_service.rb | 8 ++ config/routes.rb | 1 + 8 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 app/controllers/activitypub/references_controller.rb diff --git a/app/controllers/activitypub/references_controller.rb b/app/controllers/activitypub/references_controller.rb new file mode 100644 index 0000000000..7cd903eaf4 --- /dev/null +++ b/app/controllers/activitypub/references_controller.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +class ActivityPub::ReferencesController < ActivityPub::BaseController + include SignatureVerification + include Authorization + include AccountOwnedConcern + + REFERENCES_LIMIT = 5 + + before_action :require_signature!, if: :authorized_fetch_mode? + before_action :set_status + + def index + expires_in 0, public: public_fetch_mode? + render json: references_collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', skip_activities: true + end + + private + + def pundit_user + signed_request_account + end + + def set_status + @status = @account.statuses.find(params[:status_id]) + authorize @status, :show? + rescue Mastodon::NotPermittedError + not_found + end + + def load_statuses + cached_references + end + + def cached_references + cache_collection(Status.where(id: results).reorder(:id), Status) + end + + def results + @results ||= begin + references = @status.reference_objects.order(target_status_id: :asc) + references = references.where('target_status_id > ?', page_params[:min_id]) if page_params[:min_id].present? + references = references.limit(limit_param(REFERENCES_LIMIT)) + references.pluck(:target_status_id) + end + end + + def pagination_min_id + results.last + end + + def records_continue? + results.size == limit_param(REFERENCES_LIMIT) + end + + def references_collection_presenter + page = ActivityPub::CollectionPresenter.new( + id: ActivityPub::TagManager.instance.references_uri_for(@status, page_params), + type: :unordered, + part_of: ActivityPub::TagManager.instance.references_uri_for(@status), + items: load_statuses.map(&:uri), + next: next_page + ) + + return page if page_requested? + + ActivityPub::CollectionPresenter.new( + type: :unordered, + id: ActivityPub::TagManager.instance.references_uri_for(@status), + first: page + ) + end + + def page_requested? + truthy_param?(:page) + end + + def next_page + return unless records_continue? + + ActivityPub::TagManager.instance.references_uri_for(@status, page_params.merge(min_id: pagination_min_id)) + end + + def page_params + params_slice(:min_id, :limit).merge(page: true) + end +end diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb index 7d982d8a40..aa9636db9e 100644 --- a/app/helpers/context_helper.rb +++ b/app/helpers/context_helper.rb @@ -25,6 +25,7 @@ module ContextHelper searchable_by: { 'fedibird' => 'http://fedibird.com/ns#', 'searchableBy' => { '@id' => 'fedibird:searchableBy', '@type' => '@id' } }, subscribable_by: { 'kmyblue' => 'http://kmy.blue/ns#', 'subscribableBy' => { '@id' => 'kmyblue:subscribableBy', '@type' => '@id' } }, other_setting: { 'fedibird' => 'http://fedibird.com/ns#', 'otherSetting' => 'fedibird:otherSetting' }, + references: { 'fedibird' => 'http://fedibird.com/ns#', 'references' => { '@id' => 'fedibird:references', '@type' => '@id' } }, olm: { 'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId', 'claim' => { '@type' => '@id', '@id' => 'toot:claim' }, 'fingerprintKey' => { '@type' => '@id', '@id' => 'toot:fingerprintKey' }, 'identityKey' => { '@type' => '@id', '@id' => 'toot:identityKey' }, 'devices' => { '@type' => '@id', '@id' => 'toot:devices' }, 'messageFranking' => 'toot:messageFranking', 'messageType' => 'toot:messageType', 'cipherText' => 'toot:cipherText' }, suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' }, }.freeze diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index b7ca0927c8..b9be271c53 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -473,7 +473,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity end def process_references! - references = @json['references'].nil? ? [] : ActivityPub::FetchReferencesService(@json['references']) + references = @json['references'].nil? ? [] : ActivityPub::FetchReferencesService.new.call(@json['references']) + quote = @json['quote'] || @json['quoteUrl'] || @json['quoteURL'] || @json['_misskey_quote'] + references << quote if quote ProcessReferencesWorker.perform_async(@status.id, [], references) end diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index e28a9c30b2..d971b44237 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -78,6 +78,12 @@ class ActivityPub::TagManager account_status_replies_url(target.account, target, page_params) end + def references_uri_for(target, page_params = nil) + raise ArgumentError, 'target must be a local activity' unless %i(note comment activity).include?(target.object_type) && target.local? + + account_status_references_url(target.account, target, page_params) + end + def followers_uri_for(target) target.local? ? account_followers_url(target) : target.followers_url.presence end diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index e07eff2bf3..6ed9154e97 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -3,13 +3,13 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer include FormattingHelper - context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :searchable_by + context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :searchable_by, :references attributes :id, :type, :summary, :in_reply_to, :published, :url, :attributed_to, :to, :cc, :sensitive, :atom_uri, :in_reply_to_atom_uri, - :conversation, :searchable_by + :conversation, :searchable_by, :references attribute :content attribute :content_map, if: :language? @@ -64,6 +64,10 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer ) end + def references + ActivityPub::TagManager.instance.references_uri_for(object) + end + def language? object.language.present? end diff --git a/app/services/activitypub/fetch_references_service.rb b/app/services/activitypub/fetch_references_service.rb index d6537e7467..6ee0dc4f94 100644 --- a/app/services/activitypub/fetch_references_service.rb +++ b/app/services/activitypub/fetch_references_service.rb @@ -32,7 +32,7 @@ class ActivityPub::FetchReferencesService < BaseService all_items.concat(items) - break if all_items.size >= StatusReferenceValidator::LIMIT + break if all_items.size >= 5 collection = collection['next'].present? ? fetch_collection(collection['next']) : nil end @@ -42,7 +42,8 @@ class ActivityPub::FetchReferencesService < BaseService def fetch_collection(collection_or_uri) return collection_or_uri if collection_or_uri.is_a?(Hash) - return if invalid_origin?(collection_or_uri) + return if unsupported_uri_scheme?(collection_or_uri) + return if ActivityPub::TagManager.instance.local_uri?(collection_or_uri) fetch_resource_without_id_validation(collection_or_uri, nil, true) end diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index fd07ffc242..2e7aaa7ee6 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -45,6 +45,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService create_edits! end + update_references! download_media_files! queue_poll_notifications! @@ -240,6 +241,13 @@ class ActivityPub::ProcessStatusUpdateService < BaseService end end + def update_references! + references = @json['references'].nil? ? [] : ActivityPub::FetchReferencesService.new.call(@json['references']) + quote = @json['quote'] || @json['quoteUrl'] || @json['quoteURL'] || @json['_misskey_quote'] + references << quote if quote + ProcessReferencesWorker.perform_async(@status.id, [], references) + end + def expected_type? equals_or_includes_any?(@json['type'], %w(Note Question)) end diff --git a/config/routes.rb b/config/routes.rb index e69f964211..d0e9e3b804 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -99,6 +99,7 @@ Rails.application.routes.draw do end resources :replies, only: [:index], module: :activitypub + resources :references, only: [:index], module: :activitypub end resources :followers, only: [:index], controller: :follower_accounts From f22d5be2a3ecaa73a8f82b30cd028b2cb4060429 Mon Sep 17 00:00:00 2001 From: KMY Date: Tue, 11 Jul 2023 18:52:00 +0900 Subject: [PATCH 5/9] Fix calling fetch_references_service --- app/lib/activitypub/activity/create.rb | 2 +- app/services/activitypub/process_status_update_service.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index b9be271c53..953d0788cf 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -473,7 +473,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity end def process_references! - references = @json['references'].nil? ? [] : ActivityPub::FetchReferencesService.new.call(@json['references']) + references = @json['references'].nil? ? [] : ActivityPub::FetchReferencesService.new.call(@status, @json['references']) quote = @json['quote'] || @json['quoteUrl'] || @json['quoteURL'] || @json['_misskey_quote'] references << quote if quote ProcessReferencesWorker.perform_async(@status.id, [], references) diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index 2e7aaa7ee6..5cf89a5a6f 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -242,7 +242,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService end def update_references! - references = @json['references'].nil? ? [] : ActivityPub::FetchReferencesService.new.call(@json['references']) + references = @json['references'].nil? ? [] : ActivityPub::FetchReferencesService.new.call(@status, @json['references']) quote = @json['quote'] || @json['quoteUrl'] || @json['quoteURL'] || @json['_misskey_quote'] references << quote if quote ProcessReferencesWorker.perform_async(@status.id, [], references) From 9beef9926f583f891441025c70ecb91e22f87e19 Mon Sep 17 00:00:00 2001 From: KMY Date: Tue, 11 Jul 2023 18:57:17 +0900 Subject: [PATCH 6/9] Fix fetch_references_service --- .../activitypub/fetch_references_service.rb | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/app/services/activitypub/fetch_references_service.rb b/app/services/activitypub/fetch_references_service.rb index 6ee0dc4f94..682ec7eb16 100644 --- a/app/services/activitypub/fetch_references_service.rb +++ b/app/services/activitypub/fetch_references_service.rb @@ -13,31 +13,17 @@ class ActivityPub::FetchReferencesService < BaseService def collection_items(collection_or_uri) collection = fetch_collection(collection_or_uri) - return unless collection.is_a?(Hash) && collection['first'].present? + return unless collection.is_a?(Hash) - all_items = [] - collection = fetch_collection(collection['first']) + collection = fetch_collection(collection['first']) if collection['first'].present? + return unless collection.is_a?(Hash) - while collection.is_a?(Hash) - items = begin - case collection['type'] - when 'Collection', 'CollectionPage' - collection['items'] - when 'OrderedCollection', 'OrderedCollectionPage' - collection['orderedItems'] - end - end - - break if items.blank? - - all_items.concat(items) - - break if all_items.size >= 5 - - collection = collection['next'].present? ? fetch_collection(collection['next']) : nil + case collection['type'] + when 'Collection', 'CollectionPage' + collection['items'] + when 'OrderedCollection', 'OrderedCollectionPage' + collection['orderedItems'] end - - all_items end def fetch_collection(collection_or_uri) From 7026a250b2900c5f5668bc28e56ed2f7daae3a29 Mon Sep 17 00:00:00 2001 From: KMY Date: Tue, 11 Jul 2023 19:11:36 +0900 Subject: [PATCH 7/9] Fix variable name --- app/lib/activitypub/activity/create.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 953d0788cf..a9669e7cbf 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -473,8 +473,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity end def process_references! - references = @json['references'].nil? ? [] : ActivityPub::FetchReferencesService.new.call(@status, @json['references']) - quote = @json['quote'] || @json['quoteUrl'] || @json['quoteURL'] || @json['_misskey_quote'] + references = @object['references'].nil? ? [] : ActivityPub::FetchReferencesService.new.call(@status, @object['references']) + quote = @object['quote'] || @object['quoteUrl'] || @object['quoteURL'] || @object['_misskey_quote'] references << quote if quote ProcessReferencesWorker.perform_async(@status.id, [], references) end From fb9dbfc86605185cbb08b6b7dd8b936dd309e24d Mon Sep 17 00:00:00 2001 From: KMY Date: Tue, 11 Jul 2023 21:49:29 +0900 Subject: [PATCH 8/9] Fix unable to removing references --- app/services/process_references_service.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/services/process_references_service.rb b/app/services/process_references_service.rb index 24f3def47c..30055936f9 100644 --- a/app/services/process_references_service.rb +++ b/app/services/process_references_service.rb @@ -68,6 +68,8 @@ class ProcessReferencesService < BaseService end def create_notifications! + return if @added_objects.blank? + local_reference_objects = @added_objects.filter { |ref| ref.target_status.account.local? } return if local_reference_objects.empty? @@ -80,6 +82,7 @@ class ProcessReferencesService < BaseService return if removed_references.empty? statuses = Status.where(id: removed_references) + @status.reference_objects.where(target_status: statuses).destroy_all statuses.each do |status| status.decrement_count!(:status_referred_by_count) From 5a0483ed21d2b48a29d0b2c4383c5dd2496a37e6 Mon Sep 17 00:00:00 2001 From: KMY Date: Fri, 14 Jul 2023 12:16:08 +0900 Subject: [PATCH 9/9] Add exclude_follows, exclude_localusers settings to custom_filter --- app/controllers/api/v1/filters_controller.rb | 4 ++-- app/controllers/api/v2/filters_controller.rb | 2 +- app/controllers/filters_controller.rb | 2 +- app/models/concerns/account_interactions.rb | 2 +- app/models/custom_filter.rb | 23 +++++++++++-------- app/models/custom_filter_keyword.rb | 2 +- .../status_relationships_presenter.rb | 14 ++++++++++- app/serializers/rest/filter_serializer.rb | 2 +- app/serializers/rest/v1/filter_serializer.rb | 6 ++++- app/views/filters/_filter_fields.html.haml | 4 ++++ config/locales/simple_form.en.yml | 3 +++ config/locales/simple_form.ja.yml | 3 +++ ...14004824_add_exclude_options_to_filters.rb | 11 +++++++++ db/schema.rb | 7 ++++-- streaming/index.js | 14 +++++++++-- 15 files changed, 77 insertions(+), 22 deletions(-) create mode 100644 db/migrate/20230714004824_add_exclude_options_to_filters.rb diff --git a/app/controllers/api/v1/filters_controller.rb b/app/controllers/api/v1/filters_controller.rb index ed98acce30..3b097a3478 100644 --- a/app/controllers/api/v1/filters_controller.rb +++ b/app/controllers/api/v1/filters_controller.rb @@ -52,11 +52,11 @@ class Api::V1::FiltersController < Api::BaseController end def resource_params - params.permit(:phrase, :expires_in, :irreversible, :whole_word, context: []) + params.permit(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :whole_word, context: []) end def filter_params - resource_params.slice(:phrase, :expires_in, :irreversible, :context) + resource_params.slice(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :context) end def keyword_params diff --git a/app/controllers/api/v2/filters_controller.rb b/app/controllers/api/v2/filters_controller.rb index 2fcdeeae45..f3e9938d8c 100644 --- a/app/controllers/api/v2/filters_controller.rb +++ b/app/controllers/api/v2/filters_controller.rb @@ -43,6 +43,6 @@ class Api::V2::FiltersController < Api::BaseController end def resource_params - params.permit(:title, :expires_in, :filter_action, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy]) + params.permit(:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy]) end end diff --git a/app/controllers/filters_controller.rb b/app/controllers/filters_controller.rb index bbe177ead1..b0b2168884 100644 --- a/app/controllers/filters_controller.rb +++ b/app/controllers/filters_controller.rb @@ -49,7 +49,7 @@ class FiltersController < ApplicationController end def resource_params - params.require(:custom_filter).permit(:title, :expires_in, :filter_action, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy]) + params.require(:custom_filter).permit(:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy]) end def set_body_classes diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb index 912c0256fc..649e64e398 100644 --- a/app/models/concerns/account_interactions.rb +++ b/app/models/concerns/account_interactions.rb @@ -265,7 +265,7 @@ module AccountInteractions def status_matches_filters(status) active_filters = CustomFilter.cached_filters_for(id) - CustomFilter.apply_cached_filters(active_filters, status) + CustomFilter.apply_cached_filters(active_filters, status, following?(status.account)) end def followers_for_local_distribution diff --git a/app/models/custom_filter.rb b/app/models/custom_filter.rb index 0f4fd78cbf..f431dc59ff 100644 --- a/app/models/custom_filter.rb +++ b/app/models/custom_filter.rb @@ -4,14 +4,16 @@ # # Table name: custom_filters # -# id :bigint(8) not null, primary key -# account_id :bigint(8) -# expires_at :datetime -# phrase :text default(""), not null -# context :string default([]), not null, is an Array -# created_at :datetime not null -# updated_at :datetime not null -# action :integer default("warn"), not null +# id :bigint(8) not null, primary key +# account_id :bigint(8) +# expires_at :datetime +# phrase :text default(""), not null +# context :string default([]), not null, is an Array +# created_at :datetime not null +# updated_at :datetime not null +# action :integer default("warn"), not null +# exclude_follows :boolean default(FALSE), not null +# exclude_localusers :boolean default(FALSE), not null # class CustomFilter < ApplicationRecord @@ -94,8 +96,11 @@ class CustomFilter < ApplicationRecord active_filters.select { |custom_filter, _| !custom_filter.expired? } end - def self.apply_cached_filters(cached_filters, status) + def self.apply_cached_filters(cached_filters, status, following) cached_filters.filter_map do |filter, rules| + next if filter.exclude_follows && following + next if filter.exclude_localusers && status.account.local? + match = rules[:keywords].match(status.proper.searchable_text) if rules[:keywords].present? keyword_matches = [match.to_s] unless match.nil? diff --git a/app/models/custom_filter_keyword.rb b/app/models/custom_filter_keyword.rb index 3158b3b79a..3a853daf30 100644 --- a/app/models/custom_filter_keyword.rb +++ b/app/models/custom_filter_keyword.rb @@ -7,7 +7,7 @@ # id :bigint(8) not null, primary key # custom_filter_id :bigint(8) not null # keyword :text default(""), not null -# whole_word :boolean default(TRUE), not null +# whole_word :boolean default(FALSE), not null # created_at :datetime not null # updated_at :datetime not null # diff --git a/app/presenters/status_relationships_presenter.rb b/app/presenters/status_relationships_presenter.rb index e5ec67f8c0..5ebb9aa25a 100644 --- a/app/presenters/status_relationships_presenter.rb +++ b/app/presenters/status_relationships_presenter.rb @@ -7,6 +7,8 @@ class StatusRelationshipsPresenter :bookmarks_map, :filters_map, :emoji_reactions_map def initialize(statuses, current_account_id = nil, **options) + @current_account_id = current_account_id + if current_account_id.nil? @reblogs_map = {} @favourites_map = {} @@ -37,7 +39,7 @@ class StatusRelationshipsPresenter active_filters = CustomFilter.cached_filters_for(current_account_id) @filters_map = statuses.each_with_object({}) do |status, h| - filter_matches = CustomFilter.apply_cached_filters(active_filters, status) + filter_matches = CustomFilter.apply_cached_filters(active_filters, status, following?(status.account_id)) unless filter_matches.empty? h[status.id] = filter_matches @@ -45,4 +47,14 @@ class StatusRelationshipsPresenter end end end + + def following?(other_account_id) + return false if @current_account_id.nil? + + @account ||= Account.find(@current_account_id) + return false unless @account + + @following_map ||= @account.following.pluck(:id) + @following_map.include?(other_account_id) + end end diff --git a/app/serializers/rest/filter_serializer.rb b/app/serializers/rest/filter_serializer.rb index 8816dd8076..1b5d70dffd 100644 --- a/app/serializers/rest/filter_serializer.rb +++ b/app/serializers/rest/filter_serializer.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class REST::FilterSerializer < ActiveModel::Serializer - attributes :id, :title, :context, :expires_at, :filter_action + attributes :id, :title, :exclude_follows, :exclude_localusers, :context, :expires_at, :filter_action has_many :keywords, serializer: REST::FilterKeywordSerializer, if: :rules_requested? has_many :statuses, serializer: REST::FilterStatusSerializer, if: :rules_requested? diff --git a/app/serializers/rest/v1/filter_serializer.rb b/app/serializers/rest/v1/filter_serializer.rb index 455f17efdb..ef77e3ca82 100644 --- a/app/serializers/rest/v1/filter_serializer.rb +++ b/app/serializers/rest/v1/filter_serializer.rb @@ -2,7 +2,7 @@ class REST::V1::FilterSerializer < ActiveModel::Serializer attributes :id, :phrase, :context, :whole_word, :expires_at, - :irreversible + :irreversible, :exclude_follows, :exclude_localusers delegate :context, :expires_at, to: :custom_filter @@ -18,6 +18,10 @@ class REST::V1::FilterSerializer < ActiveModel::Serializer custom_filter.irreversible? end + delegate :exclude_follows, to: :custom_filter + + delegate :exclude_localusers, to: :custom_filter + private def custom_filter diff --git a/app/views/filters/_filter_fields.html.haml b/app/views/filters/_filter_fields.html.haml index a554b55ff6..383ec46124 100644 --- a/app/views/filters/_filter_fields.html.haml +++ b/app/views/filters/_filter_fields.html.haml @@ -12,6 +12,10 @@ .fields-group = f.input :filter_action, as: :radio_buttons, collection: %i(warn hide), include_blank: false, wrapper: :with_block_label, label_method: ->(action) { safe_join([t("simple_form.labels.filters.actions.#{action}"), content_tag(:span, t("simple_form.hints.filters.actions.#{action}"), class: 'hint')]) }, hint: t('simple_form.hints.filters.action'), required: true +.fields-group + = f.input :exclude_follows, wrapper: :with_label, kmyblue: true, label: t('simple_form.labels.filters.options.exclude_follows') + = f.input :exclude_localusers, wrapper: :with_label, kmyblue: true, label: t('simple_form.labels.filters.options.exclude_localusers') + %hr.spacer/ - unless f.object.statuses.empty? diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 1cf2f76d53..38ba894e07 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -260,6 +260,9 @@ en: actions: hide: Hide completely warn: Hide with a warning + options: + exclude_follows: Exclude following users + exclude_localusers: Exclude local users form_admin_settings: activity_api_enabled: Publish aggregate statistics about user activity in the API backups_retention_period: User archive retention period diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index d58f52b8f2..2934c0550d 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -268,6 +268,9 @@ ja: actions: hide: 完全に隠す warn: 警告付きで隠す + options: + exclude_follows: フォロー中のユーザーをフィルターの対象にしない + exclude_localusers: ローカルユーザーをフィルターの対象にしない form_admin_settings: activity_api_enabled: APIでユーザーアクティビティに関する集計統計を公開する backups_retention_period: ユーザーアーカイブの保持期間 diff --git a/db/migrate/20230714004824_add_exclude_options_to_filters.rb b/db/migrate/20230714004824_add_exclude_options_to_filters.rb new file mode 100644 index 0000000000..07ca56eb06 --- /dev/null +++ b/db/migrate/20230714004824_add_exclude_options_to_filters.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddExcludeOptionsToFilters < ActiveRecord::Migration[6.1] + def change + safety_assured do + add_column :custom_filters, :exclude_follows, :boolean, null: false, default: false + add_column :custom_filters, :exclude_localusers, :boolean, null: false, default: false + change_column_default :custom_filter_keywords, :whole_word, from: true, to: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 60f032436e..61db4f5a5c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_07_06_031715) do +ActiveRecord::Schema.define(version: 2023_07_14_004824) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -446,7 +446,7 @@ ActiveRecord::Schema.define(version: 2023_07_06_031715) do create_table "custom_filter_keywords", force: :cascade do |t| t.bigint "custom_filter_id", null: false t.text "keyword", default: "", null: false - t.boolean "whole_word", default: true, null: false + t.boolean "whole_word", default: false, null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.index ["custom_filter_id"], name: "index_custom_filter_keywords_on_custom_filter_id" @@ -469,6 +469,8 @@ ActiveRecord::Schema.define(version: 2023_07_06_031715) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "action", default: 0, null: false + t.boolean "exclude_follows", default: false, null: false + t.boolean "exclude_localusers", default: false, null: false t.index ["account_id"], name: "index_custom_filters_on_account_id" end @@ -1228,6 +1230,7 @@ ActiveRecord::Schema.define(version: 2023_07_06_031715) do t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, opclass: :text_pattern_ops, where: "(reset_password_token IS NOT NULL)" t.index ["role_id"], name: "index_users_on_role_id", where: "(role_id IS NOT NULL)" + t.index ["unconfirmed_email"], name: "index_users_on_unconfirmed_email", where: "(unconfirmed_email IS NOT NULL)" end create_table "web_push_subscriptions", force: :cascade do |t| diff --git a/streaming/index.js b/streaming/index.js index 279ebbad83..a33608826e 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -671,7 +671,13 @@ const startServer = async () => { } if (!unpackedPayload.filtered && !req.cachedFilters) { - queries.push(client.query('SELECT filter.id AS id, filter.phrase AS title, filter.context AS context, filter.expires_at AS expires_at, filter.action AS filter_action, keyword.keyword AS keyword, keyword.whole_word AS whole_word FROM custom_filter_keywords keyword JOIN custom_filters filter ON keyword.custom_filter_id = filter.id WHERE filter.account_id = $1 AND (filter.expires_at IS NULL OR filter.expires_at > NOW())', [req.accountId])); + queries.push(client.query('SELECT filter.id AS id, filter.phrase AS title, filter.context AS context, filter.expires_at AS expires_at, filter.action AS filter_action, keyword.keyword AS keyword, keyword.whole_word AS whole_word, filter.exclude_follows AS exclude_follows, filter.exclude_localusers AS exclude_localusers FROM custom_filter_keywords keyword JOIN custom_filters filter ON keyword.custom_filter_id = filter.id WHERE filter.account_id = $1 AND (filter.expires_at IS NULL OR filter.expires_at > NOW())', [req.accountId])); + } + if (!unpackedPayload.filtered) { + queries.push(client.query(`SELECT accounts.domain AS domain + FROM follows + JOIN accounts ON follows.target_account_id = accounts.id + WHERE (account_id = $1 AND target_account_id = $2)`, [req.accountId, unpackedPayload.account.id])); } Promise.all(queries).then(values => { @@ -681,6 +687,8 @@ const startServer = async () => { return; } + const following = !unpackedPayload.filtered && values[values.length - 1].rows.length > 0; + if (!unpackedPayload.filtered && !req.cachedFilters) { const filterRows = values[accountDomain ? 2 : 1].rows; @@ -697,6 +705,8 @@ const startServer = async () => { context: row.context, expires_at: row.expires_at, filter_action: ['warn', 'hide'][row.filter_action], + excludeFollows: row.exclude_follows, + excludeLocalusers: row.exclude_localusers, }, }; } @@ -732,7 +742,7 @@ const startServer = async () => { const now = new Date(); payload.filtered = []; Object.values(req.cachedFilters).forEach((cachedFilter) => { - if ((cachedFilter.expires_at === null || cachedFilter.expires_at > now)) { + if ((cachedFilter.expires_at === null || cachedFilter.expires_at > now) && (!cachedFilter.repr.excludeFollows || !following) && (!cachedFilter.repr.excludeLocalusers || accountDomain)) { const keyword_matches = searchIndex.match(cachedFilter.regexp); if (keyword_matches) { payload.filtered.push({