Moderation: add `force sensitive` and `force unlisted` actions. Accounts: add federatable `adult content` tag. Handle from remote accounts as well.
parent
5c59d1837f
commit
3b06175e8f
|
@ -2,7 +2,7 @@
|
|||
|
||||
module Admin
|
||||
class AccountsController < BaseController
|
||||
before_action :set_account, only: [:show, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize, :approve, :reject]
|
||||
before_action :set_account, only: [:show, :redownload, :remove_avatar, :remove_header, :enable, :allow_public, :allow_nonsensitive, :unsilence, :unsuspend, :memorialize, :approve, :reject]
|
||||
before_action :require_remote_account!, only: [:redownload]
|
||||
before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
|
||||
|
||||
|
@ -45,6 +45,34 @@ module Admin
|
|||
redirect_to admin_accounts_path(pending: '1')
|
||||
end
|
||||
|
||||
def force_sensitive
|
||||
authorize @account, :force_sensitive?
|
||||
@account.force_sensitive!
|
||||
log_action :force_sensitive, @account
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
def allow_nonsensitive
|
||||
authorize @account, :allow_nonsensitive?
|
||||
@account.allow_nonsensitive!
|
||||
log_action :allow_nonsensitive, @account
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
def force_unlisted
|
||||
authorize @account, :force_unlisted?
|
||||
@account.force_unlisted!
|
||||
log_action :force_unlisted, @account
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
def allow_public
|
||||
authorize @account, :allow_public?
|
||||
@account.allow_public!
|
||||
log_action :allow_public, @account
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
def unsilence
|
||||
authorize @account, :unsilence?
|
||||
@account.unsilence!
|
||||
|
|
|
@ -53,7 +53,7 @@ module Admin
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports)
|
||||
params.require(:domain_block).permit(:domain, :severity, :force_sensitive, :reject_media, :reject_reports)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,7 +25,7 @@ class Settings::ProfilesController < Settings::BaseController
|
|||
private
|
||||
|
||||
def account_params
|
||||
params.require(:account).permit(:display_name, :note, :avatar, :header, :replies, :locked, :hidden, :unlisted, :bot, :discoverable, fields_attributes: [:name, :value])
|
||||
params.require(:account).permit(:display_name, :note, :avatar, :header, :replies, :locked, :hidden, :unlisted, :adults_only, :bot, :discoverable, fields_attributes: [:name, :value])
|
||||
end
|
||||
|
||||
def set_account
|
||||
|
|
|
@ -19,7 +19,7 @@ module Admin::ActionLogsHelper
|
|||
elsif log.target_type == 'User' && [:change_email].include?(log.action)
|
||||
log.recorded_changes.slice('email', 'unconfirmed_email')
|
||||
elsif log.target_type == 'DomainBlock'
|
||||
log.recorded_changes.slice('severity', 'reject_media')
|
||||
log.recorded_changes.slice('severity', 'reject_media', 'force_sensitive')
|
||||
elsif log.target_type == 'Status' && log.action == :update
|
||||
log.recorded_changes.slice('sensitive')
|
||||
end
|
||||
|
@ -55,13 +55,13 @@ module Admin::ActionLogsHelper
|
|||
|
||||
def class_for_log_icon(log)
|
||||
case log.action
|
||||
when :enable, :unsuspend, :unsilence, :confirm, :promote, :resolve
|
||||
when :enable, :allow_public, :allow_nonsensitive, :unsuspend, :unsilence, :confirm, :promote, :resolve
|
||||
'positive'
|
||||
when :create
|
||||
opposite_verbs?(log) ? 'negative' : 'positive'
|
||||
when :update, :reset_password, :disable_2fa, :memorialize, :change_email
|
||||
'neutral'
|
||||
when :demote, :silence, :disable, :suspend, :remove_avatar, :remove_header, :reopen
|
||||
when :demote, :force_sensitive, :force_unlisted, :silence, :disable, :suspend, :remove_avatar, :remove_header, :reopen
|
||||
'negative'
|
||||
when :destroy
|
||||
opposite_verbs?(log) ? 'positive' : 'negative'
|
||||
|
|
|
@ -38,6 +38,7 @@ module StreamEntriesHelper
|
|||
content_tag(:div, class: 'roles') do
|
||||
roles = []
|
||||
roles << content_tag(:div, t('accounts.roles.bot'), class: 'account-role bot') if account.bot?
|
||||
roles << content_tag(:div, t('accounts.roles.adults_only'), class: 'account-role adults-only') if account.adults_only?
|
||||
roles << content_tag(:div, t('accounts.roles.gentlies_kobolds'), class: 'account-role gentlies') if account&.user&.setting_gently_kobolds
|
||||
roles << content_tag(:div, t('accounts.roles.kobold'), class: 'account-role kobold') if account&.user&.setting_user_is_kobold
|
||||
|
||||
|
|
|
@ -189,7 +189,8 @@ class Header extends ImmutablePureComponent {
|
|||
const content = { __html: account.get('note_emojified') };
|
||||
const displayNameHtml = { __html: account.get('display_name_html') };
|
||||
const fields = account.get('fields');
|
||||
const badge = account.get('bot') ? (<div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div>) : null;
|
||||
const badge_bot = account.get('bot') ? (<div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div>) : null;
|
||||
const badge_ao = account.get('adults_only') ? (<div className='account-role adults-only'><FormattedMessage id='account.badges.adults_only' defaultMessage="🔞 Adult content" /></div>) : null;
|
||||
const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
|
||||
|
||||
return (
|
||||
|
@ -219,7 +220,7 @@ class Header extends ImmutablePureComponent {
|
|||
|
||||
<div className='account__header__tabs__name'>
|
||||
<h1>
|
||||
<span dangerouslySetInnerHTML={displayNameHtml} /> {badge}
|
||||
<span dangerouslySetInnerHTML={displayNameHtml} /> {badge_ao}{badge_bot}
|
||||
<small>@{acct} {lockedIcon}</small>
|
||||
</h1>
|
||||
</div>
|
||||
|
@ -243,7 +244,7 @@ class Header extends ImmutablePureComponent {
|
|||
{fields.map((pair, i) => (
|
||||
<dl key={i}>
|
||||
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />
|
||||
|
||||
|
||||
<dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')}>
|
||||
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
|
||||
</dd>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"account.add_or_remove_from_list": "Add or Remove from lists",
|
||||
"account.badges.bot": "Bot",
|
||||
"account.badges.adults_only": "🔞 Adult content",
|
||||
"account.block": "Block @{name}",
|
||||
"account.block_domain": "Hide {domain}",
|
||||
"account.blocked": "Blocked",
|
||||
|
|
|
@ -34,6 +34,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
process_tags
|
||||
process_audience
|
||||
|
||||
@params[:visibility] = :unlisted if @params[:visibility] == :public && @account.force_unlisted?
|
||||
@params[:sensitive] = true if @account.force_sensitive?
|
||||
|
||||
ApplicationRecord.transaction do
|
||||
@status = Status.create!(@params)
|
||||
attach_tags(@status)
|
||||
|
|
|
@ -19,6 +19,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
|
|||
focal_point: { 'toot' => 'http://joinmastodon.org/ns#', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' } },
|
||||
identity_proof: { 'toot' => 'http://joinmastodon.org/ns#', 'IdentityProof' => 'toot:IdentityProof' },
|
||||
blurhash: { 'toot' => 'http://joinmastodon.org/ns#', 'blurhash' => 'toot:blurhash' },
|
||||
adults_only: { 'schema' => 'http://schema.org#', 'suggestedMinAge' => 'schema:suggestedMinAge' }
|
||||
}.freeze
|
||||
|
||||
def self.default_key_transform
|
||||
|
|
|
@ -48,6 +48,9 @@
|
|||
# vars :jsonb not null
|
||||
# replies :boolean default(TRUE), not null
|
||||
# unlisted :boolean default(FALSE), not null
|
||||
# force_unlisted :boolean default(FALSE), not null
|
||||
# force_sensitive :boolean default(FALSE), not null
|
||||
# adults_only :boolean default(FALSE), not null
|
||||
#
|
||||
|
||||
class Account < ApplicationRecord
|
||||
|
@ -120,6 +123,7 @@ class Account < ApplicationRecord
|
|||
:moderator?,
|
||||
:staff?,
|
||||
:locale,
|
||||
:default_sensitive?,
|
||||
:hides_network?,
|
||||
:shows_application?,
|
||||
:always_local?,
|
||||
|
@ -185,6 +189,28 @@ class Account < ApplicationRecord
|
|||
ResolveAccountService.new.call(acct)
|
||||
end
|
||||
|
||||
def force_unlisted!
|
||||
transaction do
|
||||
update!(force_unlisted: true)
|
||||
Status.where(account_id: id, visibility: :public).in_batches.update_all(visibility: :unlisted)
|
||||
end
|
||||
end
|
||||
|
||||
def force_sensitive!
|
||||
transaction do
|
||||
update!(force_sensitive: true)
|
||||
Status.where(account_id: id, sensitive: false).in_batches.update_all(sensitive: true)
|
||||
end
|
||||
end
|
||||
|
||||
def allow_public!
|
||||
update!(force_unlisted: false)
|
||||
end
|
||||
|
||||
def allow_nonsensitive!
|
||||
update!(force_sensitive: false)
|
||||
end
|
||||
|
||||
def silenced?
|
||||
silenced_at.present?
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#
|
||||
|
||||
class AccountWarning < ApplicationRecord
|
||||
enum action: %i(none disable silence suspend), _suffix: :action
|
||||
enum action: %i(none disable force_sensitive force_unlisted silence suspend), _suffix: :action
|
||||
|
||||
belongs_to :account, inverse_of: :account_warnings
|
||||
belongs_to :target_account, class_name: 'Account', inverse_of: :targeted_account_warnings
|
||||
|
|
|
@ -8,6 +8,8 @@ class Admin::AccountAction
|
|||
TYPES = %w(
|
||||
none
|
||||
disable
|
||||
force_sensitive
|
||||
force_unlisted
|
||||
silence
|
||||
suspend
|
||||
).freeze
|
||||
|
@ -56,6 +58,10 @@ class Admin::AccountAction
|
|||
case type
|
||||
when 'disable'
|
||||
handle_disable!
|
||||
when 'force_sensitive'
|
||||
handle_force_sensitive!
|
||||
when 'force_unlisted'
|
||||
handle_force_unlisted!
|
||||
when 'silence'
|
||||
handle_silence!
|
||||
when 'suspend'
|
||||
|
@ -97,6 +103,18 @@ class Admin::AccountAction
|
|||
target_account.user&.disable!
|
||||
end
|
||||
|
||||
def handle_force_sensitive!
|
||||
authorize(target_account, :force_sensitive?)
|
||||
log_action(:force_sensitive, target_account.user)
|
||||
target_account.force_sensitive!
|
||||
end
|
||||
|
||||
def handle_force_unlisted!
|
||||
authorize(target_account, :force_unlisted?)
|
||||
log_action(:force_unlisted, target_account.user)
|
||||
target_account.force_unlisted!
|
||||
end
|
||||
|
||||
def handle_silence!
|
||||
authorize(target_account, :silence?)
|
||||
log_action(:silence, target_account)
|
||||
|
|
|
@ -3,19 +3,20 @@
|
|||
#
|
||||
# Table name: domain_blocks
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# domain :string default(""), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# severity :integer default("silence")
|
||||
# reject_media :boolean default(FALSE), not null
|
||||
# reject_reports :boolean default(FALSE), not null
|
||||
# id :bigint(8) not null, primary key
|
||||
# domain :string default(""), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# severity :integer default("noop")
|
||||
# reject_media :boolean default(FALSE), not null
|
||||
# reject_reports :boolean default(FALSE), not null
|
||||
# force_sensitive :boolean default(FALSE), not null
|
||||
#
|
||||
|
||||
class DomainBlock < ApplicationRecord
|
||||
include DomainNormalizable
|
||||
|
||||
enum severity: [:silence, :suspend, :noop]
|
||||
enum severity: [:noop, :force_unlisted, :silence, :suspend]
|
||||
|
||||
validates :domain, presence: true, uniqueness: true
|
||||
|
||||
|
@ -28,10 +29,15 @@ class DomainBlock < ApplicationRecord
|
|||
where(domain: domain, severity: :suspend).exists?
|
||||
end
|
||||
|
||||
def self.force_unlisted?(domain)
|
||||
where(domain: domain, severity: :force_unlisted).exists?
|
||||
end
|
||||
|
||||
def stricter_than?(other_block)
|
||||
return true if suspend?
|
||||
return false if other_block.suspend? && (silence? || noop?)
|
||||
return false if other_block.silence? && noop?
|
||||
return false if other_block.suspend? && !suspend?
|
||||
return false if other_block.silence? && (noop? || force_unlisted?)
|
||||
return false if other_block.force_unlisted? && noop?
|
||||
(reject_media || !other_block.reject_media) && (reject_reports || !other_block.reject_reports)
|
||||
end
|
||||
|
||||
|
|
|
@ -40,8 +40,6 @@ class Status < ApplicationRecord
|
|||
|
||||
# match both with and without U+FE0F (the emoji variation selector)
|
||||
LOCAL_ONLY_TOKENS = /(?:#!|\u{1f441}\ufe0f?)\u200b?\z/
|
||||
FORCE_SENSITIVE = ENV.fetch('FORCE_SENSITIVE', '').chomp.split(/\.?\s+/).freeze
|
||||
FORCE_UNLISTED = ENV.fetch('FORCE_UNLISTED', '').chomp.split(/\.?\s+/).freeze
|
||||
|
||||
# If `override_timestamps` is set at creation time, Snowflake ID creation
|
||||
# will be based on current time instead of `created_at`
|
||||
|
@ -561,9 +559,6 @@ class Status < ApplicationRecord
|
|||
def set_visibility
|
||||
self.visibility = reblog.visibility if reblog? && visibility.nil?
|
||||
self.visibility = (account.locked? ? :private : :public) if visibility.nil?
|
||||
self.visibility = :unlisted if visibility == :public && account.domain.in?(FORCE_UNLISTED)
|
||||
self.sensitive = true if account.domain.in?(FORCE_SENSITIVE)
|
||||
self.sensitive = false if sensitive.nil?
|
||||
end
|
||||
|
||||
def set_locality
|
||||
|
|
|
@ -309,6 +309,10 @@ class User < ApplicationRecord
|
|||
@hide_captions ||= (settings.hide_captions || false)
|
||||
end
|
||||
|
||||
def default_sensitive?
|
||||
@default_sensitive ||= settings.default_sensitive
|
||||
end
|
||||
|
||||
def setting_default_privacy
|
||||
settings.default_privacy || 'public'
|
||||
end
|
||||
|
|
|
@ -29,6 +29,22 @@ class AccountPolicy < ApplicationPolicy
|
|||
staff?
|
||||
end
|
||||
|
||||
def force_unlisted?
|
||||
staff?
|
||||
end
|
||||
|
||||
def allow_public?
|
||||
staff?
|
||||
end
|
||||
|
||||
def force_sensitive?
|
||||
staff?
|
||||
end
|
||||
|
||||
def allow_nonsensitive?
|
||||
staff?
|
||||
end
|
||||
|
||||
def redownload?
|
||||
admin?
|
||||
end
|
||||
|
|
|
@ -6,7 +6,8 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
|||
context :security
|
||||
|
||||
context_extensions :manually_approves_followers, :featured, :also_known_as,
|
||||
:moved_to, :property_value, :hashtag, :emoji, :identity_proof
|
||||
:moved_to, :property_value, :hashtag, :emoji, :identity_proof,
|
||||
:adults_only
|
||||
|
||||
attributes :id, :type, :following, :followers,
|
||||
:inbox, :outbox, :featured,
|
||||
|
@ -20,6 +21,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
|||
|
||||
attribute :moved_to, if: :moved?
|
||||
attribute :also_known_as, if: :also_known_as?
|
||||
attribute :adults_only, if: :adults_only?
|
||||
|
||||
class EndpointsSerializer < ActivityPub::Serializer
|
||||
include RoutingHelper
|
||||
|
@ -66,6 +68,10 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
|||
account_collection_url(object, :featured)
|
||||
end
|
||||
|
||||
def adults_only
|
||||
18
|
||||
end
|
||||
|
||||
def endpoints
|
||||
object
|
||||
end
|
||||
|
@ -126,6 +132,10 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
|||
!object.also_known_as.empty?
|
||||
end
|
||||
|
||||
def adults_only?
|
||||
object.adults_only
|
||||
end
|
||||
|
||||
class CustomEmojiSerializer < ActivityPub::EmojiSerializer
|
||||
end
|
||||
|
||||
|
|
|
@ -5,7 +5,8 @@ class REST::AccountSerializer < ActiveModel::Serializer
|
|||
|
||||
attributes :id, :username, :acct, :display_name, :locked, :bot, :created_at,
|
||||
:note, :url, :avatar, :avatar_static, :header, :header_static,
|
||||
:followers_count, :following_count, :statuses_count, :replies
|
||||
:followers_count, :following_count, :statuses_count, :replies,
|
||||
:adults_only
|
||||
|
||||
has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested?
|
||||
has_many :emojis, serializer: REST::CustomEmojiSerializer
|
||||
|
|
|
@ -48,11 +48,13 @@ class ActivityPub::ProcessAccountService < BaseService
|
|||
|
||||
def create_account
|
||||
@account = Account.new
|
||||
@account.username = @username
|
||||
@account.domain = @domain
|
||||
@account.private_key = nil
|
||||
@account.suspended_at = domain_block.created_at if auto_suspend?
|
||||
@account.silenced_at = domain_block.created_at if auto_silence?
|
||||
@account.username = @username
|
||||
@account.domain = @domain
|
||||
@account.private_key = nil
|
||||
@account.suspended_at = domain_block.created_at if auto_suspend?
|
||||
@account.silenced_at = domain_block.created_at if auto_silence?
|
||||
@account.force_unlisted = true if force_unlisted?
|
||||
@account.force_sensitive = true if force_sensitive?
|
||||
end
|
||||
|
||||
def update_account
|
||||
|
@ -75,6 +77,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
|||
@account.display_name = @json['name'] || ''
|
||||
@account.note = @json['summary'] || ''
|
||||
@account.locked = @json['manuallyApprovesFollowers'] || false
|
||||
@account.adults_only = @json['suggestedMinAge'].to_i >= 18
|
||||
@account.fields = property_values || {}
|
||||
@account.also_known_as = as_array(@json['alsoKnownAs'] || []).map { |item| value_or_id(item) }
|
||||
@account.actor_type = actor_type
|
||||
|
@ -195,6 +198,14 @@ class ActivityPub::ProcessAccountService < BaseService
|
|||
domain_block&.silence?
|
||||
end
|
||||
|
||||
def auto_force_unlisted?
|
||||
domain_block&.force_unlisted?
|
||||
end
|
||||
|
||||
def auto_force_sensitive?
|
||||
domain_block&.force_sensitive?
|
||||
end
|
||||
|
||||
def domain_block
|
||||
return @domain_block if defined?(@domain_block)
|
||||
@domain_block = DomainBlock.find_by(domain: @domain)
|
||||
|
|
|
@ -12,8 +12,11 @@ class BlockDomainService < BaseService
|
|||
|
||||
def process_domain_block!
|
||||
clear_media! if domain_block.reject_media?
|
||||
force_accounts_sensitive! if domain_block.force_sensitive?
|
||||
|
||||
if domain_block.silence?
|
||||
if domain_block.force_unlisted?
|
||||
force_accounts_unlisted!
|
||||
elsif domain_block.silence?
|
||||
silence_accounts!
|
||||
elsif domain_block.suspend?
|
||||
suspend_accounts!
|
||||
|
@ -28,6 +31,24 @@ class BlockDomainService < BaseService
|
|||
@affected_status_ids.each { |id| Rails.cache.delete_matched("statuses/#{id}-*") }
|
||||
end
|
||||
|
||||
def force_accounts_sensitive!
|
||||
ApplicationRecord.transaction do
|
||||
blocked_domain_accounts.in_batches.update_all(force_sensitive: true)
|
||||
blocked_domain_accounts.reorder(nil).find_each do |account|
|
||||
account.statuses.where(sensitive: false).in_batches.update_all(sensitive: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def force_accounts_unlisted!
|
||||
ApplicationRecord.transaction do
|
||||
blocked_domain_accounts.in_batches.update_all(force_unlisted: true)
|
||||
blocked_domain_accounts.reorder(nil).find_each do |account|
|
||||
account.statuses.with_public_visibility.in_batches.update_all(visibility: :unlisted)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def silence_accounts!
|
||||
blocked_domain_accounts.without_silenced.in_batches.update_all(silenced_at: @domain_block.created_at)
|
||||
end
|
||||
|
@ -44,7 +65,6 @@ class BlockDomainService < BaseService
|
|||
|
||||
def suspend_accounts!
|
||||
blocked_domain_accounts.without_suspended.reorder(nil).find_each do |account|
|
||||
UnsubscribeService.new.call(account) if account.subscribed?
|
||||
SuspendAccountService.new.call(account, suspended_at: @domain_block.created_at)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,6 +30,7 @@ class PostStatusService < BaseService
|
|||
@in_reply_to = @options[:thread]
|
||||
@tags = @options[:tags]
|
||||
@local_only = @options[:local_only]
|
||||
@sensitive = (@account.force_sensitive? ? true : @options[:sensitive])
|
||||
|
||||
return idempotency_duplicate if idempotency_given? && idempotency_duplicate?
|
||||
|
||||
|
@ -58,7 +59,7 @@ class PostStatusService < BaseService
|
|||
end
|
||||
|
||||
@visibility = @options[:visibility] || @account.user&.setting_default_privacy
|
||||
@visibility = :unlisted if @visibility == :public && @account.silenced?
|
||||
@visibility = :unlisted if @visibility.in?([nil, 'public']) && @account.silenced? || @account.force_unlisted
|
||||
|
||||
if @in_reply_to.present? && @in_reply_to.visibility.present?
|
||||
v = %w(public unlisted private direct limited)
|
||||
|
@ -67,6 +68,8 @@ class PostStatusService < BaseService
|
|||
|
||||
@local_only = true if @account.user_always_local? || @in_reply_to&.local_only
|
||||
|
||||
@sensitive = (@account.default_sensitive? || @options[:spoiler_text].present?) if @sensitive.nil?
|
||||
|
||||
@scheduled_at = @options[:scheduled_at]&.to_datetime
|
||||
@scheduled_at = nil if scheduled_in_the_past?
|
||||
rescue ArgumentError
|
||||
|
@ -176,7 +179,7 @@ class PostStatusService < BaseService
|
|||
media_attachments: @media || [],
|
||||
thread: @in_reply_to,
|
||||
poll_attributes: poll_attributes,
|
||||
sensitive: (@options[:sensitive].nil? ? @account.user&.setting_default_sensitive : @options[:sensitive]) || @options[:spoiler_text].present?,
|
||||
sensitive: @sensitive,
|
||||
spoiler_text: @options[:spoiler_text] || '',
|
||||
visibility: @visibility,
|
||||
local_only: @local_only,
|
||||
|
|
|
@ -27,6 +27,13 @@ class UnblockDomainService < BaseService
|
|||
end
|
||||
|
||||
def domain_block_impact
|
||||
domain_block.silence? ? :silenced_at : :suspended_at
|
||||
case domain_block.severity
|
||||
when :force_unlisted
|
||||
:force_unlisted
|
||||
when :silence
|
||||
:silenced_at
|
||||
when :suspend
|
||||
:suspended_at
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -141,42 +141,51 @@
|
|||
= fa_icon DeliveryFailureTracker.unavailable?(@account.shared_inbox_url) ? 'times' : 'check'
|
||||
|
||||
%div{ style: 'overflow: hidden' }
|
||||
%div{ style: 'float: right' }
|
||||
- if @account.local?
|
||||
= link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user)
|
||||
- if @account.user&.otp_required_for_login?
|
||||
= link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user)
|
||||
- if !@account.memorial? && @account.user_approved?
|
||||
= link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account)
|
||||
- if @account.local? && @account.user_approved?
|
||||
= link_to t('admin.accounts.warn'), new_admin_account_action_path(@account.id, type: 'none'), class: 'button' if can?(:warn, @account)
|
||||
|
||||
- if @account.force_sensitive?
|
||||
= link_to t('admin.accounts.allow_nonsensitive'), allow_nonsensitive_admin_account_path(@account.id), method: :post, class: 'button' if can?(:allow_nonsensitive, @account)
|
||||
- elsif !@account.local? || @account.user_approved?
|
||||
= link_to t('admin.accounts.force_sensitive'), new_admin_account_action_path(@account.id, type: 'force_sensitive'), class: 'button button--destructive' if can?(:force_sensitive, @account)
|
||||
|
||||
- if @account.force_unlisted?
|
||||
= link_to t('admin.accounts.allow_public'), allow_public_admin_account_path(@account.id), method: :post, class: 'button' if can?(:allow_public, @account)
|
||||
- elsif !@account.local? || @account.user_approved?
|
||||
= link_to t('admin.accounts.force_unlisted'), new_admin_account_action_path(@account.id, type: 'force_unlisted'), class: 'button button--destructive' if can?(:force_unlisted, @account)
|
||||
|
||||
- if @account.silenced?
|
||||
= link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsilence, @account)
|
||||
- elsif !@account.local? || @account.user_approved?
|
||||
= link_to t('admin.accounts.silence'), new_admin_account_action_path(@account.id, type: 'silence'), class: 'button button--destructive' if can?(:silence, @account)
|
||||
|
||||
- if @account.local?
|
||||
- if @account.user_pending?
|
||||
= link_to t('admin.accounts.approve'), approve_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:approve, @account.user)
|
||||
= link_to t('admin.accounts.reject'), reject_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, @account.user)
|
||||
|
||||
- unless @account.user_confirmed?
|
||||
= link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' if can?(:confirm, @account.user)
|
||||
|
||||
- if @account.suspended?
|
||||
= link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsuspend, @account)
|
||||
- elsif !@account.local? || @account.user_approved?
|
||||
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@account.id, type: 'suspend'), class: 'button button--destructive' if can?(:suspend, @account)
|
||||
|
||||
- unless @account.local?
|
||||
- if DomainBlock.where(domain: @account.domain).exists?
|
||||
= link_to t('admin.domain_blocks.undo'), admin_instance_path(@account.domain), class: 'button'
|
||||
- else
|
||||
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account)
|
||||
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain), class: 'button button--destructive'
|
||||
|
||||
%div{ style: 'float: left' }
|
||||
- if @account.local? && @account.user_approved?
|
||||
= link_to t('admin.accounts.warn'), new_admin_account_action_path(@account.id, type: 'none'), class: 'button' if can?(:warn, @account)
|
||||
- if @account.silenced?
|
||||
= link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsilence, @account)
|
||||
- elsif !@account.local? || @account.user_approved?
|
||||
= link_to t('admin.accounts.silence'), new_admin_account_action_path(@account.id, type: 'silence'), class: 'button button--destructive' if can?(:silence, @account)
|
||||
|
||||
- if @account.local?
|
||||
- if @account.user_pending?
|
||||
= link_to t('admin.accounts.approve'), approve_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:approve, @account.user)
|
||||
= link_to t('admin.accounts.reject'), reject_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, @account.user)
|
||||
|
||||
- unless @account.user_confirmed?
|
||||
= link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' if can?(:confirm, @account.user)
|
||||
|
||||
- if @account.suspended?
|
||||
= link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsuspend, @account)
|
||||
- elsif !@account.local? || @account.user_approved?
|
||||
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@account.id, type: 'suspend'), class: 'button button--destructive' if can?(:suspend, @account)
|
||||
|
||||
- unless @account.local?
|
||||
- if DomainBlock.where(domain: @account.domain).exists?
|
||||
= link_to t('admin.domain_blocks.undo'), admin_instance_path(@account.domain), class: 'button'
|
||||
- else
|
||||
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain), class: 'button button--destructive'
|
||||
- if @account.local?
|
||||
= link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user)
|
||||
- if @account.user&.otp_required_for_login?
|
||||
= link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user)
|
||||
- if !@account.memorial? && @account.user_approved?
|
||||
= link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account)
|
||||
- else
|
||||
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account)
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: lambda { |type| t(".severity.#{type}") }, hint: t('.severity.desc_html')
|
||||
|
||||
.fields-group
|
||||
= f.input :force_sensitive, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.force_sensitive'), hint: I18n.t('admin.domain_blocks.force_sensitive_hint')
|
||||
|
||||
.fields-group
|
||||
= f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_media'), hint: I18n.t('admin.domain_blocks.reject_media_hint')
|
||||
|
||||
|
|
|
@ -26,6 +26,9 @@
|
|||
= f.input :unlisted, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.unlisted')
|
||||
= f.input :replies, as: :boolean, wrapper: :with_label
|
||||
|
||||
.fields-group
|
||||
= f.input :adults_only, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.adults_only')
|
||||
|
||||
.fields-group
|
||||
= f.input :bot, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.bot')
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ en:
|
|||
moderator: Mod
|
||||
kobold: Gently the kobold
|
||||
gentlies_kobolds: Gentlies kobolds
|
||||
adults_only: 🔞 Adult content
|
||||
unavailable: Profile unavailable
|
||||
unfollow: Unfollow
|
||||
admin:
|
||||
|
@ -134,6 +135,8 @@ en:
|
|||
active: Active
|
||||
all: All
|
||||
pending: Pending
|
||||
force_sensitive: Sensitive
|
||||
force_unlisted: Unlisted
|
||||
silenced: Silenced
|
||||
suspended: Suspended
|
||||
title: Moderation
|
||||
|
@ -175,6 +178,8 @@ en:
|
|||
show:
|
||||
created_reports: Made reports
|
||||
targeted_reports: Reported by others
|
||||
force_unlisted: Force unlisted
|
||||
force_sensitive: Force sensitive
|
||||
silence: Silence
|
||||
silenced: Silenced
|
||||
statuses: Statuses
|
||||
|
@ -182,6 +187,8 @@ en:
|
|||
suspended: Suspended
|
||||
title: Accounts
|
||||
unconfirmed_email: Unconfirmed email
|
||||
allow_nonsensitive: Allow non-sensitive
|
||||
allow_public: Allow public
|
||||
undo_silenced: Undo silence
|
||||
undo_suspension: Undo suspension
|
||||
unsubscribe: Unsubscribe
|
||||
|
@ -213,9 +220,14 @@ en:
|
|||
reopen_report: "%{name} reopened report %{target}"
|
||||
reset_password_user: "%{name} reset password of creature %{target}"
|
||||
resolve_report: "%{name} resolved report %{target}"
|
||||
force_sensitive_user: "%{name} forced %{target}'s media to be marked sensitive"
|
||||
force_sensitive_account: "%{name} forced %{target}'s media to be marked sensitive"
|
||||
force_unlisted_account: "%{name} forced %{target}'s roars to be marked unlisted"
|
||||
silence_account: "%{name} silenced %{target}'s account"
|
||||
suspend_account: "%{name} suspended %{target}'s account"
|
||||
unassigned_report: "%{name} unassigned report %{target}"
|
||||
allow_nonsensitive_account: "%{name} allowed non-sensitive media from %{target}'s account"
|
||||
allow_public_account: "%{name} allowed public roars from %{target}'s account"
|
||||
unsilence_account: "%{name} unsilenced %{target}'s account"
|
||||
unsuspend_account: "%{name} unsuspended %{target}'s account"
|
||||
update_custom_emoji: "%{name} updated emoji %{target}"
|
||||
|
@ -281,11 +293,14 @@ en:
|
|||
create: Create block
|
||||
hint: The domain block will not prevent creation of account entries in the database, but will retroactively and automatically apply specific moderation methods on those accounts.
|
||||
severity:
|
||||
desc_html: "<strong>Silence</strong> will make the account's roars invisible to anyone who isn't following them. <strong>Suspend</strong> will remove all of the account's content, media, and profile data. Use <strong>None</strong> if you just want to reject media files."
|
||||
desc_html: "<strong>Force Unlisted</strong> will force the account's roars to a maximum of unlisted visibility. <strong>Silence</strong> will make the account's roars invisible to anyone who isn't following them as well as disable notification. <strong>Suspend</strong> will remove all of the account's content, media, and profile data. Use <strong>None</strong> if you just want to reject media files."
|
||||
noop: None
|
||||
force_unlisted: Force unlisted
|
||||
silence: Silence
|
||||
suspend: Suspend
|
||||
title: New domain block
|
||||
force_sensitive: Mark media sensitive
|
||||
force_sensitive_hint: Forces all media from this domain to be marked sensitive.
|
||||
reject_media: Reject media files
|
||||
reject_media_hint: Removes locally stored media files and refuses to download any in the future. Irrelevant for suspensions
|
||||
reject_reports: Reject reports
|
||||
|
@ -293,15 +308,17 @@ en:
|
|||
rejecting_media: rejecting media files
|
||||
rejecting_reports: rejecting reports
|
||||
severity:
|
||||
force_unlisted: force unlisted
|
||||
silence: silenced
|
||||
suspend: suspended
|
||||
show:
|
||||
affected_accounts:
|
||||
one: One account in the database affected
|
||||
other: "%{count} accounts in the database affected"
|
||||
one: One account affected
|
||||
other: "%{count} accounts affected"
|
||||
retroactive:
|
||||
silence: Unsilence existing affected accounts from this domain
|
||||
suspend: Unsuspend existing affected accounts from this domain
|
||||
force_unlisted: Allow public roars on all existing accounts from this domain
|
||||
title: Undo domain block for %{domain}
|
||||
undo: Undo
|
||||
undo: Undo domain block
|
||||
|
@ -1057,18 +1074,24 @@ en:
|
|||
warning:
|
||||
explanation:
|
||||
disable: While your account is frozen, your account data remains intact, but you cannot perform any actions until it is unlocked.
|
||||
force_sensitive: Your account's media has been forced to sensitive visibility until this limit is removed by a moderator.
|
||||
force_unlisted: Your account's roars have been forced to unlisted visibility until this limit is removed by a moderator.
|
||||
silence: While your account is limited, only monsters who are already following you will see your roars on this server, and you may be excluded from various public listings. However, others may still manually join your pack.
|
||||
suspend: Your account has been suspended, and all of your roars and your uploaded media files have been irreversibly removed from this server, and servers where you had packmates.
|
||||
suspend: Your account has been suspended. All of your roars and your uploaded media files have been irreversibly removed from this server, and servers where you had packmates.
|
||||
review_server_policies: Review server policies
|
||||
subject:
|
||||
disable: Your account %{acct} has been frozen
|
||||
none: Warning for %{acct}
|
||||
silence: Your account %{acct} has been limited
|
||||
suspend: Your account %{acct} has been suspended
|
||||
disable: "%{acct}, you account has been frozen."
|
||||
none: "%{acct}, you've been given a moderation warning."
|
||||
force_sensitive: "%{acct}, your account's media visibility has been limited."
|
||||
force_unlisted: "%{acct}, your account's roar visibility has been limited."
|
||||
silence: "%{acct}, your account has been silenced."
|
||||
suspend: "%{acct}, your account has been suspended."
|
||||
title:
|
||||
disable: Account frozen
|
||||
none: Warning
|
||||
silence: Account limited
|
||||
force_sensitive: Media visibility limited
|
||||
force_unlisted: Roar visibility limited
|
||||
silence: Account silenced
|
||||
suspend: Account suspended
|
||||
welcome:
|
||||
edit_profile_action: Setup profile
|
||||
|
|
|
@ -11,6 +11,7 @@ en:
|
|||
warning_preset_id: Optional. You can still add custom text to end of the preset
|
||||
defaults:
|
||||
hidden: Toggles whether your public profile is publicaly accessible
|
||||
adults_only: This account may contain mature or sensitive content
|
||||
unlisted: Excludes you from public repeated/admired by lists of *local* monsters
|
||||
autofollow: People who sign up through the invite will automatically join your pack
|
||||
avatar: PNG, GIF or JPG. At most %{size}. Will be downscaled to %{dimensions}px
|
||||
|
@ -66,11 +67,14 @@ en:
|
|||
types:
|
||||
disable: Disable
|
||||
none: Do nothing
|
||||
force_sensitive: Force sensitive
|
||||
force_unlisted: Force unlisted
|
||||
silence: Silence
|
||||
suspend: Suspend and irreversibly delete account data
|
||||
warning_preset_id: Use a warning preset
|
||||
defaults:
|
||||
hidden: Disable your public profile
|
||||
adults_only: Adult content
|
||||
unlisted: Exclude from public interaction lists
|
||||
replies: Show your public replies
|
||||
autofollow: Invite to join your pack
|
||||
|
|
|
@ -188,6 +188,10 @@ Rails.application.routes.draw do
|
|||
post :subscribe
|
||||
post :unsubscribe
|
||||
post :enable
|
||||
post :force_sensitive
|
||||
post :force_unlisted
|
||||
post :allow_public
|
||||
post :allow_nonsensitive
|
||||
post :unsilence
|
||||
post :unsuspend
|
||||
post :redownload
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
|
||||
class AddForceSensitiveToDomainBlocks < ActiveRecord::Migration[5.2]
|
||||
include Mastodon::MigrationHelpers
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
safety_assured do
|
||||
add_column_with_default :domain_blocks, :force_sensitive, :boolean, default: false, allow_null: false
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :domain_blocks, :force_sensitive
|
||||
end
|
||||
end
|
|
@ -0,0 +1,6 @@
|
|||
class AddNetworkIndexToStatuses < ActiveRecord::Migration[5.2]
|
||||
disable_ddl_transaction!
|
||||
def change
|
||||
add_index :statuses, :network, where: :network, algorithm: :concurrently
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
class UpdateDomainBlockSeverityEnum < ActiveRecord::Migration[5.2]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
DomainBlock.where(severity: :force_unlisted).each do |block|
|
||||
block.severity = :suspend
|
||||
block.save
|
||||
end
|
||||
|
||||
DomainBlock.where(severity: :noop).each do |block|
|
||||
block.severity = :silence
|
||||
block.save
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
raise ActiveRecord::IrreversibleMigration
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
class AddForceOptionsToAccounts < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
safety_assured {
|
||||
add_column :accounts, :force_unlisted, :boolean, null: false, default: false
|
||||
add_column :accounts, :force_sensitive, :boolean, null: false, default: false
|
||||
}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
class AddAdultsOnlyToAccounts < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
safety_assured { add_column :accounts, :adults_only, :boolean, null: false, default: false }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
class UpdateAccountWarningActionEnum < ActiveRecord::Migration[5.2]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
AccountWarning.where(action: :force_unlisted).each do |warning|
|
||||
warning.severity = :suspend
|
||||
warning.save
|
||||
end
|
||||
|
||||
AccountWarning.where(action: :force_sensitive).each do |warning|
|
||||
warning.severity = :silence
|
||||
warning.save
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
raise ActiveRecord::IrreversibleMigration
|
||||
end
|
||||
end
|
|
@ -151,6 +151,9 @@ ActiveRecord::Schema.define(version: 2019_05_19_130537) do
|
|||
t.jsonb "vars", default: {}, null: false
|
||||
t.boolean "replies", default: true, null: false
|
||||
t.boolean "unlisted", default: false, null: false
|
||||
t.boolean "force_unlisted", default: false, null: false
|
||||
t.boolean "force_sensitive", default: false, null: false
|
||||
t.boolean "adults_only", default: false, null: false
|
||||
t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
|
||||
t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower", unique: true
|
||||
t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id"
|
||||
|
@ -258,6 +261,7 @@ ActiveRecord::Schema.define(version: 2019_05_19_130537) do
|
|||
t.integer "severity", default: 0
|
||||
t.boolean "reject_media", default: false, null: false
|
||||
t.boolean "reject_reports", default: false, null: false
|
||||
t.boolean "force_sensitive", default: false, null: false
|
||||
t.index ["domain"], name: "index_domain_blocks_on_domain", unique: true
|
||||
end
|
||||
|
||||
|
@ -651,6 +655,7 @@ ActiveRecord::Schema.define(version: 2019_05_19_130537) do
|
|||
t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20180106", order: { id: :desc }
|
||||
t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id"
|
||||
t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id"
|
||||
t.index ["network"], name: "index_statuses_on_network", where: "network"
|
||||
t.index ["reblog_of_id", "account_id"], name: "index_statuses_on_reblog_of_id_and_account_id"
|
||||
t.index ["tsv"], name: "tsv_idx", using: :gin
|
||||
t.index ["uri"], name: "index_statuses_on_uri", unique: true
|
||||
|
|
Loading…
Reference in New Issue