Merge branch 'main' into glitch-soc/merge-upstream
Conflicts: - `app/views/layouts/mailer.html.haml`: Upstream removed a line close to one modified by glitch-soc. Removed the line as upstream did.main
commit
ab59743c13
|
@ -21,7 +21,17 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
|
|||
private
|
||||
|
||||
def account_params
|
||||
params.permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, fields_attributes: [:name, :value])
|
||||
params.permit(
|
||||
:display_name,
|
||||
:note,
|
||||
:avatar,
|
||||
:header,
|
||||
:locked,
|
||||
:bot,
|
||||
:discoverable,
|
||||
:hide_collections,
|
||||
fields_attributes: [:name, :value]
|
||||
)
|
||||
end
|
||||
|
||||
def user_settings_params
|
||||
|
|
|
@ -73,7 +73,7 @@ class Admin::StatusBatchAction
|
|||
# Can't use a transaction here because UpdateStatusService queues
|
||||
# Sidekiq jobs
|
||||
statuses.includes(:media_attachments, :preview_cards).find_each do |status|
|
||||
next unless status.with_media? || status.with_preview_card?
|
||||
next if status.discarded? || !(status.with_media? || status.with_preview_card?)
|
||||
|
||||
authorize([:admin, status], :update?)
|
||||
|
||||
|
@ -89,15 +89,15 @@ class Admin::StatusBatchAction
|
|||
report.resolve!(current_account)
|
||||
log_action(:resolve, report)
|
||||
end
|
||||
|
||||
@warning = target_account.strikes.create!(
|
||||
action: :mark_statuses_as_sensitive,
|
||||
account: current_account,
|
||||
report: report,
|
||||
status_ids: status_ids
|
||||
)
|
||||
end
|
||||
|
||||
@warning = target_account.strikes.create!(
|
||||
action: :mark_statuses_as_sensitive,
|
||||
account: current_account,
|
||||
report: report,
|
||||
status_ids: status_ids
|
||||
)
|
||||
|
||||
UserMailer.warning(target_account.user, @warning).deliver_later! if warnable?
|
||||
end
|
||||
|
||||
|
|
|
@ -55,7 +55,14 @@ module Omniauthable
|
|||
|
||||
user = User.new(user_params_from_auth(email, auth))
|
||||
|
||||
user.account.avatar_remote_url = auth.info.image if /\A#{URI::DEFAULT_PARSER.make_regexp(%w(http https))}\z/.match?(auth.info.image)
|
||||
begin
|
||||
if /\A#{URI::DEFAULT_PARSER.make_regexp(%w(http https))}\z/.match?(auth.info.image)
|
||||
user.account.avatar_remote_url = auth.info.image
|
||||
end
|
||||
rescue Mastodon::UnexpectedResponseError
|
||||
user.account.avatar_remote_url = nil
|
||||
end
|
||||
|
||||
user.skip_confirmation! if email_is_verified
|
||||
user.save!
|
||||
user
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
= link_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(@report), method: :post, class: 'button'
|
||||
.report-actions__item__description
|
||||
= t('admin.reports.actions.resolve_description_html')
|
||||
- if @statuses.any? { |status| status.with_media? || status.with_preview_card? }
|
||||
- if @statuses.any? { |status| (status.with_media? || status.with_preview_card?) && !status.discarded? }
|
||||
.report-actions__item
|
||||
.report-actions__item__button
|
||||
= button_tag t('admin.reports.mark_as_sensitive'), name: :mark_as_sensitive, class: 'button'
|
||||
|
|
|
@ -177,7 +177,7 @@
|
|||
- if @report.unresolved?
|
||||
%hr.spacer/
|
||||
|
||||
%p#actions= t 'admin.reports.actions_description_html'
|
||||
%p#actions= t(@report.target_account.local? ? 'admin.reports.actions_description_html' : 'admin.reports.actions_description_remote_html')
|
||||
|
||||
= render partial: 'admin/reports/actions'
|
||||
|
||||
|
|
|
@ -29,6 +29,16 @@
|
|||
.fields-group
|
||||
= f.input :noindex, as: :boolean, wrapper: :with_label, label: t('admin.settings.default_noindex.title'), hint: t('admin.settings.default_noindex.desc_html')
|
||||
|
||||
%h4= t('admin.settings.discovery.publish_statistics')
|
||||
|
||||
.fields-group
|
||||
= f.input :activity_api_enabled, as: :boolean, wrapper: :with_label, recommended: :recommended
|
||||
|
||||
%h4= t('admin.settings.discovery.publish_discovered_servers')
|
||||
|
||||
.fields-group
|
||||
= f.input :peers_api_enabled, as: :boolean, wrapper: :with_label, recommended: :recommended
|
||||
|
||||
%h4= t('admin.settings.discovery.follow_recommendations')
|
||||
|
||||
.fields-group
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
%meta{ 'http-equiv' => 'Content-Type', 'content' => 'text/html; charset=utf-8' }/
|
||||
%meta{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, shrink-to-fit=no' }
|
||||
|
||||
%title/
|
||||
|
||||
= stylesheet_pack_tag 'core/mailer'
|
||||
%body{ dir: locale_direction }
|
||||
%table.email-table{ cellspacing: 0, cellpadding: 0 }
|
||||
|
|
|
@ -39,11 +39,11 @@
|
|||
%label.batch-table__toolbar__select.batch-checkbox-all
|
||||
= check_box_tag :batch_checkbox_all, nil, false
|
||||
.batch-table__toolbar__actions
|
||||
= f.button safe_join([fa_icon('user-plus'), t('relationships.follow_selected_followers')]), name: :follow, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } if followed_by_relationship? && !mutual_relationship?
|
||||
= f.button safe_join([fa_icon('user-plus'), t('relationships.follow_selected_followers')]), name: :follow, class: 'table-action-link', type: :submit, data: { confirm: t('relationships.confirm_follow_selected_followers') } if followed_by_relationship? && !mutual_relationship?
|
||||
|
||||
= f.button safe_join([fa_icon('user-times'), t('relationships.remove_selected_follows')]), name: :unfollow, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } unless followed_by_relationship?
|
||||
= f.button safe_join([fa_icon('user-times'), t('relationships.remove_selected_follows')]), name: :unfollow, class: 'table-action-link', type: :submit, data: { confirm: t('relationships.confirm_remove_selected_follows') } unless followed_by_relationship?
|
||||
|
||||
= f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_followers')]), name: :remove_from_followers, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } unless following_relationship?
|
||||
= f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_followers')]), name: :remove_from_followers, class: 'table-action-link', type: :submit, data: { confirm: t('relationships.confirm_remove_selected_followers') } unless following_relationship?
|
||||
|
||||
= f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_domains')]), name: :block_domains, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } if followed_by_relationship?
|
||||
.batch-table__body
|
||||
|
|
|
@ -575,9 +575,10 @@ en:
|
|||
mark_as_sensitive_description_html: The media in the reported posts will be marked as sensitive and a strike will be recorded to help you escalate on future infractions by the same account.
|
||||
other_description_html: See more options for controlling the account's behaviour and customize communication to the reported account.
|
||||
resolve_description_html: No action will be taken against the reported account, no strike recorded, and the report will be closed.
|
||||
silence_description_html: The profile will be visible only to those who already follow it or manually look it up, severely limiting its reach. Can always be reverted.
|
||||
suspend_description_html: The profile and all its contents will become inaccessible until it is eventually deleted. Interacting with the account will be impossible. Reversible within 30 days.
|
||||
silence_description_html: The account will be visible only to those who already follow it or manually look it up, severely limiting its reach. Can always be reverted. Closes all reports against this account.
|
||||
suspend_description_html: The account and all its contents will be inaccessible and eventually deleted, and interacting with it will be impossible. Reversible within 30 days. Closes all reports against this account.
|
||||
actions_description_html: Decide which action to take to resolve this report. If you take a punitive action against the reported account, an e-mail notification will be sent to them, except when the <strong>Spam</strong> category is selected.
|
||||
actions_description_remote_html: Decide which action to take to resolve this report. This will only affect how <strong>your</strong> server communicates with this remote account and handle its content.
|
||||
add_to_report: Add more to report
|
||||
are_you_sure: Are you sure?
|
||||
assign_to_self: Assign to me
|
||||
|
@ -713,6 +714,8 @@ en:
|
|||
preamble: Surfacing interesting content is instrumental in onboarding new users who may not know anyone Mastodon. Control how various discovery features work on your server.
|
||||
profile_directory: Profile directory
|
||||
public_timelines: Public timelines
|
||||
publish_discovered_servers: Publish discovered servers
|
||||
publish_statistics: Publish statistics
|
||||
title: Discovery
|
||||
trends: Trends
|
||||
domain_blocks:
|
||||
|
@ -1368,6 +1371,9 @@ en:
|
|||
unrecognized_emoji: is not a recognized emoji
|
||||
relationships:
|
||||
activity: Account activity
|
||||
confirm_follow_selected_followers: Are you sure you want to follow selected followers?
|
||||
confirm_remove_selected_followers: Are you sure you want to remove selected followers?
|
||||
confirm_remove_selected_follows: Are you sure you want to remove selected follows?
|
||||
dormant: Dormant
|
||||
follow_selected_followers: Follow selected followers
|
||||
followers: Followers
|
||||
|
|
|
@ -1343,6 +1343,9 @@ ko:
|
|||
unrecognized_emoji: 인식 되지 않은 에모지입니다
|
||||
relationships:
|
||||
activity: 계정 활동
|
||||
confirm_follow_selected_followers: 정말로 선택된 팔로워들을 팔로우 하시겠습니까?
|
||||
confirm_remove_selected_followers: 정말로 선택된 팔로워들을 삭제하시겠습니까?
|
||||
confirm_remove_selected_follows: 정말로 선택된 팔로우를 끊으시겠습니까?
|
||||
dormant: 휴면
|
||||
follow_selected_followers: 선택한 팔로워들을 팔로우
|
||||
followers: 팔로워
|
||||
|
|
|
@ -18,8 +18,8 @@ en:
|
|||
disable: Prevent the user from using their account, but do not delete or hide their contents.
|
||||
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.
|
||||
suspend: Prevent any interaction from or to this account and delete its contents. Revertible within 30 days.
|
||||
silence: Prevent the user from being able to post with public visibility, hide their posts and notifications from people not following them. Closes all reports against this account.
|
||||
suspend: Prevent any interaction from or to this account and delete its contents. Revertible within 30 days. Closes all reports against this account.
|
||||
warning_preset_id: Optional. You can still add custom text to end of the preset
|
||||
announcement:
|
||||
all_day: When checked, only the dates of the time range will be displayed
|
||||
|
@ -74,6 +74,7 @@ en:
|
|||
hide: Completely hide the filtered content, behaving as if it did not exist
|
||||
warn: Hide the filtered content behind a warning mentioning the filter's title
|
||||
form_admin_settings:
|
||||
activity_api_enabled: Counts of locally published posts, active users, and new registrations in weekly buckets
|
||||
backups_retention_period: Keep generated user archives for the specified number of days.
|
||||
bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations.
|
||||
closed_registrations_message: Displayed when sign-ups are closed
|
||||
|
@ -81,6 +82,7 @@ en:
|
|||
custom_css: You can apply custom styles on the web version of Mastodon.
|
||||
mascot: Overrides the illustration in the advanced web interface.
|
||||
media_cache_retention_period: Downloaded media files will be deleted after the specified number of days when set to a positive value, and re-downloaded on demand.
|
||||
peers_api_enabled: A list of domain names this server has encountered in the fediverse. No data is included here about whether you federate with a given server, just that your server knows about it. This is used by services that collect statistics on federation in a general sense.
|
||||
profile_directory: The profile directory lists all users who have opted-in to be discoverable.
|
||||
require_invite_text: When sign-ups require manual approval, make the “Why do you want to join?” text input mandatory rather than optional
|
||||
site_contact_email: How people can reach you for legal or support inquiries.
|
||||
|
@ -229,6 +231,7 @@ en:
|
|||
hide: Hide completely
|
||||
warn: Hide with a warning
|
||||
form_admin_settings:
|
||||
activity_api_enabled: Publish aggregate statistics about user activity in the API
|
||||
backups_retention_period: User archive retention period
|
||||
bootstrap_timeline_accounts: Always recommend these accounts to new users
|
||||
closed_registrations_message: Custom message when sign-ups are not available
|
||||
|
@ -236,6 +239,7 @@ en:
|
|||
custom_css: Custom CSS
|
||||
mascot: Custom mascot (legacy)
|
||||
media_cache_retention_period: Media cache retention period
|
||||
peers_api_enabled: Publish list of discovered servers in the API
|
||||
profile_directory: Enable profile directory
|
||||
registrations_mode: Who can sign-up
|
||||
require_invite_text: Require a reason to join
|
||||
|
|
|
@ -553,6 +553,116 @@ module Mastodon
|
|||
end
|
||||
end
|
||||
|
||||
option :concurrency, type: :numeric, default: 5, aliases: [:c]
|
||||
option :dry_run, type: :boolean
|
||||
desc 'prune', 'Prune remote accounts that never interacted with local users'
|
||||
long_desc <<-LONG_DESC
|
||||
Prune remote account that
|
||||
- follows no local accounts
|
||||
- is not followed by any local accounts
|
||||
- has no statuses on local
|
||||
- has not been mentioned
|
||||
- has not been favourited local posts
|
||||
- not muted/blocked by us
|
||||
LONG_DESC
|
||||
def prune
|
||||
dry_run = options[:dry_run] ? ' (dry run)' : ''
|
||||
|
||||
query = Account.remote.where.not(actor_type: %i(Application Service))
|
||||
query = query.where('NOT EXISTS (SELECT 1 FROM mentions WHERE account_id = accounts.id)')
|
||||
query = query.where('NOT EXISTS (SELECT 1 FROM favourites WHERE account_id = accounts.id)')
|
||||
query = query.where('NOT EXISTS (SELECT 1 FROM statuses WHERE account_id = accounts.id)')
|
||||
query = query.where('NOT EXISTS (SELECT 1 FROM follows WHERE account_id = accounts.id OR target_account_id = accounts.id)')
|
||||
query = query.where('NOT EXISTS (SELECT 1 FROM blocks WHERE account_id = accounts.id OR target_account_id = accounts.id)')
|
||||
query = query.where('NOT EXISTS (SELECT 1 FROM mutes WHERE target_account_id = accounts.id)')
|
||||
query = query.where('NOT EXISTS (SELECT 1 FROM reports WHERE target_account_id = accounts.id)')
|
||||
query = query.where('NOT EXISTS (SELECT 1 FROM follow_requests WHERE account_id = accounts.id OR target_account_id = accounts.id)')
|
||||
|
||||
_, deleted = parallelize_with_progress(query) do |account|
|
||||
next if account.bot? || account.group?
|
||||
next if account.suspended?
|
||||
next if account.silenced?
|
||||
|
||||
account.destroy unless options[:dry_run]
|
||||
1
|
||||
end
|
||||
|
||||
say("OK, pruned #{deleted} accounts#{dry_run}", :green)
|
||||
end
|
||||
|
||||
option :force, type: :boolean
|
||||
option :replay, type: :boolean
|
||||
option :target
|
||||
desc 'migrate USERNAME', 'Migrate a local user to another account'
|
||||
long_desc <<~LONG_DESC
|
||||
With --replay, replay the last migration of the specified account, in
|
||||
case some remote server may not have properly processed the associated
|
||||
`Move` activity.
|
||||
|
||||
With --target, specify another account to migrate to.
|
||||
|
||||
With --force, perform the migration even if the selected account
|
||||
redirects to a different account that the one specified.
|
||||
LONG_DESC
|
||||
def migrate(username)
|
||||
if options[:replay].present? && options[:target].present?
|
||||
say('Use --replay or --target, not both', :red)
|
||||
exit(1)
|
||||
end
|
||||
|
||||
if options[:replay].blank? && options[:target].blank?
|
||||
say('Use either --replay or --target', :red)
|
||||
exit(1)
|
||||
end
|
||||
|
||||
account = Account.find_local(username)
|
||||
|
||||
if account.nil?
|
||||
say("No such account: #{username}", :red)
|
||||
exit(1)
|
||||
end
|
||||
|
||||
migration = nil
|
||||
|
||||
if options[:replay]
|
||||
migration = account.migrations.last
|
||||
if migration.nil?
|
||||
say('The specified account has not performed any migration', :red)
|
||||
exit(1)
|
||||
end
|
||||
|
||||
unless options[:force] || migration.target_acount_id == account.moved_to_account_id
|
||||
say('The specified account is not redirecting to its last migration target. Use --force if you want to replay the migration anyway', :red)
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
if options[:target]
|
||||
target_account = ResolveAccountService.new.call(options[:target])
|
||||
|
||||
if target_account.nil?
|
||||
say("The specified target account could not be found: #{options[:target]}", :red)
|
||||
exit(1)
|
||||
end
|
||||
|
||||
unless options[:force] || account.moved_to_account_id.nil? || account.moved_to_account_id == target_account.id
|
||||
say('The specified account is redirecting to a different target account. Use --force if you want to change the migration target', :red)
|
||||
exit(1)
|
||||
end
|
||||
|
||||
begin
|
||||
migration = account.migrations.create!(acct: target_account.acct)
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
say("Error: #{e.message}", :red)
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
MoveService.new.call(migration)
|
||||
|
||||
say("OK, migrated #{account.acct} to #{migration.target_account.acct}", :green)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def rotate_keys_for_account(account, delay = 0)
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe Admin::Reports::ActionsController do
|
||||
render_views
|
||||
|
||||
let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
|
||||
let(:account) { Fabricate(:account) }
|
||||
let!(:status) { Fabricate(:status, account: account) }
|
||||
let(:media_attached_status) { Fabricate(:status, account: account) }
|
||||
let!(:media_attachment) { Fabricate(:media_attachment, account: account, status: media_attached_status) }
|
||||
let(:media_attached_deleted_status) { Fabricate(:status, account: account, deleted_at: 1.day.ago) }
|
||||
let!(:media_attachment2) { Fabricate(:media_attachment, account: account, status: media_attached_deleted_status) }
|
||||
let(:last_media_attached_status) { Fabricate(:status, account: account) }
|
||||
let!(:last_media_attachment) { Fabricate(:media_attachment, account: account, status: last_media_attached_status) }
|
||||
let!(:last_status) { Fabricate(:status, account: account) }
|
||||
|
||||
before do
|
||||
sign_in user, scope: :user
|
||||
end
|
||||
|
||||
describe 'POST #create' do
|
||||
let(:report) { Fabricate(:report, status_ids: status_ids, account: user.account, target_account: account) }
|
||||
let(:status_ids) { [media_attached_status.id, media_attached_deleted_status.id] }
|
||||
|
||||
before do
|
||||
post :create, params: { report_id: report.id, action => '' }
|
||||
end
|
||||
|
||||
context 'when action is mark_as_sensitive' do
|
||||
|
||||
let(:action) { 'mark_as_sensitive' }
|
||||
|
||||
it 'resolves the report' do
|
||||
expect(report.reload.action_taken_at).to_not be_nil
|
||||
end
|
||||
|
||||
it 'marks the non-deleted as sensitive' do
|
||||
expect(media_attached_status.reload.sensitive).to eq true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue