Add hard silence mode

This commit is contained in:
noellabo 2022-05-16 21:57:18 +09:00
parent 609d431ac0
commit 79dfb04f64
21 changed files with 100 additions and 17 deletions

View file

@ -24,6 +24,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController
disabled
sensitized
silenced
hard_silenced
suspended
username
display_name

View file

@ -320,6 +320,18 @@ class ActivityPub::Activity
end
end
def visibility_from_audience_with_silence
visibility = visibility_from_audience
if @account.hard_silenced? && %i(public, unlisted).include?(visibility)
:private
elsif @account.silenced? && %i(public).include?(visibility)
:unlisted
else
visibility
end
end
def audience_includes?(account)
uri = ActivityPub::TagManager.instance.uri_for(account)
audience_to.include?(uri) || audience_cc.include?(uri)

View file

@ -52,7 +52,7 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
uri: @json['id'],
created_at: @json['published'],
override_timestamps: @options[:override_timestamps],
visibility: visibility_from_audience,
visibility: visibility_from_audience_with_silence,
expires_at: @json['expiry'],
expires_action: :mark,
}

View file

@ -112,7 +112,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
override_timestamps: @options[:override_timestamps],
reply: @object['inReplyTo'].present?,
sensitive: @account.sensitized? || @object['sensitive'] || false,
visibility: visibility_from_audience,
visibility: visibility_from_audience_with_silence,
thread: replied_to_status,
conversation: conversation_from_context,
media_attachment_ids: process_attachments.take(4).map(&:id),

View file

@ -48,6 +48,7 @@
# suspension_origin :integer
# sensitized_at :datetime
# settings :jsonb default("{}"), not null
# silence_mode :integer default(0), not null
#
class Account < ApplicationRecord
@ -83,6 +84,7 @@ class Account < ApplicationRecord
enum protocol: [:ostatus, :activitypub]
enum suspension_origin: [:local, :remote], _prefix: true
enum silence_mode: { soft: 0, hard: 1 }, _suffix: :silence_mode
validates :username, presence: true
validates_with UniqueUsernameValidator, if: -> { will_save_change_to_username? }
@ -101,6 +103,8 @@ class Account < ApplicationRecord
scope :local, -> { where(domain: nil) }
scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) }
scope :silenced, -> { where.not(silenced_at: nil) }
scope :soft_silenced, -> { where.not(silenced_at: nil).where(silence_mode: :soft) }
scope :hard_silenced, -> { where.not(silenced_at: nil).where(silence_mode: :hard) }
scope :suspended, -> { where.not(suspended_at: nil) }
scope :sensitized, -> { where.not(sensitized_at: nil) }
scope :without_suspended, -> { where(suspended_at: nil) }
@ -233,13 +237,21 @@ class Account < ApplicationRecord
end
def silence!(date = Time.now.utc)
update!(silenced_at: date)
update!(silenced_at: date, silence_mode: :soft)
end
def unsilence!
update!(silenced_at: nil)
end
def hard_silenced?
silenced_at.present? && hard_silence_mode?
end
def hard_silence!(date = Time.now.utc)
update!(silenced_at: date, silence_mode: :hard)
end
def suspended?
suspended_at.present? && !instance_actor?
end

View file

@ -7,7 +7,8 @@ class AccountFilter
by_domain
active
pending
silenced
soft_silenced
hard_silenced
suspended
username
display_name
@ -38,7 +39,7 @@ class AccountFilter
def set_defaults!
params['local'] = '1' if params['remote'].blank?
params['active'] = '1' if params['suspended'].blank? && params['silenced'].blank? && params['pending'].blank?
params['active'] = '1' if params['suspended'].blank? && params['soft_silenced'].blank? && params['hard_silenced'].blank? && params['pending'].blank?
params['order'] = 'recent' if params['order'].blank?
end
@ -58,6 +59,10 @@ class AccountFilter
accounts_with_users.merge(User.disabled)
when 'silenced'
Account.silenced
when 'soft_silenced'
Account.soft_silenced
when 'hard_silenced'
Account.hard_silenced
when 'suspended'
Account.suspended
when 'username'

View file

@ -13,7 +13,7 @@
#
class AccountWarning < ApplicationRecord
enum action: %i(none disable sensitive silence suspend), _suffix: :action
enum action: %i(none disable hard_silence sensitive silence suspend), _suffix: :action
belongs_to :account, inverse_of: :account_warnings
belongs_to :target_account, class_name: 'Account', inverse_of: :targeted_account_warnings

View file

@ -10,6 +10,7 @@ class Admin::AccountAction
disable
sensitive
silence
hard_silence
suspend
).freeze
@ -69,6 +70,8 @@ class Admin::AccountAction
handle_sensitive!
when 'silence'
handle_silence!
when 'hard_silence'
handle_hard_silence!
when 'suspend'
handle_suspend!
end
@ -124,6 +127,12 @@ class Admin::AccountAction
target_account.silence!
end
def handle_hard_silence!
authorize(target_account, :silence?)
log_action(:hard_silence, target_account)
target_account.hard_silence!
end
def handle_suspend!
authorize(target_account, :suspend?)
log_action(:suspend, target_account)

View file

@ -40,6 +40,7 @@ class Admin::ActionLogFilter
resolve_report: { target_type: 'Report', action: 'resolve' }.freeze,
sensitive_account: { target_type: 'Account', action: 'sensitive' }.freeze,
silence_account: { target_type: 'Account', action: 'silence' }.freeze,
hard_silence_account: { target_type: 'Account', action: 'hard_silence' }.freeze,
suspend_account: { target_type: 'Account', action: 'suspend' }.freeze,
unassigned_report: { target_type: 'Report', action: 'unassigned' }.freeze,
unsensitive_account: { target_type: 'Account', action: 'unsensitive' }.freeze,

View file

@ -30,7 +30,7 @@ class BlockDomainService < BaseService
end
def silence_accounts!
blocked_domain_accounts.without_silenced.in_batches.update_all(silenced_at: @domain_block.created_at)
blocked_domain_accounts.without_silenced.in_batches.update_all(silenced_at: @domain_block.created_at, silence_mode: :hard)
end
def suspend_accounts!

View file

@ -73,6 +73,7 @@ class PostStatusService < BaseService
@text = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present?
@visibility = @options[:visibility] || @account.user&.setting_default_privacy
@visibility = :unlisted if @visibility&.to_sym == :public && @account.silenced?
@visibility = :private if %i(public unlisted).include?(@visibility&.to_sym) && @account.hard_silenced?
@visibility = :limited if @circle.present?
@visibility = :limited if @visibility&.to_sym != :direct && @in_reply_to&.limited_visibility?
@scheduled_at = @options[:scheduled_at].is_a?(Time) ? @options[:scheduled_at] : @options[:scheduled_at]&.to_datetime&.to_time

View file

@ -11,9 +11,10 @@
%strong= t('admin.accounts.moderation.title')
%ul
%li= link_to safe_join([t('admin.accounts.moderation.pending'), "(#{number_with_delimiter(User.pending.count)})"], ' '), admin_pending_accounts_path
%li= filter_link_to t('admin.accounts.moderation.active'), silenced: nil, suspended: nil, pending: nil
%li= filter_link_to t('admin.accounts.moderation.silenced'), silenced: '1', suspended: nil, pending: nil
%li= filter_link_to t('admin.accounts.moderation.suspended'), suspended: '1', silenced: nil, pending: nil
%li= filter_link_to t('admin.accounts.moderation.active'), soft_silenced: nil, hard_silenced: nil, suspended: nil, pending: nil
%li= filter_link_to t('admin.accounts.moderation.soft_silenced'), soft_silenced: '1', hard_silenced: nil, suspended: nil, pending: nil
%li= filter_link_to t('admin.accounts.moderation.hard_silenced'), hard_silenced: '1', soft_silenced: nil, suspended: nil, pending: nil
%li= filter_link_to t('admin.accounts.moderation.suspended'), suspended: '1', soft_silenced: nil, hard_silenced: nil, pending: nil
.filter-subset
%strong= t('admin.accounts.role')
%ul

View file

@ -65,6 +65,8 @@
= t('admin.accounts.memorialized')
- elsif @account.suspended?
= t('admin.accounts.suspended')
- elsif @account.hard_silenced?
= t('admin.accounts.hard_silenced')
- elsif @account.silenced?
= t('admin.accounts.silenced')
- elsif @account.local? && @account.user&.disabled?
@ -228,6 +230,7 @@
= 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' if can?(:silence, @account)
= link_to t('admin.accounts.hard_silence'), new_admin_account_action_path(@account.id, type: 'hard_silence'), class: 'button' if can?(:silence, @account)
- if @account.local?
- if @account.user_pending?

View file

@ -87,6 +87,7 @@
= link_to t('admin.accounts.warn'), new_admin_account_action_path(@report.target_account_id, type: 'none', report_id: @report.id), class: 'button'
= link_to t('admin.accounts.disable'), new_admin_account_action_path(@report.target_account_id, type: 'disable', report_id: @report.id), class: 'button button--destructive'
= link_to t('admin.accounts.silence'), new_admin_account_action_path(@report.target_account_id, type: 'silence', report_id: @report.id), class: 'button button--destructive'
= link_to t('admin.accounts.hard_silence'), new_admin_account_action_path(@report.target_account_id, type: 'hard_silence', report_id: @report.id), class: 'button button--destructive'
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@report.target_account_id, type: 'suspend', report_id: @report.id), class: 'button button--destructive'
%hr.spacer

View file

@ -141,6 +141,8 @@ en:
enabled_msg: Successfully unfroze %{username}'s account
followers: Followers
follows: Follows
hard_silence: Hard limit
hard_silenced: Hard limited
header: Header
inbox_url: Inbox URL
invite_request_text: Reasons for joining
@ -160,6 +162,7 @@ en:
moderation:
active: Active
all: All
hard_silenced: Hard limited
pending: Pending
silenced: Limited
suspended: Suspended
@ -262,6 +265,7 @@ en:
enable_sign_in_token_auth_user: Enable E-mail Token Authentication for User
enable_user: Enable User
expire_status: Close Post
hard_silence_account: Hard limit Account
memorialize_account: Memorialize Account
promote_user: Promote User
remove_avatar_user: Remove Avatar
@ -308,6 +312,7 @@ en:
enable_sign_in_token_auth_user_html: "%{name} enabled e-mail token authentication for %{target}"
enable_user_html: "%{name} enabled login for user %{target}"
expire_status_html: "%{name} closed post by %{target}"
hard_silence_account_html: "%{name} hard limited %{target}'s account"
memorialize_account_html: "%{name} turned %{target}'s account into a memoriam page"
promote_user_html: "%{name} promoted user %{target}"
remove_avatar_user_html: "%{name} removed %{target}'s avatar"
@ -1612,6 +1617,7 @@ en:
explanation:
disable: You can no longer login to your account or use it in any other way, but your profile and other data remains intact.
sensitive: Your uploaded media files and linked media will be treated as sensitive.
hard_silence: You can still use your account but only people who are already following you will see your posts on this server, and you may be excluded from various public listings. However, others may still manually follow you.
silence: You can still use your account but only people who are already following you will see your posts on this server, and you may be excluded from various public listings. However, others may still manually follow you.
suspend: You can no longer use your account, and your profile and other data are no longer accessible. You can still login to request a backup of your data until the data is fully removed, but we will retain some data to prevent you from evading the suspension.
get_in_touch: You can reply to this e-mail to get in touch with the staff of %{instance}.
@ -1619,12 +1625,14 @@ en:
statuses: 'Specifically, for:'
subject:
disable: Your account %{acct} has been frozen
hard_silence: Your account %{acct} has been limited
none: Warning for %{acct}
sensitive: Your account %{acct} posting media has been marked as sensitive
silence: Your account %{acct} has been limited
suspend: Your account %{acct} has been suspended
title:
disable: Account frozen
hard_silence: Account limited
none: Warning
sensitive: Your media has been marked as sensitive
silence: Account limited

