include Paginable
include AccountCounters
include DomainNormalizable
+ include AccountMerging
TRUST_LEVELS = {
untrusted: 0,
--- /dev/null
+# frozen_string_literal: true
+
+module AccountMerging
+ extend ActiveSupport::Concern
+
+ def merge_with!(other_account)
+ # Since it's the same remote resource, the remote resource likely
+ # already believes we are following/blocking, so it's safe to
+ # re-attribute the relationships too. However, during the presence
+ # of the index bug users could have *also* followed the reference
+ # account already, therefore mass update will not work and we need
+ # to check for (and skip past) uniqueness errors
+
+ owned_classes = [
+ Status, StatusPin, MediaAttachment, Poll, Report, Tombstone, Favourite,
+ Follow, FollowRequest, Block, Mute, AccountIdentityProof,
+ AccountModerationNote, AccountPin, AccountStat, ListAccount,
+ PollVote, Mention
+ ]
+
+ owned_classes.each do |klass|
+ klass.where(account_id: other_account.id).find_each do |record|
+ begin
+ record.update_attribute(:account_id, id)
+ rescue ActiveRecord::RecordNotUnique
+ next
+ end
+ end
+ end
+
+ target_classes = [Follow, FollowRequest, Block, Mute, AccountModerationNote, AccountPin]
+
+ target_classes.each do |klass|
+ klass.where(target_account_id: other_account.id).find_each do |record|
+ begin
+ record.update_attribute(:target_account_id, id)
+ rescue ActiveRecord::RecordNotUnique
+ next
+ end
+ end
+ end
+ end
+end
say('OK', :green)
end
+ 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)
+
+ if from_account.nil? || from_account.local?
+ say("No such account (#{from_acct})", :red)
+ exit(1)
+ end
+
+ username, domain = to_acct.split('@')
+ to_account = Account.find_remote(username, domain)
+
+ if to_account.nil? || to_account.local?
+ say("No such account (#{to_acct})", :red)
+ exit(1)
+ end
+
+ if from_account.public_key != to_account.public_key && !options[:force]
+ say("Accounts don't have the same public key, might not be duplicates!", :red)
+ say('Override with --force', :red)
+ exit(1)
+ end
+
+ to_account.merge_with!(from_account)
+ from_account.destroy
+
+ say('OK', :green)
+ end
+
desc 'backup USERNAME', 'Request a backup for a user'
long_desc <<-LONG_DESC
Request a new backup for an account with a given USERNAME.
option :verbose, type: :boolean, aliases: [:v]
desc 'unfollow ACCT', 'Make all local accounts unfollow account specified by ACCT'
def unfollow(acct)
- target_account = Account.find_remote(*acct.split('@'))
+ username, domain = acct.split('@')
+ target_account = Account.find_remote(username, domain)
if target_account.nil?
say('No such account', :red)
if other_account.public_key == reference_account.public_key
# The accounts definitely point to the same resource, so
# it's safe to re-attribute content and relationships
- merge_accounts!(reference_account, other_account)
+ reference_account.merge_with!(other_account)
end
other_account.destroy
end
end
- def merge_accounts!(main_account, duplicate_account)
- # Since it's the same remote resource, the remote resource likely
- # already believes we are following/blocking, so it's safe to
- # re-attribute the relationships too. However, during the presence
- # of the index bug users could have *also* followed the reference
- # account already, therefore mass update will not work and we need
- # to check for (and skip past) uniqueness errors
- owned_classes = [
- Status, StatusPin, MediaAttachment, Poll, Report, Tombstone, Favourite,
- Follow, FollowRequest, Block, Mute, AccountIdentityProof,
- AccountModerationNote, AccountPin, AccountStat, ListAccount,
- PollVote, Mention
- ]
- owned_classes.each do |klass|
- klass.where(account_id: duplicate_account.id).find_each do |record|
- begin
- record.update_attribute(:account_id, main_account.id)
- rescue ActiveRecord::RecordNotUnique
- next
- end
- end
- end
-
- target_classes = [Follow, FollowRequest, Block, Mute, AccountModerationNote, AccountPin]
- target_classes.each do |klass|
- klass.where(target_account_id: duplicate_account.id).find_each do |record|
- begin
- record.update_attribute(:target_account_id, main_account.id)
- rescue ActiveRecord::RecordNotUnique
- next
- end
- end
- end
- end
-
def merge_conversations!(main_conv, duplicate_conv)
owned_classes = [ConversationMute, AccountConversation]
owned_classes.each do |klass|