Sync Mastodon IDs/profiles to local accounts
Add a new service to import some data from Mastodon accounts: * Find users by username, store Mastodon account ID in local db when found * Import display name (don't overwrite existing) * Import avatar (don't overwrite existing)
This commit is contained in:
parent
c291765777
commit
c2bdf5247e
@ -193,7 +193,11 @@ class SettingsController < ApplicationController
|
|||||||
|
|
||||||
def store_user_avatar
|
def store_user_avatar
|
||||||
io = @user.avatar_new.tempfile
|
io = @user.avatar_new.tempfile
|
||||||
img_data = process_avatar(io)
|
img_data = UserManager::ProcessAvatar.call(io: io)
|
||||||
|
if img_data.blank?
|
||||||
|
@user.errors.add(:avatar, "failed to process file")
|
||||||
|
false
|
||||||
|
end
|
||||||
tempfile = Tempfile.create
|
tempfile = Tempfile.create
|
||||||
tempfile.binmode
|
tempfile.binmode
|
||||||
tempfile.write(img_data)
|
tempfile.write(img_data)
|
||||||
@ -212,18 +216,4 @@ class SettingsController < ApplicationController
|
|||||||
@user.save
|
@user.save
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_avatar(io)
|
|
||||||
processed = ImageProcessing::Vips
|
|
||||||
.source(io)
|
|
||||||
.resize_to_fill(400, 400)
|
|
||||||
.saver(strip: true)
|
|
||||||
.call
|
|
||||||
io.rewind
|
|
||||||
processed.read
|
|
||||||
rescue Vips::Error => e
|
|
||||||
Sentry.capture_exception(e) if Setting.sentry_enabled?
|
|
||||||
Rails.logger.error { "Image processing failed for avatar: #{e.message}" }
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
12
app/services/mastodon_manager/fetch_user.rb
Normal file
12
app/services/mastodon_manager/fetch_user.rb
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
module MastodonManager
|
||||||
|
class FetchUser < MastodonManagerService
|
||||||
|
def initialize(mastodon_id:)
|
||||||
|
@mastodon_id = mastodon_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
user = get "v1/admin/accounts/#{@mastodon_id}"
|
||||||
|
user.with_indifferent_access
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
14
app/services/mastodon_manager/find_user.rb
Normal file
14
app/services/mastodon_manager/find_user.rb
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module MastodonManager
|
||||||
|
class FindUser < MastodonManagerService
|
||||||
|
def initialize(username:)
|
||||||
|
@username = username
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
users = get "v2/admin/accounts?username=#{@username}&origin=local"
|
||||||
|
users = users.map { |u| u.with_indifferent_access }
|
||||||
|
# Results may contain partial matches
|
||||||
|
users.find { |u| u.dig(:username).downcase == @username.downcase }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
57
app/services/mastodon_manager/sync_account_profiles.rb
Normal file
57
app/services/mastodon_manager/sync_account_profiles.rb
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
module MastodonManager
|
||||||
|
class SyncAccountProfiles < MastodonManagerService
|
||||||
|
def initialize(direction: "down", overwrite: false)
|
||||||
|
@direction = direction
|
||||||
|
@overwrite = overwrite
|
||||||
|
|
||||||
|
if @direction != "down"
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
Rails.logger.debug { "Syncing account profiles (direction: #{@direction}, overwrite: #{@overwrite})"}
|
||||||
|
|
||||||
|
User.find_each do |user|
|
||||||
|
if user.mastodon_id.blank?
|
||||||
|
mastodon_user = MastodonManager::FindUser.call username: user.cn
|
||||||
|
if mastodon_user
|
||||||
|
Rails.logger.debug { "Setting mastodon_id for user #{user.cn}" }
|
||||||
|
user.update! mastodon_id: @user[:id]
|
||||||
|
else
|
||||||
|
Rails.logger.debug { "No Mastodon user found for username #{user.cn}" }
|
||||||
|
next
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
next if user.avatar.attached? && user.display_name.present?
|
||||||
|
|
||||||
|
unless mastodon_user
|
||||||
|
Rails.logger.debug { "Fetching Mastodon account with ID #{user.mastodon_id} for #{user.cn}" }
|
||||||
|
mastodon_user = MastodonManager::FetchUser.call mastodon_id: user.mastodon_id
|
||||||
|
end
|
||||||
|
|
||||||
|
if user.display_name.blank?
|
||||||
|
if mastodon_display_name = mastodon_user.dig(:account, :display_name)
|
||||||
|
Rails.logger.debug { "Setting display name for user #{user.cn} from Mastodon" }
|
||||||
|
LdapManager::UpdateDisplayName.call(
|
||||||
|
dn: user.dn, display_name: mastodon_display_name
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if !user.avatar.attached?
|
||||||
|
if avatar_url = mastodon_user.dig(:account, :avatar_static)
|
||||||
|
Rails.logger.debug { "Importing Mastodon avatar for user #{user.cn}" }
|
||||||
|
UserManager::ImportRemoteAvatar.call(
|
||||||
|
user: user, avatar_url: avatar_url
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
Sentry.capture_exception(e) if Setting.sentry_enabled?
|
||||||
|
Rails.logger.error e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
41
app/services/user_manager/import_remote_avatar.rb
Normal file
41
app/services/user_manager/import_remote_avatar.rb
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
module UserManager
|
||||||
|
class ImportRemoteAvatar < UserManagerService
|
||||||
|
def initialize(user:, avatar_url:)
|
||||||
|
@user = user
|
||||||
|
@avatar_url = avatar_url
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
if import_remote_avatar
|
||||||
|
LdapManager::UpdateAvatar.call(user: @user)
|
||||||
|
XmppSetAvatarJob.perform_later(user: @user) if Setting.ejabberd_enabled?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def import_remote_avatar
|
||||||
|
tempfile = Down.download(@avatar_url)
|
||||||
|
content_type = tempfile.content_type
|
||||||
|
unless %w[image/jpeg image/png].include?(content_type)
|
||||||
|
Rails.logger.warn { "Wrong content type of remote avatar for user #{user.cn}: '#{content_type}'" }
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
img_data = UserManager::ProcessAvatar.call(io: tempfile)
|
||||||
|
tempfile = Tempfile.create
|
||||||
|
tempfile.binmode
|
||||||
|
tempfile.write(img_data)
|
||||||
|
tempfile.rewind
|
||||||
|
|
||||||
|
hash = Digest::SHA256.hexdigest(img_data)
|
||||||
|
ext = content_type == "image/png" ? "png" : "jpg"
|
||||||
|
filename = "#{hash}.#{ext}"
|
||||||
|
key = "users/#{@user.cn}/avatars/#{filename}"
|
||||||
|
|
||||||
|
@user.avatar.attach io: tempfile, key: key, filename: filename
|
||||||
|
rescue => e
|
||||||
|
Sentry.capture_exception(e) if Setting.sentry_enabled?
|
||||||
|
Rails.logger.warn "Importing remote avatar failed: \"#{e.message}\""
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
21
app/services/user_manager/process_avatar.rb
Normal file
21
app/services/user_manager/process_avatar.rb
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
module UserManager
|
||||||
|
class ProcessAvatar < UserManagerService
|
||||||
|
def initialize(io:)
|
||||||
|
@io = io
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
processed = ImageProcessing::Vips
|
||||||
|
.source(@io)
|
||||||
|
.resize_to_fill(400, 400)
|
||||||
|
.saver(strip: true)
|
||||||
|
.call
|
||||||
|
@io.rewind
|
||||||
|
processed.read
|
||||||
|
rescue Vips::Error => e
|
||||||
|
Sentry.capture_exception(e) if Setting.sentry_enabled?
|
||||||
|
Rails.logger.warn { "Image processing failed for avatar: #{e.message}" }
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
x
Reference in New Issue
Block a user