Merge branch 'kb_development' into kb_migration
This commit is contained in:
commit
f64e5666ba
33 changed files with 329 additions and 32 deletions
|
@ -11,18 +11,31 @@ module Admin
|
|||
def create
|
||||
authorize :ng_words, :create?
|
||||
|
||||
begin
|
||||
test_words
|
||||
rescue
|
||||
flash[:alert] = I18n.t('admin.ng_words.test_error')
|
||||
redirect_to after_update_redirect_path
|
||||
return
|
||||
end
|
||||
|
||||
@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
|
||||
render :show
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def test_words
|
||||
ng_words = settings_params['ng_words'].split(/\r\n|\r|\n/)
|
||||
Admin::NgWord.reject_with_custom_words?('Sample text', ng_words)
|
||||
end
|
||||
|
||||
def after_update_redirect_path
|
||||
admin_ng_words_path
|
||||
end
|
||||
|
|
48
app/controllers/admin/sensitive_words_controller.rb
Normal file
48
app/controllers/admin/sensitive_words_controller.rb
Normal file
|
@ -0,0 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class SensitiveWordsController < BaseController
|
||||
def show
|
||||
authorize :sensitive_words, :show?
|
||||
|
||||
@admin_settings = Form::AdminSettings.new
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :sensitive_words, :create?
|
||||
|
||||
begin
|
||||
test_words
|
||||
rescue
|
||||
flash[:alert] = I18n.t('admin.ng_words.test_error')
|
||||
redirect_to after_update_redirect_path
|
||||
return
|
||||
end
|
||||
|
||||
@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 test_words
|
||||
sensitive_words = settings_params['sensitive_words'].split(/\r\n|\r|\n/)
|
||||
sensitive_words_for_full = settings_params['sensitive_words_for_full'].split(/\r\n|\r|\n/)
|
||||
Admin::NgWord.reject_with_custom_words?('Sample text', sensitive_words + sensitive_words_for_full)
|
||||
end
|
||||
|
||||
def after_update_redirect_path
|
||||
admin_sensitive_words_path
|
||||
end
|
||||
|
||||
def settings_params
|
||||
params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -206,7 +206,7 @@ export function submitCompose(routerHistory) {
|
|||
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
|
||||
media_ids: media.map(item => item.get('id')),
|
||||
media_attributes,
|
||||
sensitive: getState().getIn(['compose', 'sensitive']),
|
||||
sensitive: media.size > 0 ? getState().getIn(['compose', 'sensitive']) : false,
|
||||
spoiler_text: getState().getIn(['compose', 'spoiler']) ? getState().getIn(['compose', 'spoiler_text'], '') : '',
|
||||
markdown: getState().getIn(['compose', 'markdown']),
|
||||
visibility: getState().getIn(['compose', 'privacy']),
|
||||
|
|
|
@ -9,6 +9,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import AttachmentList from 'mastodon/components/attachment_list';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
|
||||
|
||||
|
@ -466,7 +467,14 @@ class Status extends ImmutablePureComponent {
|
|||
} else if (status.get('media_attachments').size > 0) {
|
||||
const language = status.getIn(['translation', 'language']) || status.get('language');
|
||||
|
||||
if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
|
||||
if (this.props.muted) {
|
||||
media = (
|
||||
<AttachmentList
|
||||
compact
|
||||
media={status.get('media_attachments')}
|
||||
/>
|
||||
);
|
||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
|
||||
const attachment = status.getIn(['media_attachments', 0]);
|
||||
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ const messages = defineMessages({
|
|||
status: { id: 'notification.status', defaultMessage: '{name} just posted' },
|
||||
statusReference: { id: 'notification.status_reference', defaultMessage: '{name} refered' },
|
||||
update: { id: 'notification.update', defaultMessage: '{name} edited a post' },
|
||||
warning: { id: 'notification.warning', defaultMessage: 'You have been warned and "{action}" has been executed. Check your mailbox' },
|
||||
adminSignUp: { id: 'notification.admin.sign_up', defaultMessage: '{name} signed up' },
|
||||
adminReport: { id: 'notification.admin.report', defaultMessage: '{name} reported {target}' },
|
||||
});
|
||||
|
@ -443,6 +444,31 @@ class Notification extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
renderWarning (notification) {
|
||||
const { intl, unread } = this.props;
|
||||
console.dir(notification);
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-warning focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.statusReference, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<div className='notification__favourite-icon-wrapper'>
|
||||
<Icon id='exclamation-triangle' className='star-icon' fixedWidth />
|
||||
</div>
|
||||
|
||||
<span title={notification.get('created_at')}>
|
||||
<FormattedMessage id='notification.warning' defaultMessage='You have been warned and "{action}" has been executed. Check your mailbox' values={{action: notification.getIn(['account_warning', 'action'])}} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className='notification__warning-text'>
|
||||
{notification.getIn(['account_warning', 'text'])}
|
||||
</div>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
renderAdminSignUp (notification, account, link) {
|
||||
const { intl, unread } = this.props;
|
||||
|
||||
|
@ -522,6 +548,8 @@ class Notification extends ImmutablePureComponent {
|
|||
return this.renderUpdate(notification, link);
|
||||
case 'poll':
|
||||
return this.renderPoll(notification, account);
|
||||
case 'warning':
|
||||
return this.renderWarning(notification);
|
||||
case 'admin.sign_up':
|
||||
return this.renderAdminSignUp(notification, account, link);
|
||||
case 'admin.report':
|
||||
|
|
|
@ -439,6 +439,7 @@
|
|||
"notification.status": "{name}さんが投稿しました",
|
||||
"notification.status_reference": "{name}さんがあなたの投稿を参照しました",
|
||||
"notification.update": "{name}さんが投稿を編集しました",
|
||||
"notification.warning": "あなたは警告を出され、「{action}」が実行されました。詳細はメールをご確認ください",
|
||||
"notifications.clear": "通知を消去",
|
||||
"notifications.clear_confirmation": "本当に通知を消去しますか?",
|
||||
"notifications.column_settings.admin.report": "新しい通報:",
|
||||
|
|
|
@ -52,10 +52,11 @@ const notificationToMap = notification => ImmutableMap({
|
|||
id: notification.id,
|
||||
type: notification.type,
|
||||
account: notification.account.id,
|
||||
emoji_reaction: ImmutableMap(notification.emoji_reaction),
|
||||
created_at: notification.created_at,
|
||||
emoji_reaction: ImmutableMap(notification.emoji_reaction),
|
||||
status: notification.status ? notification.status.id : null,
|
||||
report: notification.report ? fromJS(notification.report) : null,
|
||||
account_warning: notification.account_warning ? ImmutableMap(notification.account_warning) : null,
|
||||
});
|
||||
|
||||
const normalizeNotification = (state, notification, usePendingItems) => {
|
||||
|
|
|
@ -1848,6 +1848,11 @@ a.account__display-name {
|
|||
}
|
||||
}
|
||||
|
||||
.notification__warning-text {
|
||||
padding: 16px 16px 16px 48px;
|
||||
color: $darker-text-color;
|
||||
}
|
||||
|
||||
.notification__display-name {
|
||||
color: inherit;
|
||||
font-weight: 500;
|
||||
|
@ -3722,6 +3727,7 @@ a.status-card {
|
|||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 286px;
|
||||
object-fit: cover;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
|
@ -6265,6 +6271,7 @@ a.status-card {
|
|||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 64px;
|
||||
max-height: 70vh;
|
||||
display: grid;
|
||||
grid-template-columns: 50% 50%;
|
||||
grid-template-rows: 50% 50%;
|
||||
|
|
|
@ -105,7 +105,7 @@
|
|||
&__input {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
border: 1px solid $ui-button-background-color;
|
||||
border: 1px solid $ui-primary-color;
|
||||
box-sizing: border-box;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
|
@ -124,10 +124,15 @@
|
|||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
border-color: $ui-button-focus-background-color;
|
||||
border-color: lighten($valid-value-color, 15%);
|
||||
border-width: 4px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: $valid-value-color;
|
||||
border-color: $valid-value-color;
|
||||
}
|
||||
|
||||
&::-moz-focus-inner {
|
||||
outline: 0 !important;
|
||||
border: 0;
|
||||
|
@ -211,6 +216,14 @@
|
|||
padding: 10px;
|
||||
}
|
||||
|
||||
.poll__input {
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
border-color: $ui-button-focus-background-color;
|
||||
}
|
||||
}
|
||||
|
||||
.poll__footer {
|
||||
border-top: 1px solid darken($simple-background-color, 8%);
|
||||
padding: 10px;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::Activity::Like < ActivityPub::Activity
|
||||
include Redisable
|
||||
include Lockable
|
||||
|
||||
def perform
|
||||
@original_status = status_from_uri(object_uri)
|
||||
|
||||
|
@ -44,9 +47,15 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
|
|||
Trends.statuses.register(@original_status)
|
||||
end
|
||||
|
||||
return if EmojiReaction.where(account: @account, status: @original_status).count >= EmojiReaction::EMOJI_REACTION_PER_ACCOUNT_LIMIT
|
||||
reaction = nil
|
||||
|
||||
with_redis_lock("emoji_reaction:#{@original_status.id}") do
|
||||
return if EmojiReaction.where(account: @account, status: @original_status).count >= EmojiReaction::EMOJI_REACTION_PER_ACCOUNT_LIMIT
|
||||
return if EmojiReaction.find_by(account: @account, status: @original_status, name: shortcode)
|
||||
|
||||
reaction = @original_status.emoji_reactions.create!(account: @account, name: shortcode, custom_emoji: emoji, uri: @json['id'])
|
||||
end
|
||||
|
||||
reaction = @original_status.emoji_reactions.create!(account: @account, name: shortcode, custom_emoji: emoji, uri: @json['id'])
|
||||
write_stream(reaction)
|
||||
|
||||
if @original_status.account.local?
|
||||
|
|
|
@ -26,11 +26,17 @@ class AccountStatusesFilter
|
|||
scope.merge!(no_reblogs_scope) if exclude_reblogs?
|
||||
scope.merge!(hashtag_scope) if tagged?
|
||||
|
||||
scope.merge!(scope.where(searchability: :public)) if domain_block&.reject_send_not_public_searchability
|
||||
scope.merge!(scope.where.not(visibility: :public_unlisted)) if domain_block&.reject_send_public_unlisted || (domain_block&.detect_invalid_subscription && @account.user&.setting_reject_public_unlisted_subscription)
|
||||
scope.merge!(scope.where.not(visibility: :unlisted)) if domain_block&.detect_invalid_subscription && @account.user&.setting_reject_unlisted_subscription
|
||||
available_searchabilities = [:public, :unlisted, :private, :direct, :limited, nil]
|
||||
available_visibilities = [:public, :public_unlisted, :login, :unlisted, :private, :direct, :limited]
|
||||
|
||||
available_searchabilities = [:public] if domain_block&.reject_send_not_public_searchability
|
||||
available_visibilities -= [:public_unlisted] if domain_block&.reject_send_public_unlisted || (domain_block&.detect_invalid_subscription && @account.user&.setting_reject_public_unlisted_subscription)
|
||||
available_visibilities -= [:unlisted] if domain_block&.detect_invalid_subscription && @account.user&.setting_reject_unlisted_subscription
|
||||
available_visibilities -= [:login] if current_account.nil?
|
||||
|
||||
scope.merge!(scope.where(spoiler_text: ['', nil])) if domain_block&.reject_send_sensitive
|
||||
scope.merge!(scope.where.not(visibility: :login)) if current_account.nil?
|
||||
scope.merge!(scope.where(searchability: available_searchabilities))
|
||||
scope.merge!(scope.where(visibility: available_visibilities))
|
||||
|
||||
scope
|
||||
end
|
||||
|
|
|
@ -54,6 +54,7 @@ class Admin::AccountAction
|
|||
|
||||
process_email!
|
||||
process_queue!
|
||||
notify!
|
||||
end
|
||||
|
||||
def report
|
||||
|
@ -107,6 +108,10 @@ class Admin::AccountAction
|
|||
log_action(:create, @warning) if @warning.text.present? && type == 'none'
|
||||
end
|
||||
|
||||
def notify!
|
||||
LocalNotificationWorker.perform_async(target_account.id, @warning.id, 'AccountWarning', 'warning') if @warning && %w(none sensitive silence).include?(type)
|
||||
end
|
||||
|
||||
def process_reports!
|
||||
# If we're doing "mark as resolved" on a single report,
|
||||
# then we want to keep other reports open in case they
|
||||
|
|
|
@ -3,7 +3,11 @@
|
|||
class Admin::NgWord
|
||||
class << self
|
||||
def reject?(text)
|
||||
ng_words.any? { |word| text.include?(word) }
|
||||
ng_words.any? { |word| include?(text, word) }
|
||||
end
|
||||
|
||||
def reject_with_custom_words?(text, custom_ng_words)
|
||||
custom_ng_words.any? { |word| include?(text, word) }
|
||||
end
|
||||
|
||||
def hashtag_reject?(hashtag_count)
|
||||
|
@ -16,8 +20,16 @@ class Admin::NgWord
|
|||
|
||||
private
|
||||
|
||||
def include?(text, word)
|
||||
if word.start_with?('?') && word.size >= 2
|
||||
text =~ /#{word[1..]}/i
|
||||
else
|
||||
text.include?(word)
|
||||
end
|
||||
end
|
||||
|
||||
def ng_words
|
||||
Setting.ng_words
|
||||
Setting.ng_words || []
|
||||
end
|
||||
|
||||
def post_hash_tags_max
|
||||
|
|
33
app/models/admin/sensitive_word.rb
Normal file
33
app/models/admin/sensitive_word.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::SensitiveWord
|
||||
class << self
|
||||
def sensitive?(text, spoiler_text)
|
||||
exposure_text = (spoiler_text.presence || text)
|
||||
(spoiler_text.blank? && sensitive_words.any? { |word| include?(text, word) }) ||
|
||||
sensitive_words_for_full.any? { |word| include?(exposure_text, word) }
|
||||
end
|
||||
|
||||
def modified_text(text, spoiler_text)
|
||||
spoiler_text.present? ? "#{spoiler_text}\n\n#{text}" : text
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def include?(text, word)
|
||||
if word.start_with?('?') && word.size >= 2
|
||||
text =~ /#{word[1..]}/i
|
||||
else
|
||||
text.include?(word)
|
||||
end
|
||||
end
|
||||
|
||||
def sensitive_words
|
||||
Setting.sensitive_words || []
|
||||
end
|
||||
|
||||
def sensitive_words_for_full
|
||||
Setting.sensitive_words_for_full || []
|
||||
end
|
||||
end
|
||||
end
|
|
@ -17,6 +17,7 @@ class Admin::StatusBatchAction
|
|||
|
||||
def save!
|
||||
process_action!
|
||||
notify!
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -157,6 +158,10 @@ class Admin::StatusBatchAction
|
|||
report.save!
|
||||
end
|
||||
|
||||
def notify!
|
||||
LocalNotificationWorker.perform_async(target_account.id, @warning.id, 'AccountWarning', 'warning') if warnable? && @warning
|
||||
end
|
||||
|
||||
def report
|
||||
@report ||= Report.find(report_id) if report_id.present?
|
||||
end
|
||||
|
|
|
@ -28,7 +28,6 @@ class EmojiReaction < ApplicationRecord
|
|||
|
||||
has_one :notification, as: :activity, dependent: :destroy
|
||||
|
||||
validate :status_same_emoji_reaction
|
||||
validate :status_emoji_reactions_count
|
||||
validates_with EmojiReactionValidator
|
||||
|
||||
|
|
|
@ -38,6 +38,8 @@ class Form::AdminSettings
|
|||
enable_block_emoji_reaction_settings
|
||||
hide_local_users_for_anonymous
|
||||
post_hash_tags_max
|
||||
sensitive_words
|
||||
sensitive_words_for_full
|
||||
).freeze
|
||||
|
||||
INTEGER_KEYS = %i(
|
||||
|
@ -70,6 +72,8 @@ class Form::AdminSettings
|
|||
|
||||
STRING_ARRAY_KEYS = %i(
|
||||
ng_words
|
||||
sensitive_words
|
||||
sensitive_words_for_full
|
||||
).freeze
|
||||
|
||||
attr_accessor(*KEYS)
|
||||
|
|
|
@ -28,6 +28,7 @@ class Notification < ApplicationRecord
|
|||
'EmojiReaction' => :emoji_reaction,
|
||||
'StatusReference' => :status_reference,
|
||||
'Poll' => :poll,
|
||||
'AccountWarning' => :warning,
|
||||
}.freeze
|
||||
|
||||
TYPES = %i(
|
||||
|
@ -42,6 +43,7 @@ class Notification < ApplicationRecord
|
|||
reaction
|
||||
poll
|
||||
update
|
||||
warning
|
||||
admin.sign_up
|
||||
admin.report
|
||||
).freeze
|
||||
|
@ -73,6 +75,7 @@ class Notification < ApplicationRecord
|
|||
belongs_to :status_reference, inverse_of: :notification
|
||||
belongs_to :poll, inverse_of: false
|
||||
belongs_to :report, inverse_of: false
|
||||
belongs_to :account_warning, inverse_of: false
|
||||
end
|
||||
|
||||
validates :type, inclusion: { in: TYPES }
|
||||
|
@ -159,6 +162,15 @@ class Notification < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def from_account_web
|
||||
case activity_type
|
||||
when 'AccountWarning'
|
||||
account_warning&.target_account
|
||||
else
|
||||
from_account
|
||||
end
|
||||
end
|
||||
|
||||
after_initialize :set_from_account
|
||||
before_validation :set_from_account
|
||||
|
||||
|
@ -168,7 +180,7 @@ class Notification < ApplicationRecord
|
|||
return unless new_record?
|
||||
|
||||
case activity_type
|
||||
when 'Status', 'Follow', 'Favourite', 'EmojiReaction', 'EmojiReact', 'FollowRequest', 'Poll', 'Report'
|
||||
when 'Status', 'Follow', 'Favourite', 'EmojiReaction', 'EmojiReact', 'FollowRequest', 'Poll', 'Report', 'AccountWarning'
|
||||
self.from_account_id = activity&.account_id
|
||||
when 'Mention', 'StatusReference'
|
||||
self.from_account_id = activity&.status&.account_id
|
||||
|
|
|
@ -107,7 +107,7 @@ class PublicFeed
|
|||
end
|
||||
|
||||
def anonymous_scope
|
||||
Status.where.not(visibility: :login)
|
||||
Status.where(visibility: [:public, :public_unlisted])
|
||||
end
|
||||
|
||||
def account_filters_scope
|
||||
|
|
|
@ -36,6 +36,7 @@ class UserRole < ApplicationRecord
|
|||
manage_roles: (1 << 17),
|
||||
manage_user_access: (1 << 18),
|
||||
delete_user_data: (1 << 19),
|
||||
manage_sensitive_words: (1 << 29),
|
||||
manage_ng_words: (1 << 30),
|
||||
}.freeze
|
||||
|
||||
|
@ -63,6 +64,7 @@ class UserRole < ApplicationRecord
|
|||
manage_taxonomies
|
||||
manage_invites
|
||||
manage_ng_words
|
||||
manage_sensitive_words
|
||||
).freeze,
|
||||
|
||||
administration: %w(
|
||||
|
|
11
app/policies/sensitive_words_policy.rb
Normal file
11
app/policies/sensitive_words_policy.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class SensitiveWordsPolicy < ApplicationPolicy
|
||||
def show?
|
||||
role.can?(:manage_sensitive_words)
|
||||
end
|
||||
|
||||
def create?
|
||||
role.can?(:manage_sensitive_words)
|
||||
end
|
||||
end
|
5
app/serializers/rest/account_warning_serializer.rb
Normal file
5
app/serializers/rest/account_warning_serializer.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::AccountWarningSerializer < ActiveModel::Serializer
|
||||
attributes :id, :action, :text, :status_ids
|
||||
end
|
|
@ -3,10 +3,11 @@
|
|||
class REST::NotificationSerializer < ActiveModel::Serializer
|
||||
attributes :id, :type, :created_at
|
||||
|
||||
belongs_to :from_account, key: :account, serializer: REST::AccountSerializer
|
||||
belongs_to :from_account_web, key: :account, serializer: REST::AccountSerializer
|
||||
belongs_to :target_status, key: :status, if: :status_type?, serializer: REST::StatusSerializer
|
||||
belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer
|
||||
belongs_to :emoji_reaction, if: :emoji_reaction_type?, serializer: REST::NotifyEmojiReactionSerializer
|
||||
belongs_to :account_warning, if: :warning_type?, serializer: REST::AccountWarningSerializer
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
|
@ -20,6 +21,10 @@ class REST::NotificationSerializer < ActiveModel::Serializer
|
|||
object.type == :'admin.report'
|
||||
end
|
||||
|
||||
def warning_type?
|
||||
object.type == :warning
|
||||
end
|
||||
|
||||
def emoji_reaction_type?
|
||||
object.type == :emoji_reaction
|
||||
end
|
||||
|
|
|
@ -21,6 +21,8 @@ class ActivityPub::ProcessAccountService < BaseService
|
|||
@domain = TagManager.instance.normalize_domain(domain)
|
||||
@collections = {}
|
||||
|
||||
return unless valid_account?
|
||||
|
||||
# The key does not need to be unguessable, it just needs to be somewhat unique
|
||||
@options[:request_id] ||= "#{Time.now.utc.to_i}-#{username}@#{domain}"
|
||||
|
||||
|
@ -123,6 +125,12 @@ class ActivityPub::ProcessAccountService < BaseService
|
|||
@account.settings = other_settings
|
||||
end
|
||||
|
||||
def valid_account?
|
||||
display_name = @json['name']
|
||||
note = @json['summary']
|
||||
!Admin::NgWord.reject?(display_name) && !Admin::NgWord.reject?(note)
|
||||
end
|
||||
|
||||
def set_fetchable_key!
|
||||
@account.public_key = public_key || ''
|
||||
end
|
||||
|
|
|
@ -18,9 +18,12 @@ 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? || !valid_status?
|
||||
return @status if !expected_type? || already_updated_more_recently?
|
||||
|
||||
if @status_parser.edited_at.present? && (@status.edited_at.nil? || @status_parser.edited_at > @status.edited_at)
|
||||
read_metadata
|
||||
return @status unless valid_status?
|
||||
|
||||
handle_explicit_update!
|
||||
else
|
||||
handle_implicit_update!
|
||||
|
@ -153,7 +156,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
|||
end
|
||||
|
||||
def valid_status?
|
||||
!Admin::NgWord.reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}") && !Admin::NgWord.hashtag_reject?(@tags.size)
|
||||
!Admin::NgWord.reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}") && !Admin::NgWord.hashtag_reject?(@raw_tags.size)
|
||||
end
|
||||
|
||||
def update_immediate_attributes!
|
||||
|
@ -169,7 +172,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
|||
@status.save!
|
||||
end
|
||||
|
||||
def update_metadata!
|
||||
def read_metadata
|
||||
@raw_tags = []
|
||||
@raw_mentions = []
|
||||
@raw_emojis = []
|
||||
|
@ -183,7 +186,9 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
|||
@raw_emojis << tag
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_metadata!
|
||||
update_tags!
|
||||
update_mentions!
|
||||
update_emojis!
|
||||
|
|
|
@ -4,6 +4,7 @@ class EmojiReactService < BaseService
|
|||
include Authorization
|
||||
include Payloadable
|
||||
include Redisable
|
||||
include Lockable
|
||||
|
||||
# React a status with emoji and notify remote user
|
||||
# @param [Account] account
|
||||
|
@ -14,17 +15,20 @@ class EmojiReactService < BaseService
|
|||
status = status.reblog if status.reblog? && !status.reblog.nil?
|
||||
authorize_with account, status, :emoji_reaction?
|
||||
|
||||
emoji_reaction = EmojiReaction.find_by(account: account, status: status, name: name)
|
||||
emoji_reaction = nil
|
||||
|
||||
return emoji_reaction unless emoji_reaction.nil?
|
||||
with_redis_lock("emoji_reaction:#{status.id}") do
|
||||
emoji_reaction = EmojiReaction.find_by(account: account, status: status, name: name)
|
||||
raise Mastodon::ValidationError, I18n.t('reactions.errors.duplication') unless emoji_reaction.nil?
|
||||
|
||||
shortcode, domain = name.split('@')
|
||||
shortcode, domain = name.split('@')
|
||||
custom_emoji = CustomEmoji.find_by(shortcode: shortcode, domain: domain)
|
||||
emoji_reaction = EmojiReaction.create!(account: account, status: status, name: shortcode, custom_emoji: custom_emoji)
|
||||
|
||||
custom_emoji = CustomEmoji.find_by(shortcode: shortcode, domain: domain)
|
||||
status.touch # rubocop:disable Rails/SkipsModelValidations
|
||||
end
|
||||
|
||||
emoji_reaction = EmojiReaction.create!(account: account, status: status, name: shortcode, custom_emoji: custom_emoji)
|
||||
|
||||
status.touch # rubocop:disable Rails/SkipsModelValidations
|
||||
raise Mastodon::ValidationError, I18n.t('reactions.errors.duplication') if emoji_reaction.nil?
|
||||
|
||||
Trends.statuses.register(status)
|
||||
|
||||
|
|
|
@ -82,10 +82,18 @@ class PostStatusService < BaseService
|
|||
@scheduled_at = @options[:scheduled_at]&.to_datetime
|
||||
@scheduled_at = nil if scheduled_in_the_past?
|
||||
@reference_ids = (@options[:status_reference_ids] || []).map(&:to_i).filter(&:positive?)
|
||||
process_sensitive_words
|
||||
rescue ArgumentError
|
||||
raise ActiveRecord::RecordInvalid
|
||||
end
|
||||
|
||||
def process_sensitive_words
|
||||
if [:public, :public_unlisted, :login].include?(@visibility&.to_sym) && Admin::SensitiveWord.sensitive?(@text, @options[:spoiler_text] || '')
|
||||
@text = Admin::SensitiveWord.modified_text(@text, @options[:spoiler_text])
|
||||
@options[:spoiler_text] = I18n.t('admin.sensitive_words.alert')
|
||||
end
|
||||
end
|
||||
|
||||
def searchability
|
||||
return :private if @options[:searchability]&.to_sym == :public && @visibility&.to_sym == :unlisted && @account.user&.setting_disallow_unlisted_public_searchability
|
||||
|
||||
|
|
|
@ -127,6 +127,7 @@ class UpdateStatusService < BaseService
|
|||
@status.markdown = @options[:markdown] || false
|
||||
@status.sensitive = @options[:sensitive] || @options[:spoiler_text].present? if @options.key?(:sensitive) || @options.key?(:spoiler_text)
|
||||
@status.language = valid_locale_cascade(@options[:language], @status.language, @status.account.user&.preferred_posting_language, I18n.default_locale)
|
||||
process_sensitive_words
|
||||
|
||||
# We raise here to rollback the entire transaction
|
||||
raise NoChangesSubmittedError unless significant_changes?
|
||||
|
@ -137,6 +138,13 @@ class UpdateStatusService < BaseService
|
|||
@status.save!
|
||||
end
|
||||
|
||||
def process_sensitive_words
|
||||
return unless [:public, :public_unlisted, :login].include?(@status.visibility&.to_sym) && Admin::SensitiveWord.sensitive?(@status.text, @status.spoiler_text || '')
|
||||
|
||||
@status.text = Admin::SensitiveWord.modified_text(@status.text, @status.spoiler_text)
|
||||
@status.spoiler_text = I18n.t('admin.sensitive_words.alert')
|
||||
end
|
||||
|
||||
def update_expiration!
|
||||
UpdateStatusExpirationService.new.call(@status)
|
||||
end
|
||||
|
|
19
app/views/admin/sensitive_words/show.html.haml
Normal file
19
app/views/admin/sensitive_words/show.html.haml
Normal file
|
@ -0,0 +1,19 @@
|
|||
- content_for :page_title do
|
||||
= t('admin.sensitive_words.title')
|
||||
|
||||
- content_for :header_tags do
|
||||
= javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
|
||||
|
||||
%p.hint= t 'admin.sensitive_words.hint'
|
||||
|
||||
= simple_form_for @admin_settings, url: admin_sensitive_words_path, html: { method: :post } do |f|
|
||||
= render 'shared/error_messages', object: @admin_settings
|
||||
|
||||
.fields-group
|
||||
= f.input :sensitive_words_for_full, wrapper: :with_label, as: :text, input_html: { rows: 12 }, label: t('admin.sensitive_words.keywords_for_all'), hint: t('admin.sensitive_words.keywords_for_all_hint')
|
||||
|
||||
.fields-group
|
||||
= f.input :sensitive_words, wrapper: :with_label, as: :text, input_html: { rows: 12 }, label: t('admin.sensitive_words.keywords'), hint: t('admin.sensitive_words.keywords_hint')
|
||||
|
||||
.actions
|
||||
= f.button :button, t('generic.save_changes'), type: :submit
|
|
@ -600,6 +600,7 @@ en:
|
|||
hide_local_users_for_anonymous: Hide timeline local user posts from anonymous
|
||||
keywords: Reject keywords
|
||||
post_hash_tags_max: Hash tags max for posts
|
||||
test_error: Testing is returned any errors
|
||||
title: NG words and against spams
|
||||
relationships:
|
||||
title: "%{acct}'s relationships"
|
||||
|
@ -773,6 +774,12 @@ en:
|
|||
edit: Edit rule
|
||||
empty: No server rules have been defined yet.
|
||||
title: Server rules
|
||||
sensitive_words:
|
||||
alert: This post contains sensitive words, so alert added
|
||||
hint: This keywords is applied to public posts only..
|
||||
keywords: Sensitive keywords
|
||||
keywords_for_all: Sensitive keywords (Contains CW alert)
|
||||
title: Sensitive words and moderation options
|
||||
settings:
|
||||
about:
|
||||
manage_rules: Manage server rules
|
||||
|
|
|
@ -599,6 +599,7 @@ ja:
|
|||
hide_local_users_for_anonymous: ログインしていない状態でローカルユーザーの投稿をタイムラインから取得できないようにする
|
||||
keywords: 投稿できないキーワード
|
||||
post_hash_tags_max: 投稿に設定可能なハッシュタグの最大数
|
||||
test_error: NGワードのテストに失敗しました。正規表現のミスが含まれているかもしれません
|
||||
title: NGワードとスパム
|
||||
relationships:
|
||||
title: "%{acct} さんのフォロー・フォロワー"
|
||||
|
@ -769,6 +770,14 @@ ja:
|
|||
edit: ルールを編集
|
||||
empty: サーバーのルールが定義されていません。
|
||||
title: サーバーのルール
|
||||
sensitive_words:
|
||||
alert: この投稿にはセンシティブなキーワードが含まれるため、警告文が追加されました
|
||||
hint: センシティブなキーワードの設定は、当サーバーのローカルユーザーによる公開範囲「公開」「ローカル公開」「ログインユーザーのみ」に対して適用されます。
|
||||
keywords: センシティブなキーワード(警告文は除外)
|
||||
keywords_for_all: センシティブなキーワード(警告文にも適用)
|
||||
keywords_for_all_hint: ここで指定したキーワードを含む投稿は強制的にCWになります。警告文にも含まれていればCWになります
|
||||
keywords_hint: ここで指定したキーワードを含む投稿は強制的にCWになります。ただし警告文に使用していた場合は無視されます
|
||||
title: センシティブ単語と設定
|
||||
settings:
|
||||
about:
|
||||
manage_rules: サーバーのルールを管理
|
||||
|
@ -1851,8 +1860,8 @@ ja:
|
|||
explanation:
|
||||
delete_statuses: あなたの投稿のいくつかは、1つ以上のコミュニティガイドラインに違反していることが判明し、%{instance}のモデレータによって削除されました。
|
||||
disable: アカウントは使用できませんが、プロフィールやその他のデータはそのまま残ります。 データのバックアップをリクエストしたり、アカウント設定を変更したり、アカウントを削除したりできます。
|
||||
force_cw: あなたのいくつかの投稿は、%{instance}のモデレータによって閲覧注意としてマークされています。これは、投稿本文が表示される前にユーザが投稿内のボタンをタップする必要があることを意味します。あなたは将来投稿する際に自分自身で文章に警告を記述することができます。
|
||||
mark_statuses_as_sensitive: あなたのいくつかの投稿は、%{instance}のモデレータによって閲覧注意としてマークされています。これは、プレビューが表示される前にユーザが投稿内のメディアをタップする必要があることを意味します。あなたは将来投稿する際に自分自身でメディアを閲覧注意としてマークすることができます。
|
||||
force_cw: あなたのいくつかの投稿は、%{instance}のモデレータによって閲覧注意としてマークされています。これは、投稿本文が表示される前にユーザが投稿内のボタンをタップする必要があることを意味します。あなたはこの投稿を削除する必要はありませんが、将来同様の投稿をする際に自分自身で文章に警告を追加してください。
|
||||
mark_statuses_as_sensitive: あなたのいくつかの投稿は、%{instance}のモデレータによって閲覧注意としてマークされています。これは、プレビューが表示される前にユーザが投稿内のメディアをタップする必要があることを意味します。あなたはこの投稿を削除する必要はありませんが、将来同様の投稿をする際に自分自身でメディアを閲覧注意としてマークしてください。
|
||||
sensitive: 今後、アップロードされたすべてのメディアファイルは閲覧注意としてマークされ、クリック解除式の警告で覆われるようになります。
|
||||
silence: アカウントが制限されています。このサーバーでは既にフォローしている人だけがあなたの投稿を見ることができます。 様々な発見機能から除外されるかもしれません。他の人があなたを手動でフォローすることは可能です。
|
||||
suspend: アカウントは使用できなくなり、プロフィールなどのデータにもアクセスできなくなります。約30日後にデータが完全に削除されるまでは、ログインしてデータのバックアップを要求することができますが、アカウントの停止回避を防ぐために一部の基本データを保持します。
|
||||
|
|
|
@ -36,11 +36,12 @@ 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, :manage_ng_words) } 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, :manage_sensitive_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 :ng_words, safe_join([fa_icon('list 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 :sensitive_words, safe_join([fa_icon('list fw'), t('admin.sensitive_words.title')]), admin_sensitive_words_path, highlights_on: %r{/admin/sensitive_words}, if: -> { current_user.can?(:manage_sensitive_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) }
|
||||
|
|
|
@ -34,6 +34,7 @@ namespace :admin do
|
|||
resources :warning_presets, except: [:new, :show]
|
||||
resources :media_attachments, only: [:index]
|
||||
resource :ng_words, only: [:show, :create]
|
||||
resource :sensitive_words, only: [:show, :create]
|
||||
|
||||
resources :announcements, except: [:show] do
|
||||
member do
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue