From 745bdb11a0d81cc4aff3fe3bba5eecdb8671a632 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 13 Jan 2023 17:00:23 +0100 Subject: Add `tootctl accounts migrate` (#22330) * Add tootctl accounts replay-migration Fixes #22281 * Change `tootctl accounts replay-migration` to `tootctl accounts migrate` --- lib/mastodon/accounts_cli.rb | 73 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) (limited to 'lib') diff --git a/lib/mastodon/accounts_cli.rb b/lib/mastodon/accounts_cli.rb index 0dd852131..693c9547c 100644 --- a/lib/mastodon/accounts_cli.rb +++ b/lib/mastodon/accounts_cli.rb @@ -553,6 +553,79 @@ module Mastodon end 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) -- cgit From 0e8f8a1a1c225272596b3256e3adb0a20a0dc483 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Sat, 14 Jan 2023 06:34:16 +0900 Subject: Implement tootctl accounts prune (#18397) * Implement tootctl accounts prune * Optimise query Co-authored-by: Claire --- lib/mastodon/accounts_cli.rb | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) (limited to 'lib') diff --git a/lib/mastodon/accounts_cli.rb b/lib/mastodon/accounts_cli.rb index 693c9547c..34afbc699 100644 --- a/lib/mastodon/accounts_cli.rb +++ b/lib/mastodon/accounts_cli.rb @@ -553,6 +553,43 @@ 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 -- cgit