View file

@ -133,6 +133,8 @@ ja:
enabled_msg: "%{username} の無効化を解除しました"
followers: フォロワー数
follows: フォロー数
hard_silence: ハードサイレンス
hard_silenced: ハードサイレンス済み
header: ヘッダー
inbox_url: Inbox URL
invite_request_text: 意気込み
@ -152,8 +154,10 @@ ja:
moderation:
active: アクティブ
all: すべて
hard_silenced: ハードサイレンス済み
pending: 承認待ち
silenced: サイレンス済み
soft_silenced: ソフトサイレンス済み
suspended: 停止済み
title: モデレーション
moderation_notes: モデレーションメモ
@ -248,6 +252,7 @@ ja:
enable_custom_emoji: カスタム絵文字を有効化
enable_user: ユーザーを有効化
expire_status: 投稿を公開終了
hard_silence_account: アカウントをハードサイレンス
memorialize_account: 追悼アカウント化
promote_user: ユーザーを昇格
remove_avatar_user: アイコンを削除
@ -292,6 +297,7 @@ ja:
enable_custom_emoji_html: "%{name} さんがカスタム絵文字 %{target} を有効化しました"
enable_user_html: "%{name} さんが %{target} さんのログインを有効化しました"
expire_status_html: "%{name} さんが %{target} さんの投稿を公開終了しました"
hard_silence_account_html: "%{name} さんが %{target} さんをハードサイレンスにしました"
memorialize_account_html: "%{name} さんが %{target} さんを追悼アカウントページに登録しました"
promote_user_html: "%{name} さんが %{target} さんを昇格しました"
remove_avatar_user_html: "%{name} さんが %{target} さんのアイコンを削除しました"
@ -1513,6 +1519,7 @@ ja:
warning:
explanation:
disable: あなたのアカウントはログインが禁止され使用できなくなりました。しかしアカウントのデータはそのまま残っています。
hard_silence: あなたのアカウントは制限されましたがそのまま使用できます。ただし既にフォローしている人はあなたの投稿を見ることができますが、様々な公開タイムラインには表示されない場合があります。また他のユーザーは今後も手動であなたをフォローすることができます。
sensitive: あなたのアップロードしたメディアファイルとリンク先のメディアは、閲覧注意として扱われます。
silence: あなたのアカウントは制限されましたがそのまま使用できます。ただし既にフォローしている人はあなたの投稿を見ることができますが、様々な公開タイムラインには表示されない場合があります。また他のユーザーは今後も手動であなたをフォローすることができます。
suspend: あなたのアカウントは使用できなくなりプロフィールやその他データにアクセスできなくなりました。アカウントが完全に削除されるまではログインしてデータのエクスポートをリクエストできます。証拠隠滅を防ぐため一部のデータは削除されず残ります。
@ -1521,12 +1528,14 @@ ja:
statuses: '特に次の投稿:'
subject:
disable: あなたのアカウント %{acct} は凍結されました
hard_silence: あなたのアカウント %{acct} はサイレンスにされました
none: "%{acct} に対する警告"
sensitive: あなたのアカウント %{acct} の投稿メディアは閲覧注意とマークされました
silence: あなたのアカウント %{acct} はサイレンスにされました
suspend: あなたのアカウント %{acct} は停止されました
title:
disable: アカウントが凍結されました
hard_silence: アカウントがサイレンスにされました
none: 警告
sensitive: あなたのメディアが閲覧注意とマークされました
silence: アカウントがサイレンスにされました

