2018-08-26 20:21:03 +02:00
# frozen_string_literal: true
2018-10-27 22:56:16 +02:00
require 'set'
2023-05-24 11:55:40 +02:00
require_relative 'base'
2018-08-26 20:21:03 +02:00
2023-05-23 16:08:26 +02:00
module Mastodon::CLI
2023-05-24 11:55:40 +02:00
class Accounts < Base
2018-08-26 20:21:03 +02:00
option :all , type : :boolean
desc 'rotate [USERNAME]' , 'Generate and broadcast new keys'
long_desc <<-LONG_DESC
Generate and broadcast new RSA keys as part of security
maintenance .
With the - - all option , all local accounts will be subject
to the rotation . Otherwise , and by default , only a single
account specified by the USERNAME argument will be
processed .
LONG_DESC
def rotate ( username = nil )
if options [ :all ]
processed = 0
delay = 0
2019-09-10 13:48:48 +02:00
scope = Account . local . without_suspended
progress = create_progress_bar ( scope . count )
2018-08-26 20:21:03 +02:00
2019-09-10 13:48:48 +02:00
scope . find_in_batches do | accounts |
2018-08-26 20:21:03 +02:00
accounts . each do | account |
rotate_keys_for_account ( account , delay )
2019-09-10 13:48:48 +02:00
progress . increment
2018-08-26 20:21:03 +02:00
processed += 1
end
delay += 5 . minutes
end
2019-09-10 13:48:48 +02:00
progress . finish
2018-08-26 20:21:03 +02:00
say ( " OK, rotated keys for #{ processed } accounts " , :green )
elsif username . present?
rotate_keys_for_account ( Account . find_local ( username ) )
say ( 'OK' , :green )
else
2024-01-26 09:53:44 +01:00
fail_with_message 'No account(s) given'
2018-08-26 20:21:03 +02:00
end
end
2018-09-09 13:33:36 +02:00
option :email , required : true
option :confirmed , type : :boolean
2022-07-05 02:41:40 +02:00
option :role
2018-09-09 13:33:36 +02:00
option :reattach , type : :boolean
option :force , type : :boolean
2023-04-14 14:41:15 +02:00
option :approve , type : :boolean
2022-08-13 15:41:12 +02:00
desc 'create USERNAME' , 'Create a new user account'
2018-09-09 13:33:36 +02:00
long_desc <<-LONG_DESC
Create a new user account with a given USERNAME and an
e - mail address provided with - - email .
With the - - confirmed option , the confirmation e - mail will
be skipped and the account will be active straight away .
2022-07-05 02:41:40 +02:00
With the - - role option , the role can be supplied .
2018-09-09 13:33:36 +02:00
With the - - reattach option , the new user will be reattached
to a given existing username of an old account . If the old
account is still in use by someone else , you can supply
the - - force option to delete the old record and reattach the
username to the new account anyway .
2023-04-14 14:41:15 +02:00
With the - - approve option , the account will be approved .
2018-09-09 13:33:36 +02:00
LONG_DESC
2018-09-14 17:42:22 +02:00
def create ( username )
2022-07-05 02:41:40 +02:00
role_id = nil
if options [ :role ]
role = UserRole . find_by ( name : options [ :role ] )
2024-01-26 09:53:44 +01:00
fail_with_message 'Cannot find user role with that name' if role . nil?
2022-07-05 02:41:40 +02:00
role_id = role . id
end
2018-09-09 13:33:36 +02:00
account = Account . new ( username : username )
password = SecureRandom . hex
2023-04-14 14:41:15 +02:00
user = User . new ( email : options [ :email ] , password : password , agreement : true , role_id : role_id , confirmed_at : options [ :confirmed ] ? Time . now . utc : nil , bypass_invite_request_check : true )
2018-09-09 13:33:36 +02:00
if options [ :reattach ]
account = Account . find_local ( username ) || Account . new ( username : username )
if account . user . present? && ! options [ :force ]
say ( 'The chosen username is currently in use' , :red )
say ( 'Use --force to reattach it anyway and delete the other user' )
return
elsif account . user . present?
2023-04-27 10:15:45 +02:00
DeleteAccountService . new . call ( account , reserve_email : false , reserve_username : false )
2023-04-23 22:29:31 +02:00
account = Account . new ( username : username )
2018-09-09 13:33:36 +02:00
end
end
2019-05-14 19:05:02 +02:00
account . suspended_at = nil
user . account = account
2018-09-09 13:33:36 +02:00
if user . save
if options [ :confirmed ]
user . confirmed_at = nil
2024-01-15 19:04:58 +01:00
user . mark_email_as_confirmed!
2018-09-09 13:33:36 +02:00
end
2023-04-14 14:41:15 +02:00
user . approve! if options [ :approve ]
2018-09-09 13:33:36 +02:00
say ( 'OK' , :green )
say ( " New password: #{ password } " )
else
2023-05-30 16:09:15 +02:00
report_errors ( user . errors )
2018-09-09 13:33:36 +02:00
end
end
2022-07-05 02:41:40 +02:00
option :role
2022-10-27 14:31:10 +02:00
option :remove_role , type : :boolean
2018-09-14 17:42:22 +02:00
option :email
option :confirm , type : :boolean
option :enable , type : :boolean
option :disable , type : :boolean
option :disable_2fa , type : :boolean
2019-05-03 20:49:27 +02:00
option :approve , type : :boolean
2020-02-22 01:29:14 +01:00
option :reset_password , type : :boolean
2022-08-13 15:41:12 +02:00
desc 'modify USERNAME' , 'Modify a user account'
2018-09-14 17:42:22 +02:00
long_desc <<-LONG_DESC
Modify a user account .
2022-10-27 14:31:10 +02:00
With the - - role option , update the user 's role. To remove the user' s
role , i . e . demote to normal user , use - - remove - role .
2018-09-14 17:42:22 +02:00
With the - - email option , update the user ' s e - mail address . With
the - - confirm option , mark the user ' s e - mail as confirmed .
With the - - disable option , lock the user out of their account . The
- - enable option is the opposite .
2019-05-03 20:49:27 +02:00
With the - - approve option , the account will be approved , if it was
previously not due to not having open registrations .
2018-09-14 17:42:22 +02:00
With the - - disable - 2 fa option , the two - factor authentication
requirement for the user can be removed .
2020-02-22 01:29:14 +01:00
With the - - reset - password option , the user ' s password is replaced by
a randomly - generated one , printed in the output .
2018-09-14 17:42:22 +02:00
LONG_DESC
def modify ( username )
user = Account . find_local ( username ) & . user
2024-01-26 09:53:44 +01:00
fail_with_message 'No user with such username' if user . nil?
2018-09-14 17:42:22 +02:00
if options [ :role ]
2022-07-05 02:41:40 +02:00
role = UserRole . find_by ( name : options [ :role ] )
2024-01-26 09:53:44 +01:00
fail_with_message 'Cannot find user role with that name' if role . nil?
2022-07-05 02:41:40 +02:00
user . role_id = role . id
2022-10-27 14:31:10 +02:00
elsif options [ :remove_role ]
user . role_id = nil
2018-09-14 17:42:22 +02:00
end
2020-02-22 01:29:14 +01:00
password = SecureRandom . hex if options [ :reset_password ]
user . password = password if options [ :reset_password ]
2018-09-14 17:42:22 +02:00
user . email = options [ :email ] if options [ :email ]
user . disabled = false if options [ :enable ]
user . disabled = true if options [ :disable ]
2019-05-03 20:49:27 +02:00
user . approved = true if options [ :approve ]
2018-09-14 17:42:22 +02:00
user . otp_required_for_login = false if options [ :disable_2fa ]
if user . save
2023-04-18 09:51:24 +02:00
user . confirm if options [ :confirm ]
2018-09-14 17:42:22 +02:00
say ( 'OK' , :green )
2020-02-22 01:29:14 +01:00
say ( " New password: #{ password } " ) if options [ :reset_password ]
2018-09-14 17:42:22 +02:00
else
2023-05-30 16:09:15 +02:00
report_errors ( user . errors )
2018-09-14 17:42:22 +02:00
end
end
2022-12-15 14:52:50 +01:00
option :email
option :dry_run , type : :boolean
desc 'delete [USERNAME]' , 'Delete a user'
2018-09-09 13:33:36 +02:00
long_desc <<-LONG_DESC
Remove a user account with a given USERNAME .
2022-12-15 14:52:50 +01:00
With the - - email option , the user is selected based on email
rather than username .
LONG_DESC
def delete ( username = nil )
if username . present? && options [ :email ] . present?
2024-01-26 09:53:44 +01:00
fail_with_message 'Use username or --email, not both'
2022-12-15 14:52:50 +01:00
elsif username . blank? && options [ :email ] . blank?
2024-01-26 09:53:44 +01:00
fail_with_message 'No username provided'
2022-12-15 14:52:50 +01:00
end
account = nil
if username . present?
account = Account . find_local ( username )
2024-01-26 09:53:44 +01:00
fail_with_message 'No user with such username' if account . nil?
2022-12-15 14:52:50 +01:00
else
account = Account . left_joins ( :user ) . find_by ( user : { email : options [ :email ] } )
2024-01-26 09:53:44 +01:00
fail_with_message 'No user with such email' if account . nil?
2018-09-09 13:33:36 +02:00
end
2023-05-30 16:07:44 +02:00
say ( " Deleting user with #{ account . statuses_count } statuses, this might take a while... #{ dry_run_mode_suffix } " )
DeleteAccountService . new . call ( account , reserve_email : false ) unless dry_run?
say ( " OK #{ dry_run_mode_suffix } " , :green )
2018-09-09 13:33:36 +02:00
end
2020-11-23 17:50:16 +01:00
option :force , type : :boolean , aliases : [ :f ] , description : 'Override public key check'
desc 'merge FROM TO' , 'Merge two remote accounts into one'
long_desc <<-LONG_DESC
Merge two remote accounts specified by their username @domain
into one , whereby the TO account is the one being merged into
and kept , while the FROM one is removed . It is primarily meant
to fix duplicates caused by other servers changing their domain .
The command by default only works if both accounts have the same
public key to prevent mistakes . To override this , use the - - force .
LONG_DESC
def merge ( from_acct , to_acct )
username , domain = from_acct . split ( '@' )
from_account = Account . find_remote ( username , domain )
2024-01-26 09:53:44 +01:00
fail_with_message " No such account ( #{ from_acct } ) " if from_account . nil? || from_account . local?
2020-11-23 17:50:16 +01:00
username , domain = to_acct . split ( '@' )
to_account = Account . find_remote ( username , domain )
2024-01-26 09:53:44 +01:00
fail_with_message " No such account ( #{ to_acct } ) " if to_account . nil? || to_account . local?
2020-11-23 17:50:16 +01:00
if from_account . public_key != to_account . public_key && ! options [ :force ]
2024-01-26 09:53:44 +01:00
fail_with_message << ~ ERROR
Accounts don ' t have the same public key , might not be duplicates!
Override with - - force
ERROR
2020-11-23 17:50:16 +01:00
end
to_account . merge_with! ( from_account )
from_account . destroy
say ( 'OK' , :green )
end
2020-12-18 23:26:26 +01:00
desc 'fix-duplicates' , 'Find duplicate remote accounts and merge them'
option :dry_run , type : :boolean
long_desc <<-LONG_DESC
Merge known remote accounts sharing an ActivityPub actor identifier .
Such duplicates can occur when a remote server admin misconfigures their
domain configuration .
LONG_DESC
def fix_duplicates
2020-12-19 13:34:16 +01:00
Account . remote . select ( :uri , 'count(*)' ) . group ( :uri ) . having ( 'count(*) > 1' ) . pluck ( :uri ) . each do | uri |
2020-12-18 23:26:26 +01:00
say ( " Duplicates found for #{ uri } " )
begin
2023-05-30 16:07:44 +02:00
ActivityPub :: FetchRemoteAccountService . new . call ( uri ) unless dry_run?
2020-12-18 23:26:26 +01:00
rescue = > e
say ( " Error processing #{ uri } : #{ e } " , :red )
end
end
end
2018-09-28 03:34:24 +02:00
desc 'backup USERNAME' , 'Request a backup for a user'
long_desc <<-LONG_DESC
Request a new backup for an account with a given USERNAME .
The backup will be created in Sidekiq asynchronously , and
the user will receive an e - mail with a link to it once
it ' s done .
LONG_DESC
def backup ( username )
account = Account . find_local ( username )
2024-01-26 09:53:44 +01:00
fail_with_message 'No user with such username' if account . nil?
2018-09-28 03:34:24 +02:00
backup = account . user . backups . create!
BackupWorker . perform_async ( backup . id )
say ( 'OK' , :green )
end
2019-09-10 13:48:48 +02:00
option :concurrency , type : :numeric , default : 5 , aliases : [ :c ]
2018-09-09 13:33:36 +02:00
option :dry_run , type : :boolean
2021-10-14 21:09:56 +02:00
desc 'cull [DOMAIN...]' , 'Remove remote accounts that no longer exist'
2018-09-09 13:33:36 +02:00
long_desc <<-LONG_DESC
Query every single remote account in the database to determine
if it still exists on the origin server , and if it doesn ' t ,
remove it from the database .
Accounts that have had confirmed activity within the last week
are excluded from the checks .
LONG_DESC
2021-10-14 21:09:56 +02:00
def cull ( * domains )
2018-10-27 22:56:16 +02:00
skip_threshold = 7 . days . ago
2019-09-10 13:48:48 +02:00
skip_domains = Concurrent :: Set . new
2018-09-09 13:33:36 +02:00
2024-03-14 14:19:20 +01:00
query = Account . remote . activitypub
2021-10-14 21:09:56 +02:00
query = query . where ( domain : domains ) unless domains . empty?
processed , culled = parallelize_with_progress ( query . partitioned ) do | account |
2019-09-10 13:48:48 +02:00
next if account . updated_at > = skip_threshold || ( account . last_webfingered_at . present? && account . last_webfingered_at > = skip_threshold ) || skip_domains . include? ( account . domain )
2018-09-09 13:33:36 +02:00
2019-04-04 16:46:27 +02:00
code = 0
2019-09-10 13:48:48 +02:00
begin
code = Request . new ( :head , account . uri ) . perform ( & :code )
2023-05-02 15:10:09 +02:00
rescue HTTP :: TimeoutError , HTTP :: ConnectionError , OpenSSL :: SSL :: SSLError , Mastodon :: PrivateNetworkAddressError
2019-09-10 13:48:48 +02:00
skip_domains << account . domain
2018-09-09 13:33:36 +02:00
end
2018-10-21 22:52:27 +02:00
if [ 404 , 410 ] . include? ( code )
2023-05-30 16:07:44 +02:00
DeleteAccountService . new . call ( account , reserve_username : false ) unless dry_run?
2019-09-10 13:48:48 +02:00
1
2018-09-09 13:33:36 +02:00
else
2019-09-10 13:48:48 +02:00
# Touch account even during dry run to avoid getting the account into the window again
account . touch
2018-09-09 13:33:36 +02:00
end
end
2023-05-30 16:07:44 +02:00
say ( " Visited #{ processed } accounts, removed #{ culled } #{ dry_run_mode_suffix } " , :green )
2018-09-09 13:33:36 +02:00
2018-10-27 22:56:16 +02:00
unless skip_domains . empty?
2019-09-10 13:48:48 +02:00
say ( 'The following domains were not available during the check:' , :yellow )
2022-07-05 02:41:40 +02:00
skip_domains . each { | domain | say ( " #{ domain } " ) }
2018-09-09 13:33:36 +02:00
end
end
2018-09-14 17:42:22 +02:00
option :all , type : :boolean
option :domain
2019-09-10 13:48:48 +02:00
option :concurrency , type : :numeric , default : 5 , aliases : [ :c ]
option :verbose , type : :boolean , aliases : [ :v ]
option :dry_run , type : :boolean
2023-03-08 17:06:53 +01:00
desc 'refresh [USERNAMES]' , 'Fetch remote user data and files'
2018-09-14 17:42:22 +02:00
long_desc <<-LONG_DESC
Fetch remote user data and files for one or multiple accounts .
With the - - all option , all remote accounts will be processed .
Through the - - domain option , this can be narrowed down to a
2023-03-08 17:06:53 +01:00
specific domain only . Otherwise , remote accounts must be
specified with space - separated USERNAMES .
2018-09-14 17:42:22 +02:00
LONG_DESC
2023-03-08 17:06:53 +01:00
def refresh ( * usernames )
2018-09-14 17:42:22 +02:00
if options [ :domain ] || options [ :all ]
scope = Account . remote
scope = scope . where ( domain : options [ :domain ] ) if options [ :domain ]
2019-09-10 13:48:48 +02:00
processed , = parallelize_with_progress ( scope ) do | account |
2023-05-30 16:07:44 +02:00
next if dry_run?
2019-09-10 13:48:48 +02:00
account . reset_avatar!
account . reset_header!
account . save
2018-09-14 17:42:22 +02:00
end
2023-05-30 16:07:44 +02:00
say ( " Refreshed #{ processed } accounts #{ dry_run_mode_suffix } " , :green , true )
2023-03-08 17:06:53 +01:00
elsif ! usernames . empty?
usernames . each do | user |
user , domain = user . split ( '@' )
account = Account . find_remote ( user , domain )
2024-01-26 09:53:44 +01:00
fail_with_message 'No such account' if account . nil?
2018-09-14 17:42:22 +02:00
2023-05-30 16:07:44 +02:00
next if dry_run?
2018-09-14 17:42:22 +02:00
2023-03-08 17:06:53 +01:00
begin
account . reset_avatar!
account . reset_header!
account . save
rescue Mastodon :: UnexpectedResponseError
say ( " Account failed: #{ user } @ #{ domain } " , :red )
end
2019-09-10 13:48:48 +02:00
end
2023-05-30 16:07:44 +02:00
say ( " OK #{ dry_run_mode_suffix } " , :green )
2018-09-14 17:42:22 +02:00
else
2024-01-26 09:53:44 +01:00
fail_with_message 'No account(s) given'
2018-09-14 17:42:22 +02:00
end
end
2019-09-10 13:48:48 +02:00
option :concurrency , type : :numeric , default : 5 , aliases : [ :c ]
option :verbose , type : :boolean , aliases : [ :v ]
desc 'follow USERNAME' , 'Make all local accounts follow account specified by USERNAME'
def follow ( username )
target_account = Account . find_local ( username )
2019-01-01 16:24:26 +01:00
2024-01-26 09:53:44 +01:00
fail_with_message 'No such account' if target_account . nil?
2019-01-01 16:24:26 +01:00
2019-09-10 13:48:48 +02:00
processed , = parallelize_with_progress ( Account . local . without_suspended ) do | account |
2020-12-26 23:52:46 +01:00
FollowService . new . call ( account , target_account , bypass_limit : true )
2019-01-01 16:24:26 +01:00
end
2019-09-10 13:48:48 +02:00
say ( " OK, followed target from #{ processed } accounts " , :green )
2019-01-01 16:24:26 +01:00
end
2019-09-10 13:48:48 +02:00
option :concurrency , type : :numeric , default : 5 , aliases : [ :c ]
option :verbose , type : :boolean , aliases : [ :v ]
2019-01-01 16:24:26 +01:00
desc 'unfollow ACCT' , 'Make all local accounts unfollow account specified by ACCT'
def unfollow ( acct )
2020-11-23 17:50:16 +01:00
username , domain = acct . split ( '@' )
target_account = Account . find_remote ( username , domain )
2019-01-01 16:24:26 +01:00
2024-01-26 09:53:44 +01:00
fail_with_message 'No such account' if target_account . nil?
2019-01-01 16:24:26 +01:00
2021-01-29 18:38:56 +01:00
processed , = parallelize_with_progress ( target_account . followers . local ) do | account |
2019-09-10 13:48:48 +02:00
UnfollowService . new . call ( account , target_account )
2019-01-01 16:24:26 +01:00
end
2019-09-10 13:48:48 +02:00
say ( " OK, unfollowed target from #{ processed } accounts " , :green )
2019-01-01 16:24:26 +01:00
end
2019-04-08 07:46:55 +02:00
option :follows , type : :boolean , default : false
option :followers , type : :boolean , default : false
desc 'reset-relationships USERNAME' , 'Reset all follows and/or followers for a user'
long_desc <<-LONG_DESC
Reset all follows and / or followers for a user specified by USERNAME .
With the - - follows option , the command unfollows everyone that the account follows ,
and then re - follows the users that would be followed by a brand new account .
With the - - followers option , the command removes all followers of the account .
LONG_DESC
def reset_relationships ( username )
2024-01-26 09:53:44 +01:00
fail_with_message 'Please specify either --follows or --followers, or both' unless options [ :follows ] || options [ :followers ]
2019-04-08 07:46:55 +02:00
account = Account . find_local ( username )
2024-01-26 09:53:44 +01:00
fail_with_message 'No such account' if account . nil?
2019-04-08 07:46:55 +02:00
2019-09-10 13:48:48 +02:00
total = 0
2023-12-06 15:15:54 +01:00
total += account . following . reorder ( nil ) . count if options [ :follows ]
total += account . followers . reorder ( nil ) . count if options [ :followers ]
2019-09-10 13:48:48 +02:00
progress = create_progress_bar ( total )
processed = 0
2019-04-08 07:46:55 +02:00
2019-09-10 13:48:48 +02:00
if options [ :follows ]
2023-12-06 15:15:54 +01:00
account . following . reorder ( nil ) . find_each do | target_account |
2023-02-18 23:09:40 +01:00
UnfollowService . new . call ( account , target_account )
rescue = > e
progress . log pastel . red ( " Error processing #{ target_account . id } : #{ e } " )
ensure
progress . increment
processed += 1
2019-04-08 07:46:55 +02:00
end
BootstrapTimelineWorker . perform_async ( account . id )
end
if options [ :followers ]
2023-12-06 15:15:54 +01:00
account . followers . reorder ( nil ) . find_each do | target_account |
2023-02-18 23:09:40 +01:00
UnfollowService . new . call ( target_account , account )
rescue = > e
progress . log pastel . red ( " Error processing #{ target_account . id } : #{ e } " )
ensure
progress . increment
processed += 1
2019-04-08 07:46:55 +02:00
end
end
2019-09-10 13:48:48 +02:00
progress . finish
say ( " Processed #{ processed } relationships " , :green , true )
2019-04-08 07:46:55 +02:00
end
2019-04-06 04:47:05 +02:00
option :number , type : :numeric , aliases : [ :n ]
option :all , type : :boolean
desc 'approve [USERNAME]' , 'Approve pending accounts'
long_desc << ~ LONG_DESC
When registrations require review from staff , approve pending accounts ,
either all of them with the - - all option , or a specific number of them
specified with the - - number ( - n ) option , or only a single specific
account identified by its username .
LONG_DESC
def approve ( username = nil )
2024-01-26 09:53:44 +01:00
fail_with_message 'Number must be positive' if options [ :number ] & . negative?
2019-04-06 04:47:05 +02:00
if options [ :all ]
User . pending . find_each ( & :approve! )
say ( 'OK' , :green )
2023-04-20 10:57:11 +02:00
elsif options [ :number ] & . positive?
2023-04-30 06:50:58 +02:00
User . pending . order ( created_at : :asc ) . limit ( options [ :number ] ) . each ( & :approve! )
2019-04-06 04:47:05 +02:00
say ( 'OK' , :green )
elsif username . present?
account = Account . find_local ( username )
2024-01-26 09:53:44 +01:00
fail_with_message 'No such account' if account . nil?
2019-04-06 04:47:05 +02:00
account . user & . approve!
say ( 'OK' , :green )
end
end
2023-01-13 22:34:16 +01:00
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
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?
2023-05-30 16:07:44 +02:00
account . destroy unless dry_run?
2023-01-13 22:34:16 +01:00
1
end
2023-05-30 16:07:44 +02:00
say ( " OK, pruned #{ deleted } accounts #{ dry_run_mode_suffix } " , :green )
2023-01-13 22:34:16 +01:00
end
2023-01-13 17:00:23 +01:00
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 )
2024-01-26 09:53:44 +01:00
fail_with_message 'Use --replay or --target, not both' if options [ :replay ] . present? && options [ :target ] . present?
2023-01-13 17:00:23 +01:00
2024-01-26 09:53:44 +01:00
fail_with_message 'Use either --replay or --target' if options [ :replay ] . blank? && options [ :target ] . blank?
2023-01-13 17:00:23 +01:00
account = Account . find_local ( username )
2024-01-26 09:53:44 +01:00
fail_with_message " No such account: #{ username } " if account . nil?
2023-01-13 17:00:23 +01:00
migration = nil
if options [ :replay ]
migration = account . migrations . last
2024-01-26 09:53:44 +01:00
fail_with_message 'The specified account has not performed any migration' if migration . nil?
2023-01-13 17:00:23 +01:00
2024-01-26 09:53:44 +01:00
fail_with_message 'The specified account is not redirecting to its last migration target. Use --force if you want to replay the migration anyway' unless options [ :force ] || migration . target_account_id == account . moved_to_account_id
2023-01-13 17:00:23 +01:00
end
if options [ :target ]
target_account = ResolveAccountService . new . call ( options [ :target ] )
2024-01-26 09:53:44 +01:00
fail_with_message " The specified target account could not be found: #{ options [ :target ] } " if target_account . nil?
2023-01-13 17:00:23 +01:00
2024-01-26 09:53:44 +01:00
fail_with_message 'The specified account is redirecting to a different target account. Use --force if you want to change the migration target' unless options [ :force ] || account . moved_to_account_id . nil? || account . moved_to_account_id == target_account . id
2023-01-13 17:00:23 +01:00
begin
migration = account . migrations . create! ( acct : target_account . acct )
rescue ActiveRecord :: RecordInvalid = > e
2024-01-26 09:53:44 +01:00
fail_with_message " Error: #{ e . message } "
2023-01-13 17:00:23 +01:00
end
end
MoveService . new . call ( migration )
say ( " OK, migrated #{ account . acct } to #{ migration . target_account . acct } " , :green )
end
2018-08-26 20:21:03 +02:00
private
2023-05-30 16:09:15 +02:00
def report_errors ( errors )
2024-01-26 09:53:44 +01:00
message = errors . map do | error |
<< ~ STRING
Failure / Error : #{error.attribute}
#{error.type}
STRING
end . join
fail_with_message message
2023-05-30 16:09:15 +02:00
end
2018-08-26 20:21:03 +02:00
def rotate_keys_for_account ( account , delay = 0 )
2024-01-26 09:53:44 +01:00
fail_with_message 'No such account' if account . nil?
2018-09-14 17:42:22 +02:00
2018-08-26 20:21:03 +02:00
old_key = account . private_key
2018-12-18 01:22:29 +01:00
new_key = OpenSSL :: PKey :: RSA . new ( 2048 )
account . update ( private_key : new_key . to_pem , public_key : new_key . public_key . to_pem )
2022-12-07 14:13:10 +01:00
ActivityPub :: UpdateDistributionWorker . perform_in ( delay , account . id , { 'sign_with' = > old_key } )
2018-08-26 20:21:03 +02:00
end
end
end