From c553275381a18719da9b3dd3ac57126472d0b3b4 Mon Sep 17 00:00:00 2001 From: KMY Date: Mon, 24 Jul 2023 10:38:36 +0900 Subject: [PATCH 1/6] Add ng words for post/update --- app/controllers/admin/ng_words_controller.rb | 34 +++++++++++++++++++ app/lib/activitypub/activity/create.rb | 6 ++++ app/models/admin/ng_word.rb | 15 ++++++++ app/models/form/admin_settings.rb | 9 +++++ app/models/user_role.rb | 2 ++ app/policies/ng_words_policy.rb | 11 ++++++ .../process_status_update_service.rb | 6 +++- app/services/post_status_service.rb | 5 +++ app/services/update_status_service.rb | 6 ++++ app/views/admin/ng_words/show.html.haml | 14 ++++++++ config/navigation.rb | 3 +- config/routes/admin.rb | 1 + 12 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 app/controllers/admin/ng_words_controller.rb create mode 100644 app/models/admin/ng_word.rb create mode 100644 app/policies/ng_words_policy.rb create mode 100644 app/views/admin/ng_words/show.html.haml diff --git a/app/controllers/admin/ng_words_controller.rb b/app/controllers/admin/ng_words_controller.rb new file mode 100644 index 0000000000..1af50acb42 --- /dev/null +++ b/app/controllers/admin/ng_words_controller.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Admin + class NgWordsController < BaseController + def show + authorize :ng_words, :show? + + @admin_settings = Form::AdminSettings.new + end + + def create + authorize :ng_words, :create? + + @admin_settings = Form::AdminSettings.new(settings_params) + + if @admin_settings.save + flash[:notice] = I18n.t('generic.changes_saved_msg') + redirect_to after_update_redirect_path + else + render :index + end + end + + private + + def after_update_redirect_path + admin_ng_words_path + end + + def settings_params + params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS) + end + end +end diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index a9669e7cbf..048a54fdb0 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -88,6 +88,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity process_tags process_audience + return unless valid_status? + ApplicationRecord.transaction do @status = Status.create!(@params) attach_tags(@status) @@ -139,6 +141,10 @@ class ActivityPub::Activity::Create < ActivityPub::Activity } end + def valid_status? + !Admin::NgWord.reject?("#{@params[:spoiler_text]}\n#{@params[:text]}") + end + def reply_to_local_account? accounts_in_audience.any?(&:local?) end diff --git a/app/models/admin/ng_word.rb b/app/models/admin/ng_word.rb new file mode 100644 index 0000000000..3051c5d1bb --- /dev/null +++ b/app/models/admin/ng_word.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class Admin::NgWord + class << self + def reject?(text) + ng_words.any? { |word| text.include?(word) } + end + + private + + def ng_words + Setting.ng_words + end + end +end diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index a6be55fd7b..89625249d3 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -34,6 +34,7 @@ class Form::AdminSettings backups_retention_period status_page_url captcha_enabled + ng_words ).freeze INTEGER_KEYS = %i( @@ -61,6 +62,10 @@ class Form::AdminSettings mascot ).freeze + STRING_ARRAY_KEYS = %i( + ng_words + ).freeze + attr_accessor(*KEYS) validates :registrations_mode, inclusion: { in: %w(open approved none) }, if: -> { defined?(@registrations_mode) } @@ -80,6 +85,8 @@ class Form::AdminSettings stored_value = if UPLOAD_KEYS.include?(key) SiteUpload.where(var: key).first_or_initialize(var: key) + elsif STRING_ARRAY_KEYS.include?(key) + Setting.public_send(key)&.join("\n") || '' else Setting.public_send(key) end @@ -122,6 +129,8 @@ class Form::AdminSettings value == '1' elsif INTEGER_KEYS.include?(key) value.blank? ? value : Integer(value) + elsif STRING_ARRAY_KEYS.include?(key) + value&.split(/\r\n|\r|\n/)&.filter(&:present?)&.uniq || [] else value end diff --git a/app/models/user_role.rb b/app/models/user_role.rb index 5472646c60..0fd9b6e498 100644 --- a/app/models/user_role.rb +++ b/app/models/user_role.rb @@ -36,6 +36,7 @@ class UserRole < ApplicationRecord manage_roles: (1 << 17), manage_user_access: (1 << 18), delete_user_data: (1 << 19), + manage_ng_words: (1 << 30), }.freeze module Flags @@ -61,6 +62,7 @@ class UserRole < ApplicationRecord manage_blocks manage_taxonomies manage_invites + manage_ng_words ).freeze, administration: %w( diff --git a/app/policies/ng_words_policy.rb b/app/policies/ng_words_policy.rb new file mode 100644 index 0000000000..0ba640a895 --- /dev/null +++ b/app/policies/ng_words_policy.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class NgWordsPolicy < ApplicationPolicy + def show? + role.can?(:manage_ng_words) + end + + def create? + role.can?(:manage_ng_words) + end +end diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index 5cf89a5a6f..edfd2b315b 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -18,7 +18,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService @request_id = request_id # Only native types can be updated at the moment - return @status if !expected_type? || already_updated_more_recently? + return @status if !expected_type? || already_updated_more_recently? || !valid_status? if @status_parser.edited_at.present? && (@status.edited_at.nil? || @status_parser.edited_at > @status.edited_at) handle_explicit_update! @@ -152,6 +152,10 @@ class ActivityPub::ProcessStatusUpdateService < BaseService end end + def valid_status? + !Admin::NgWord.reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}") + end + def update_immediate_attributes! @status.text = @status_parser.text || '' @status.spoiler_text = @status_parser.spoiler_text || '' diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index ed8f215d8d..806afed340 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -44,6 +44,7 @@ class PostStatusService < BaseService return idempotency_duplicate if idempotency_given? && idempotency_duplicate? + validate_status! validate_media! preprocess_attributes! @@ -158,6 +159,10 @@ class PostStatusService < BaseService GroupReblogService.new.call(@status) end + def validate_status! + raise Mastodon::ValidationError, I18n.t('statuses.contains_ng_words') if Admin::NgWord.reject?("#{@options[:spoiler_text]}\n#{@options[:text]}") + end + def validate_media! if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable) @media = [] diff --git a/app/services/update_status_service.rb b/app/services/update_status_service.rb index 8ca2abe2ee..63f77cd753 100644 --- a/app/services/update_status_service.rb +++ b/app/services/update_status_service.rb @@ -27,6 +27,8 @@ class UpdateStatusService < BaseService clear_histories! if @options[:no_history] + validate_status! + Status.transaction do create_previous_edit! unless @options[:no_history] update_media_attachments! if @options.key?(:media_ids) @@ -71,6 +73,10 @@ class UpdateStatusService < BaseService @status.media_attachments.reload end + def validate_status! + raise Mastodon::ValidationError, I18n.t('statuses.contains_ng_words') if Admin::NgWord.reject?("#{@options[:spoiler_text]}\n#{@options[:text]}") + end + def validate_media! return [] if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable) diff --git a/app/views/admin/ng_words/show.html.haml b/app/views/admin/ng_words/show.html.haml new file mode 100644 index 0000000000..4168b135c0 --- /dev/null +++ b/app/views/admin/ng_words/show.html.haml @@ -0,0 +1,14 @@ +- content_for :page_title do + = t('admin.ng_words.title') + +- content_for :header_tags do + = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous' + += simple_form_for @admin_settings, url: admin_ng_words_path, html: { method: :post } do |f| + = render 'shared/error_messages', object: @admin_settings + + .fields-group + = f.input :ng_words, wrapper: :with_label, as: :text, input_html: { rows: 12 }, label: t('antennas.edit.keywords_raw') + + .actions + = f.button :button, t('generic.save_changes'), type: :submit diff --git a/config/navigation.rb b/config/navigation.rb index c8de1cb031..8b7b6b7df2 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -36,10 +36,11 @@ SimpleNavigation::Configuration.run do |navigation| s.item :links, safe_join([fa_icon('newspaper-o fw'), t('admin.trends.links.title')]), admin_trends_links_path, highlights_on: %r{/admin/trends/links} end - n.item :moderation, safe_join([fa_icon('gavel fw'), t('moderation.title')]), nil, if: -> { current_user.can?(:manage_reports, :view_audit_log, :manage_users, :manage_invites, :manage_taxonomies, :manage_federation, :manage_blocks) } do |s| + n.item :moderation, safe_join([fa_icon('gavel fw'), t('moderation.title')]), nil, if: -> { current_user.can?(:manage_reports, :view_audit_log, :manage_users, :manage_invites, :manage_taxonomies, :manage_federation, :manage_blocks, :manage_ng_words) } do |s| s.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_path, highlights_on: %r{/admin/reports}, if: -> { current_user.can?(:manage_reports) } s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_path(origin: 'local'), highlights_on: %r{/admin/accounts|/admin/pending_accounts|/admin/disputes|/admin/users}, if: -> { current_user.can?(:manage_users) } s.item :media_attachments, safe_join([fa_icon('picture-o fw'), t('admin.media_attachments.title')]), admin_media_attachments_path, highlights_on: %r{/admin/media_attachments}, if: -> { current_user.can?(:manage_users) } + s.item :ng_words, safe_join([fa_icon('picture-o fw'), t('admin.ng_words.title')]), admin_ng_words_path, highlights_on: %r{/admin/ng_words}, if: -> { current_user.can?(:manage_ng_words) } s.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path, if: -> { current_user.can?(:manage_invites) } s.item :follow_recommendations, safe_join([fa_icon('user-plus fw'), t('admin.follow_recommendations.title')]), admin_follow_recommendations_path, highlights_on: %r{/admin/follow_recommendations}, if: -> { current_user.can?(:manage_taxonomies) } s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_path(limited: whitelist_mode? ? nil : '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks|/admin/domain_allows}, if: -> { current_user.can?(:manage_federation) } diff --git a/config/routes/admin.rb b/config/routes/admin.rb index b7edca79e7..6c2a37f3c2 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -33,6 +33,7 @@ namespace :admin do resources :action_logs, only: [:index] resources :warning_presets, except: [:new, :show] resources :media_attachments, only: [:index] + resource :ng_words, only: [:show, :create] resources :announcements, except: [:show] do member do From a1cfab66cf5dc25dfd6f0059ceb09a5ec41c8620 Mon Sep 17 00:00:00 2001 From: KMY Date: Mon, 24 Jul 2023 11:36:29 +0900 Subject: [PATCH 2/6] Fix translations --- app/views/admin/ng_words/show.html.haml | 2 +- config/locales/en.yml | 4 ++++ config/locales/ja.yml | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/views/admin/ng_words/show.html.haml b/app/views/admin/ng_words/show.html.haml index 4168b135c0..fe997df0e4 100644 --- a/app/views/admin/ng_words/show.html.haml +++ b/app/views/admin/ng_words/show.html.haml @@ -8,7 +8,7 @@ = render 'shared/error_messages', object: @admin_settings .fields-group - = f.input :ng_words, wrapper: :with_label, as: :text, input_html: { rows: 12 }, label: t('antennas.edit.keywords_raw') + = f.input :ng_words, wrapper: :with_label, as: :text, input_html: { rows: 12 }, label: t('admin.ng_words.keywords') .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/config/locales/en.yml b/config/locales/en.yml index 9867ab6a58..7e359ce134 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -595,6 +595,9 @@ en: title: IP rules media_attachments: title: Media attachments + ng_words: + keywords: Reject keywords + title: NG words and against spams relationships: title: "%{acct}'s relationships" relays: @@ -1710,6 +1713,7 @@ en: one: "%{count} video" other: "%{count} videos" boosted_from_html: Boosted from %{acct_link} + contains_ng_words: The post contains NG words content_warning: 'Content warning: %{warning}' default_language: Same as interface language disallowed_hashtags: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 1dbd259a34..afe8b5759f 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -594,6 +594,9 @@ ja: title: IPルール media_attachments: title: 投稿された画像 + ng_words: + keywords: 投稿できないキーワード + title: NGワードとスパム relationships: title: "%{acct} さんのフォロー・フォロワー" relays: @@ -1694,6 +1697,7 @@ ja: video: other: "%{count}本の動画" boosted_from_html: '%{acct_link}からブースト' + contains_ng_words: 投稿できない単語が含まれています content_warning: '閲覧注意: %{warning}' default_language: UIの表示言語 disallowed_hashtags: From bf0d6fd2f58636c1b4c3f6bb454a8bcf9a5adc72 Mon Sep 17 00:00:00 2001 From: KMY Date: Mon, 24 Jul 2023 11:46:21 +0900 Subject: [PATCH 3/6] Add enable_block_emoji_reaction_settings setting --- app/models/account.rb | 3 +++ app/models/form/admin_settings.rb | 2 ++ app/validators/emoji_reaction_validator.rb | 15 +++++++-------- app/views/admin/ng_words/show.html.haml | 3 +++ .../preferences/notifications/show.html.haml | 7 ++++--- config/locales/en.yml | 1 + config/locales/ja.yml | 1 + 7 files changed, 21 insertions(+), 11 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index 670b2c6083..fe7a87d7eb 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -336,6 +336,7 @@ class Account < ApplicationRecord end def emoji_reactions_must_following? + return false unless Setting.enable_block_emoji_reaction_settings return user&.settings&.[]('emoji_reactions.must_be_following') || false if user.present? return settings['emoji_reactions_must_be_following'] || false if settings.present? @@ -343,6 +344,7 @@ class Account < ApplicationRecord end def emoji_reactions_must_follower? + return false unless Setting.enable_block_emoji_reaction_settings return user&.settings&.[]('emoji_reactions.must_be_follower') || false if user.present? return settings['emoji_reaction_must_be_follower'] || false if settings.present? @@ -350,6 +352,7 @@ class Account < ApplicationRecord end def emoji_reactions_deny_from_all? + return false unless Setting.enable_block_emoji_reaction_settings return user&.settings&.[]('emoji_reactions.deny_from_all') || false if user.present? return settings['emoji_reaction_deny_from_all'] || false if settings.present? diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 89625249d3..61071a5b76 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -35,6 +35,7 @@ class Form::AdminSettings status_page_url captcha_enabled ng_words + enable_block_emoji_reaction_settings ).freeze INTEGER_KEYS = %i( @@ -55,6 +56,7 @@ class Form::AdminSettings noindex require_invite_text captcha_enabled + enable_block_emoji_reaction_settings ).freeze UPLOAD_KEYS = %i( diff --git a/app/validators/emoji_reaction_validator.rb b/app/validators/emoji_reaction_validator.rb index 4d45a77468..e7da9d0724 100644 --- a/app/validators/emoji_reaction_validator.rb +++ b/app/validators/emoji_reaction_validator.rb @@ -8,7 +8,7 @@ class EmojiReactionValidator < ActiveModel::Validator emoji_reaction.errors.add(:name, I18n.t('reactions.errors.unrecognized_emoji')) if emoji_reaction.custom_emoji_id.blank? && !unicode_emoji?(emoji_reaction.name) emoji_reaction.errors.add(:name, I18n.t('reactions.errors.unrecognized_emoji')) if emoji_reaction.custom_emoji_id.present? && disabled_custom_emoji?(emoji_reaction.custom_emoji) - emoji_reaction.errors.add(:name, I18n.t('reactions.errors.banned')) if deny_from_all?(emoji_reaction) || non_follower?(emoji_reaction) || non_following?(emoji_reaction) + emoji_reaction.errors.add(:name, I18n.t('reactions.errors.banned')) if deny_emoji_reactions?(emoji_reaction) end private @@ -21,24 +21,23 @@ class EmojiReactionValidator < ActiveModel::Validator custom_emoji.nil? ? false : custom_emoji.disabled end - def deny_from_all?(emoji_reaction) + def deny_emoji_reactions?(emoji_reaction) + return false unless Setting.enable_block_emoji_reaction_settings return false if emoji_reaction.status.account.user.nil? return false if emoji_reaction.status.account_id == emoji_reaction.account_id + deny_from_all?(emoji_reaction) || non_follower?(emoji_reaction) || non_following?(emoji_reaction) + end + + def deny_from_all?(emoji_reaction) emoji_reaction.status.account.user.settings['emoji_reactions.deny_from_all'] end def non_following?(emoji_reaction) - return false if emoji_reaction.status.account.user.nil? - return false if emoji_reaction.status.account_id == emoji_reaction.account_id - emoji_reaction.status.account.user.settings['emoji_reactions.must_be_following'] && !emoji_reaction.status.account.following?(emoji_reaction.account) end def non_follower?(emoji_reaction) - return false if emoji_reaction.status.account.user.nil? - return false if emoji_reaction.status.account_id == emoji_reaction.account_id - emoji_reaction.status.account.user.settings['emoji_reactions.must_be_follower'] && !emoji_reaction.account.following?(emoji_reaction.status.account) end end diff --git a/app/views/admin/ng_words/show.html.haml b/app/views/admin/ng_words/show.html.haml index fe997df0e4..419a28b54d 100644 --- a/app/views/admin/ng_words/show.html.haml +++ b/app/views/admin/ng_words/show.html.haml @@ -10,5 +10,8 @@ .fields-group = f.input :ng_words, wrapper: :with_label, as: :text, input_html: { rows: 12 }, label: t('admin.ng_words.keywords') + .fields-group + = f.input :enable_block_emoji_reaction_settings, wrapper: :with_label, as: :boolean, label: t('admin.ng_words.enable_block_emoji_reaction_settings') + .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/settings/preferences/notifications/show.html.haml b/app/views/settings/preferences/notifications/show.html.haml index a87b765da9..2a7abb9488 100644 --- a/app/views/settings/preferences/notifications/show.html.haml +++ b/app/views/settings/preferences/notifications/show.html.haml @@ -33,9 +33,10 @@ = ff.input :'interactions.must_be_follower', wrapper: :with_label, label: I18n.t('simple_form.labels.interactions.must_be_follower') = ff.input :'interactions.must_be_following', wrapper: :with_label, label: I18n.t('simple_form.labels.interactions.must_be_following') = ff.input :'interactions.must_be_following_dm', wrapper: :with_label, label: I18n.t('simple_form.labels.interactions.must_be_following_dm') - = ff.input :'emoji_reactions.must_be_follower', kmyblue: true, wrapper: :with_label, label: I18n.t('simple_form.labels.emoji_reactions.must_be_follower') - = ff.input :'emoji_reactions.must_be_following', kmyblue: true, wrapper: :with_label, label: I18n.t('simple_form.labels.emoji_reactions.must_be_following') - = ff.input :'emoji_reactions.deny_from_all', kmyblue: true, wrapper: :with_label, label: I18n.t('simple_form.labels.emoji_reactions.deny_from_all') + - if Setting.enable_block_emoji_reaction_settings + = ff.input :'emoji_reactions.must_be_follower', kmyblue: true, wrapper: :with_label, label: I18n.t('simple_form.labels.emoji_reactions.must_be_follower') + = ff.input :'emoji_reactions.must_be_following', kmyblue: true, wrapper: :with_label, label: I18n.t('simple_form.labels.emoji_reactions.must_be_following') + = ff.input :'emoji_reactions.deny_from_all', kmyblue: true, wrapper: :with_label, label: I18n.t('simple_form.labels.emoji_reactions.deny_from_all') = f.simple_fields_for :settings, current_user.settings do |ff| .fields-group diff --git a/config/locales/en.yml b/config/locales/en.yml index 7e359ce134..5f280594b8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -596,6 +596,7 @@ en: media_attachments: title: Media attachments ng_words: + enable_block_emoji_reaction_settings: Enable block emoji reactions settings for users keywords: Reject keywords title: NG words and against spams relationships: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index afe8b5759f..bb6b6d263b 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -595,6 +595,7 @@ ja: media_attachments: title: 投稿された画像 ng_words: + enable_block_emoji_reaction_settings: 各ユーザーにスタンプ機能のブロック設定項目を解放する keywords: 投稿できないキーワード title: NGワードとスパム relationships: From 650cfae270b06d5939790f403c71538ab5c7aea5 Mon Sep 17 00:00:00 2001 From: KMY Date: Mon, 24 Jul 2023 12:12:37 +0900 Subject: [PATCH 4/6] Add hide_local_users_for_anonymous setting --- app/models/form/admin_settings.rb | 2 ++ app/models/public_feed.rb | 6 +++++- app/models/tag_feed.rb | 2 +- app/views/admin/ng_words/show.html.haml | 3 +++ config/locales/en.yml | 1 + config/locales/ja.yml | 1 + 6 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 61071a5b76..2b77353783 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -36,6 +36,7 @@ class Form::AdminSettings captcha_enabled ng_words enable_block_emoji_reaction_settings + hide_local_users_for_anonymous ).freeze INTEGER_KEYS = %i( @@ -57,6 +58,7 @@ class Form::AdminSettings require_invite_text captcha_enabled enable_block_emoji_reaction_settings + hide_local_users_for_anonymous ).freeze UPLOAD_KEYS = %i( diff --git a/app/models/public_feed.rb b/app/models/public_feed.rb index 929f062562..3579623232 100644 --- a/app/models/public_feed.rb +++ b/app/models/public_feed.rb @@ -24,7 +24,7 @@ class PublicFeed scope.merge!(without_replies_scope) unless with_replies? scope.merge!(without_reblogs_scope) unless with_reblogs? scope.merge!(local_only_scope) if local_only? - scope.merge!(remote_only_scope) if remote_only? + scope.merge!(remote_only_scope) if remote_only? || hide_local_users? scope.merge!(global_timeline_only_scope) if global_timeline? scope.merge!(account_filters_scope) if account? scope.merge!(media_only_scope) if media_only? @@ -54,6 +54,10 @@ class PublicFeed options[:remote] end + def hide_local_users? + @account.nil? && Setting.hide_local_users_for_anonymous + end + def global_timeline? !options[:remote] && !options[:local] end diff --git a/app/models/tag_feed.rb b/app/models/tag_feed.rb index fe805ed0dc..14f4afe737 100644 --- a/app/models/tag_feed.rb +++ b/app/models/tag_feed.rb @@ -29,7 +29,7 @@ class TagFeed < PublicFeed scope.merge!(tagged_with_all_scope) scope.merge!(tagged_with_none_scope) scope.merge!(local_only_scope) if local_only? - scope.merge!(remote_only_scope) if remote_only? + scope.merge!(remote_only_scope) if remote_only? || hide_local_users? scope.merge!(account_filters_scope) if account? scope.merge!(media_only_scope) if media_only? scope.merge!(anonymous_scope) unless account? diff --git a/app/views/admin/ng_words/show.html.haml b/app/views/admin/ng_words/show.html.haml index 419a28b54d..e8a87519c6 100644 --- a/app/views/admin/ng_words/show.html.haml +++ b/app/views/admin/ng_words/show.html.haml @@ -13,5 +13,8 @@ .fields-group = f.input :enable_block_emoji_reaction_settings, wrapper: :with_label, as: :boolean, label: t('admin.ng_words.enable_block_emoji_reaction_settings') + .fields-group + = f.input :hide_local_users_for_anonymous, wrapper: :with_label, as: :boolean, label: t('admin.ng_words.hide_local_users_for_anonymous') + .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/config/locales/en.yml b/config/locales/en.yml index 5f280594b8..a70a11bf75 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -597,6 +597,7 @@ en: title: Media attachments ng_words: enable_block_emoji_reaction_settings: Enable block emoji reactions settings for users + hide_local_users_for_anonymous: Hide timeline local user posts from anonymous keywords: Reject keywords title: NG words and against spams relationships: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index bb6b6d263b..49df945803 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -596,6 +596,7 @@ ja: title: 投稿された画像 ng_words: enable_block_emoji_reaction_settings: 各ユーザーにスタンプ機能のブロック設定項目を解放する + hide_local_users_for_anonymous: ログインしていない状態でローカルユーザーの投稿をタイムラインから取得できないようにする keywords: 投稿できないキーワード title: NGワードとスパム relationships: From 03dabcad9c788fba4fc5f06a24410a7529fb86c5 Mon Sep 17 00:00:00 2001 From: KMY Date: Mon, 24 Jul 2023 12:45:34 +0900 Subject: [PATCH 5/6] Add post_hash_tags_max setting --- app/lib/activitypub/activity/create.rb | 2 +- app/models/admin/ng_word.rb | 13 +++++++++++++ app/models/form/admin_settings.rb | 2 ++ .../activitypub/process_status_update_service.rb | 2 +- app/services/post_status_service.rb | 1 + app/services/update_status_service.rb | 1 + app/views/admin/ng_words/show.html.haml | 3 +++ config/locales/en.yml | 2 ++ config/locales/ja.yml | 2 ++ 9 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 048a54fdb0..8d6203b99d 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -142,7 +142,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity end def valid_status? - !Admin::NgWord.reject?("#{@params[:spoiler_text]}\n#{@params[:text]}") + !Admin::NgWord.reject?("#{@params[:spoiler_text]}\n#{@params[:text]}") && !Admin::NgWord.hashtag_reject?(@tags.size) end def reply_to_local_account? diff --git a/app/models/admin/ng_word.rb b/app/models/admin/ng_word.rb index 3051c5d1bb..878151f648 100644 --- a/app/models/admin/ng_word.rb +++ b/app/models/admin/ng_word.rb @@ -6,10 +6,23 @@ class Admin::NgWord ng_words.any? { |word| text.include?(word) } end + def hashtag_reject?(hashtag_count) + post_hash_tags_max.positive? && post_hash_tags_max < hashtag_count + end + + def hashtag_reject_with_extractor?(text) + hashtag_reject?(Extractor.extract_hashtags(text)&.size || 0) + end + private def ng_words Setting.ng_words end + + def post_hash_tags_max + value = Setting.post_hash_tags_max + value.is_a?(Integer) && value.positive? ? value : 0 + end end end diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 2b77353783..ff7ef50c12 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -37,12 +37,14 @@ class Form::AdminSettings ng_words enable_block_emoji_reaction_settings hide_local_users_for_anonymous + post_hash_tags_max ).freeze INTEGER_KEYS = %i( media_cache_retention_period content_cache_retention_period backups_retention_period + post_hash_tags_max ).freeze BOOLEAN_KEYS = %i( diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index edfd2b315b..e058290cf4 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -153,7 +153,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService end def valid_status? - !Admin::NgWord.reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}") + !Admin::NgWord.reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}") && !Admin::NgWord.hashtag_reject?(@tags.size) end def update_immediate_attributes! diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 806afed340..08d3f1998a 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -161,6 +161,7 @@ class PostStatusService < BaseService def validate_status! raise Mastodon::ValidationError, I18n.t('statuses.contains_ng_words') if Admin::NgWord.reject?("#{@options[:spoiler_text]}\n#{@options[:text]}") + raise Mastodon::ValidationError, I18n.t('statuses.too_many_hashtags') if Admin::NgWord.hashtag_reject_with_extractor?(@options[:text]) end def validate_media! diff --git a/app/services/update_status_service.rb b/app/services/update_status_service.rb index 63f77cd753..be0bf526bc 100644 --- a/app/services/update_status_service.rb +++ b/app/services/update_status_service.rb @@ -75,6 +75,7 @@ class UpdateStatusService < BaseService def validate_status! raise Mastodon::ValidationError, I18n.t('statuses.contains_ng_words') if Admin::NgWord.reject?("#{@options[:spoiler_text]}\n#{@options[:text]}") + raise Mastodon::ValidationError, I18n.t('statuses.too_many_hashtags') if Admin::NgWord.hashtag_reject_with_extractor?(@options[:text]) end def validate_media! diff --git a/app/views/admin/ng_words/show.html.haml b/app/views/admin/ng_words/show.html.haml index e8a87519c6..be8b56ac40 100644 --- a/app/views/admin/ng_words/show.html.haml +++ b/app/views/admin/ng_words/show.html.haml @@ -10,6 +10,9 @@ .fields-group = f.input :ng_words, wrapper: :with_label, as: :text, input_html: { rows: 12 }, label: t('admin.ng_words.keywords') + .fields-group + = f.input :post_hash_tags_max, wrapper: :with_label, as: :integer, label: t('admin.ng_words.post_hash_tags_max') + .fields-group = f.input :enable_block_emoji_reaction_settings, wrapper: :with_label, as: :boolean, label: t('admin.ng_words.enable_block_emoji_reaction_settings') diff --git a/config/locales/en.yml b/config/locales/en.yml index a70a11bf75..7da4133fbd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -599,6 +599,7 @@ en: enable_block_emoji_reaction_settings: Enable block emoji reactions settings for users hide_local_users_for_anonymous: Hide timeline local user posts from anonymous keywords: Reject keywords + post_hash_tags_max: Hash tags max for posts title: NG words and against spams relationships: title: "%{acct}'s relationships" @@ -1754,6 +1755,7 @@ en: show_thread: Show thread sign_in_to_participate: Login to participate in the conversation title: '%{name}: "%{quote}"' + too_many_hashtags: Too many hashtags visibilities: direct: Direct private: Followers-only diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 49df945803..9eb9a9f3d2 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -598,6 +598,7 @@ ja: enable_block_emoji_reaction_settings: 各ユーザーにスタンプ機能のブロック設定項目を解放する hide_local_users_for_anonymous: ログインしていない状態でローカルユーザーの投稿をタイムラインから取得できないようにする keywords: 投稿できないキーワード + post_hash_tags_max: 投稿に設定可能なハッシュタグの最大数 title: NGワードとスパム relationships: title: "%{acct} さんのフォロー・フォロワー" @@ -1735,6 +1736,7 @@ ja: show_thread: スレッドを表示 sign_in_to_participate: ログインして会話に参加 title: '%{name}: "%{quote}"' + too_many_hashtags: ハッシュタグが多すぎます visibilities: direct: ダイレクト private: フォロワー限定 From 563fc88821ad27eddf676123caeb9a65bad4e353 Mon Sep 17 00:00:00 2001 From: KMY Date: Tue, 25 Jul 2023 14:58:25 +0900 Subject: [PATCH 6/6] Fix subscription setting is not change --- .../settings/preferences/other/show.html.haml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/views/settings/preferences/other/show.html.haml b/app/views/settings/preferences/other/show.html.haml index 8a89efff98..b77baa3c6f 100644 --- a/app/views/settings/preferences/other/show.html.haml +++ b/app/views/settings/preferences/other/show.html.haml @@ -48,16 +48,16 @@ .fields-group = ff.input :show_application, wrapper: :with_label, recommended: true, label: I18n.t('simple_form.labels.defaults.setting_show_application'), hint: I18n.t('simple_form.hints.defaults.setting_show_application') - %h4= t 'preferences.stop_deliver' + %h4= t 'preferences.stop_deliver' - .fields-group - = f.input :setting_send_without_domain_blocks, kmyblue: true, recommended: :not_recommended, as: :boolean, wrapper: :with_label + .fields-group + = ff.input :send_without_domain_blocks, kmyblue: true, recommended: :not_recommended, as: :boolean, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_send_without_domain_blocks'), hint: I18n.t('simple_form.hints.defaults.setting_send_without_domain_blocks') - .fields-group - = f.input :setting_reject_public_unlisted_subscription, kmyblue: true, as: :boolean, wrapper: :with_label + .fields-group + = ff.input :reject_public_unlisted_subscription, kmyblue: true, as: :boolean, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_reject_public_unlisted_subscription') - .fields-group - = f.input :setting_reject_unlisted_subscription, kmyblue: true, as: :boolean, wrapper: :with_label + .fields-group + = ff.input :reject_unlisted_subscription, kmyblue: true, as: :boolean, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_reject_unlisted_subscription'), hint: I18n.t('simple_form.hints.defaults.setting_reject_unlisted_subscription') %h4= t 'preferences.hide'