1
0
Fork 0
forked from gitea/nas

Merge commit '563fc88821' into kb_migration

This commit is contained in:
KMY 2023-07-25 15:02:53 +09:00
commit 5233e68f13
20 changed files with 183 additions and 22 deletions

View file

@ -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

View file

@ -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]}") && !Admin::NgWord.hashtag_reject?(@tags.size)
end
def reply_to_local_account?
accounts_in_audience.any?(&:local?)
end

View file

@ -343,6 +343,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?
@ -350,6 +351,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?
@ -357,6 +359,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?

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
class Admin::NgWord
class << self
def reject?(text)
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

View file

@ -34,12 +34,17 @@ class Form::AdminSettings
backups_retention_period
status_page_url
captcha_enabled
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(
@ -54,6 +59,8 @@ class Form::AdminSettings
noindex
require_invite_text
captcha_enabled
enable_block_emoji_reaction_settings
hide_local_users_for_anonymous
).freeze
UPLOAD_KEYS = %i(
@ -61,6 +68,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 +91,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 +135,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

View file

@ -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

View file

@ -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?

View file

@ -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(

View file

@ -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

View file

@ -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}") && !Admin::NgWord.hashtag_reject?(@tags.size)
end
def update_immediate_attributes!
@status.text = @status_parser.text || ''
@status.spoiler_text = @status_parser.spoiler_text || ''

View file

@ -44,6 +44,7 @@ class PostStatusService < BaseService
return idempotency_duplicate if idempotency_given? && idempotency_duplicate?
validate_status!
validate_media!
preprocess_attributes!
@ -158,6 +159,11 @@ 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]}")
raise Mastodon::ValidationError, I18n.t('statuses.too_many_hashtags') if Admin::NgWord.hashtag_reject_with_extractor?(@options[:text])
end
def validate_media!
if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)
@media = []

View file

@ -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,11 @@ 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]}")
raise Mastodon::ValidationError, I18n.t('statuses.too_many_hashtags') if Admin::NgWord.hashtag_reject_with_extractor?(@options[:text])
end
def validate_media!
return [] if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)

View file

@ -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

View file

@ -0,0 +1,23 @@
- 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('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')
.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

View file

@ -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

View file

@ -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'

View file

@ -595,6 +595,12 @@ en:
title: IP rules
media_attachments:
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
post_hash_tags_max: Hash tags max for posts
title: NG words and against spams
relationships:
title: "%{acct}'s relationships"
relays:
@ -1710,6 +1716,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:
@ -1747,6 +1754,7 @@ en:
show_older: Show older
show_thread: Show thread
title: '%{name}: "%{quote}"'
too_many_hashtags: Too many hashtags
visibilities:
direct: Direct
private: Followers-only

View file

@ -594,6 +594,12 @@ ja:
title: IPルール
media_attachments:
title: 投稿された画像
ng_words:
enable_block_emoji_reaction_settings: 各ユーザーにスタンプ機能のブロック設定項目を解放する
hide_local_users_for_anonymous: ログインしていない状態でローカルユーザーの投稿をタイムラインから取得できないようにする
keywords: 投稿できないキーワード
post_hash_tags_max: 投稿に設定可能なハッシュタグの最大数
title: NGワードとスパム
relationships:
title: "%{acct} さんのフォロー・フォロワー"
relays:
@ -1697,6 +1703,7 @@ ja:
video:
other: "%{count}本の動画"
boosted_from_html: '%{acct_link}からブースト'
contains_ng_words: 投稿できない単語が含まれています
content_warning: '閲覧注意: %{warning}'
default_language: UIの表示言語
disallowed_hashtags:
@ -1731,6 +1738,7 @@ ja:
show_older: 古いものを表示
show_thread: スレッドを表示
title: '%{name}: "%{quote}"'
too_many_hashtags: ハッシュタグが多すぎます
visibilities:
direct: ダイレクト
private: フォロワー限定

View file

@ -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) }

View file

@ -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