View file

@ -18,9 +18,10 @@ en:
type_html: Choose what to do with <strong>%{acct}</strong>
types:
disable: Prevent the user from using their account, but do not delete or hide their contents.
hard_silence: Force users to private visibility to prevent them from posting in public visibility, hide their posts and notifications from people not following them.
none: Use this to send a warning to the user, without triggering any other action.
sensitive: Force all this user's media attachments to be flagged as sensitive.
silence: Prevent the user from being able to post with public visibility, hide their posts and notifications from people not following them.
silence: Force users to unlisted visibility to prevent them from posting in public visibility, hide their posts and notifications from people not following them.
suspend: Prevent any interaction from or to this account and delete its contents. Revertible within 30 days.
warning_preset_id: Optional. You can still add custom text to end of the preset
announcement:
@ -158,6 +159,7 @@ en:
type: Action
types:
disable: Freeze
hard_silence: Hard limit
none: Send a warning
sensitive: Sensitive
silence: Limit

View file

@ -18,9 +18,10 @@ ja:
type_html: "<strong>%{acct}</strong>さんに対し、何を行うか選択してください"
types:
disable: ユーザーが自分のアカウントを使用できないようにします。コンテンツを削除したり非表示にすることはありません。
hard_silence: ユーザーが公開投稿できないようフォロワー限定に強制し、フォローしていない人に投稿や通知が表示されないようにする。
none: これを使用すると、他の操作をせずにユーザーに警告を送信できます。
sensitive: このユーザーが添付したメディアを強制的に閲覧注意にする
silence: ユーザーが公開投稿できないようし、フォローしていない人に投稿や通知が表示されないようにする。
silence: ユーザーが公開投稿できないよう未収載に強制し、フォローしていない人に投稿や通知が表示されないようにする。
suspend: このアカウントとのやりとりを止め、コンテンツを削除します。30日以内は取消可能です。
warning_preset_id: オプションです。プリセット警告文の末尾に任意の文字列を追加することができます
announcement:
@ -158,6 +159,7 @@ ja:
type: アクション
types:
disable: ログインを無効化
hard_silence: ハードサイレンス
none: 警告を送信
sensitive: 閲覧注意
silence: サイレンス

View file

@ -0,0 +1,15 @@
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddSilenceModeToAccount < ActiveRecord::Migration[6.1]
include Mastodon::MigrationHelpers
disable_ddl_transaction!
def up
safety_assured { add_column_with_default :accounts, :silence_mode, :integer, default: 0, allow_null: false }
end
def down
remove_column :accounts, :silence_mode
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2022_04_13_004654) do
ActiveRecord::Schema.define(version: 2023_01_29_193248) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -208,6 +208,7 @@ ActiveRecord::Schema.define(version: 2022_04_13_004654) do
t.integer "suspension_origin"
t.datetime "sensitized_at"
t.jsonb "settings", default: "{}", null: false
t.integer "silence_mode", default: 0, 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), COALESCE(lower((domain)::text), ''::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"

View file

@ -115,16 +115,16 @@ RSpec.describe Admin::AccountAction, type: :model do
context 'account.local?' do
let(:account) { Fabricate(:account, domain: nil) }
it 'returns ["none", "disable", "sensitive", "silence", "suspend"]' do
expect(subject).to eq %w(none disable sensitive silence suspend)
it 'returns ["none", "disable", "hard_silence", "sensitive", "silence", "suspend"]' do
expect(subject).to eq %w(none disable hard_silence sensitive silence suspend)
end
end
context '!account.local?' do
let(:account) { Fabricate(:account, domain: 'hoge.com') }
it 'returns ["sensitive", "silence", "suspend"]' do
expect(subject).to eq %w(sensitive silence suspend)
it 'returns ["hard_silence", "sensitive", "silence", "suspend"]' do
expect(subject).to eq %w(hard_silence sensitive silence suspend)
end
end
end