소스 검색

Change auto-following admin-selected accounts, show in recommendations

undefined
Eugen Rochko 5 일 전
부모
커밋
6169d8e18d
10개의 변경된 파일59개의 추가작업 그리고 96개의 파일을 삭제
  1. +3
    -3
      app/lib/potential_friendship_tracker.rb
  2. +31
    -1
      app/models/account_suggestions.rb
  3. +2
    -1
      app/models/follow_recommendation.rb
  4. +0
    -2
      app/models/form/admin_settings.rb
  5. +1
    -36
      app/services/bootstrap_timeline_service.rb
  6. +19
    -5
      app/validators/existing_username_validator.rb
  7. +1
    -4
      app/views/admin/settings/edit.html.haml
  8. +2
    -5
      config/locales/en.yml
  9. +0
    -1
      config/settings.yml
  10. +0
    -38
      spec/services/bootstrap_timeline_service_spec.rb

+ 3
- 3
app/lib/potential_friendship_tracker.rb 파일 보기

@@ -28,14 +28,14 @@ class PotentialFriendshipTracker
redis.zrem("interactions:#{account_id}", target_account_id)
end

def get(account, limit)
account_ids = redis.zrevrange("interactions:#{account.id}", 0, limit)
def get(account, limit, exclude_account_ids = [])
account_ids = redis.zrevrange("interactions:#{account.id}", 0, limit).map(&:to_i) - exclude_account_ids

return [] if account_ids.empty? || limit < 1

accounts = Account.searchable.where(id: account_ids).index_by(&:id)

account_ids.map { |id| accounts[id.to_i] }.compact
account_ids.map { |id| accounts[id] }.compact
end
end
end

+ 31
- 1
app/models/account_suggestions.rb 파일 보기

@@ -5,8 +5,38 @@ class AccountSuggestions
attributes :account, :source
end

class StaffPicks
def self.get(account, limit)
return [] if Setting.bootstrap_timeline_accounts.blank?

usernames_and_domains = begin
Setting.bootstrap_timeline_accounts.split(',').map do |str|
username, domain = str.strip.gsub(/\A@/, '').split('@', 2)
domain = nil if TagManager.instance.local_domain?(domain)

next if username.blank?

[username, domain]
end.compact
end

accounts = Account.searchable
.followable_by(account)
.not_excluded_by_account(account)
.not_domain_blocked_by_account(account)
.where(locked: false)
.where.not(id: account.id)
.where(usernames_and_domains.map { |(username, domain)| Arel::Nodes::Grouping.new(Account.arel_table[:username].lower.eq(username.downcase).and(Account.arel_table[:domain].lower.eq(domain&.downcase))) }.reduce(:or))
.limit(limit)
.index_by { |target_account| [target_account.username, target_account.domain] }

usernames_and_domains.map { |x| accounts[x] }.compact
end
end

def self.get(account, limit)
suggestions = PotentialFriendshipTracker.get(account, limit).map { |target_account| Suggestion.new(account: target_account, source: :past_interaction) }
suggestions = StaffPicks.get(account, limit).map { |target_account| Suggestion.new(account: target_account, source: :staff) }
suggestions.concat(PotentialFriendshipTracker.get(account, limit - suggestions.size, suggestions.map { |suggestion| suggestion.account.id }).map { |target_account| Suggestion.new(account: target_account, source: :past_interaction) }) if suggestions.size < limit
suggestions.concat(FollowRecommendation.get(account, limit - suggestions.size, suggestions.map { |suggestion| suggestion.account.id }).map { |target_account| Suggestion.new(account: target_account, source: :global) }) if suggestions.size < limit
suggestions
end


+ 2
- 1
app/models/follow_recommendation.rb 파일 보기

@@ -27,7 +27,8 @@ class FollowRecommendation < ApplicationRecord

return [] if account_ids.empty? || limit < 1

accounts = Account.followable_by(account)
accounts = Account.searchable
.followable_by(account)
.not_excluded_by_account(account)
.not_domain_blocked_by_account(account)
.where(id: account_ids)


+ 0
- 2
app/models/form/admin_settings.rb 파일 보기

@@ -16,7 +16,6 @@ class Form::AdminSettings
open_deletion
timeline_preview
show_staff_badge
enable_bootstrap_timeline_accounts
bootstrap_timeline_accounts
theme
min_invite_role
@@ -41,7 +40,6 @@ class Form::AdminSettings
open_deletion
timeline_preview
show_staff_badge
enable_bootstrap_timeline_accounts
activity_api_enabled
peers_api_enabled
show_known_fediverse_at_about_page


+ 1
- 36
app/services/bootstrap_timeline_service.rb 파일 보기

@@ -5,48 +5,13 @@ class BootstrapTimelineService < BaseService
@source_account = source_account

autofollow_inviter!
autofollow_bootstrap_timeline_accounts! if Setting.enable_bootstrap_timeline_accounts
end

private

def autofollow_inviter!
return unless @source_account&.user&.invite&.autofollow?
FollowService.new.call(@source_account, @source_account.user.invite.user.account)
end

def autofollow_bootstrap_timeline_accounts!
bootstrap_timeline_accounts.each do |target_account|
begin
FollowService.new.call(@source_account, target_account)
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
nil
end
end
end

def bootstrap_timeline_accounts
return @bootstrap_timeline_accounts if defined?(@bootstrap_timeline_accounts)

@bootstrap_timeline_accounts = bootstrap_timeline_accounts_usernames.empty? ? admin_accounts : local_unlocked_accounts(bootstrap_timeline_accounts_usernames)
end

def bootstrap_timeline_accounts_usernames
@bootstrap_timeline_accounts_usernames ||= (Setting.bootstrap_timeline_accounts || '').split(',').map { |str| str.strip.gsub(/\A@/, '') }.reject(&:blank?)
end

def admin_accounts
User.admins
.includes(:account)
.where(accounts: { locked: false })
.map(&:account)
end

def local_unlocked_accounts(usernames)
Account.local
.without_suspended
.where(username: usernames)
.where(locked: false)
.where(moved_to_account_id: nil)
FollowService.new.call(@source_account, @source_account.user.invite.user.account)
end
end

+ 19
- 5
app/validators/existing_username_validator.rb 파일 보기

@@ -4,11 +4,25 @@ class ExistingUsernameValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if value.blank?

if options[:multiple]
missing_usernames = value.split(',').map { |username| username.strip.gsub(/\A@/, '') }.filter_map { |username| username unless Account.find_local(username) }
record.errors.add(attribute, I18n.t('existing_username_validator.not_found_multiple', usernames: missing_usernames.join(', '))) if missing_usernames.any?
else
record.errors.add(attribute, I18n.t('existing_username_validator.not_found')) unless Account.find_local(value.strip.gsub(/\A@/, ''))
usernames_and_domains = begin
value.split(',').map do |str|
username, domain = str.strip.gsub(/\A@/, '').split('@')
domain = nil if TagManager.instance.local_domain?(domain)

next if username.blank?

[str, username, domain]
end.compact
end

usernames_with_no_accounts = usernames_and_domains.filter_map do |(str, username, domain)|
str unless Account.find_remote(username, domain)
end

if usernames_with_no_accounts.any? && options[:multiple]
record.errors.add(attribute, I18n.t('existing_username_validator.not_found_multiple', usernames: usernames_with_no_accounts.join(', ')))
elsif usernames_with_no_accounts.any? || usernames_and_domains.size > 1
record.errors.add(attribute, I18n.t('existing_username_validator.not_found'))
end
end
end

+ 1
- 4
app/views/admin/settings/edit.html.haml 파일 보기

@@ -50,10 +50,7 @@
%hr.spacer/

.fields-group
= f.input :enable_bootstrap_timeline_accounts, as: :boolean, wrapper: :with_label, label: t('admin.settings.enable_bootstrap_timeline_accounts.title'), hint: t('admin.settings.enable_bootstrap_timeline_accounts.desc_html')

.fields-group
= f.input :bootstrap_timeline_accounts, wrapper: :with_block_label, label: t('admin.settings.bootstrap_timeline_accounts.title'), hint: t('admin.settings.bootstrap_timeline_accounts.desc_html'), disabled: !Setting.enable_bootstrap_timeline_accounts
= f.input :bootstrap_timeline_accounts, wrapper: :with_block_label, label: t('admin.settings.bootstrap_timeline_accounts.title'), hint: t('admin.settings.bootstrap_timeline_accounts.desc_html', follow_recommendations_path: admin_follow_recommendations_path), disabled: !Setting.enable_bootstrap_timeline_accounts

%hr.spacer/



+ 2
- 5
config/locales/en.yml 파일 보기

@@ -564,8 +564,8 @@ en:
desc_html: Counts of locally posted statuses, active users, and new registrations in weekly buckets
title: Publish aggregate statistics about user activity
bootstrap_timeline_accounts:
desc_html: Separate multiple usernames by comma. Only local and unlocked accounts will work. Default when empty is all local admins.
title: Default follows for new users
desc_html: Separate multiple usernames by comma. See also <a href="%{follow_recommendations_path}">follow recommendations</a>
title: Recommend these accounts to new users
contact_information:
email: Business e-mail
username: Contact username
@@ -582,9 +582,6 @@ en:
users: To logged-in local users
domain_blocks_rationale:
title: Show rationale
enable_bootstrap_timeline_accounts:
desc_html: Make new users automatically follow configured accounts so their home feed doesn't start out empty
title: Enable default follows for new users
hero:
desc_html: Displayed on the frontpage. At least 600x100px recommended. When not set, falls back to server thumbnail
title: Hero image


+ 0
- 1
config/settings.yml 파일 보기

@@ -62,7 +62,6 @@ defaults: &defaults
- mod
- moderator
disallowed_hashtags: # space separated string or list of hashtags without the hash
enable_bootstrap_timeline_accounts: true
bootstrap_timeline_accounts: ''
activity_api_enabled: true
peers_api_enabled: true


+ 0
- 38
spec/services/bootstrap_timeline_service_spec.rb 파일 보기

@@ -1,42 +1,4 @@
require 'rails_helper'

RSpec.describe BootstrapTimelineService, type: :service do
subject { described_class.new }

describe '#call' do
let(:source_account) { Fabricate(:account) }

context 'when setting is empty' do
let!(:admin) { Fabricate(:user, admin: true) }

before do
Setting.bootstrap_timeline_accounts = nil
subject.call(source_account)
end

it 'follows admin accounts from account' do
expect(source_account.following?(admin.account)).to be true
end
end

context 'when setting is set' do
let!(:alice) { Fabricate(:account, username: 'alice') }
let!(:bob) { Fabricate(:account, username: 'bob') }
let!(:eve) { Fabricate(:account, username: 'eve', suspended: true) }

before do
Setting.bootstrap_timeline_accounts = 'alice, @bob, eve, unknown'
subject.call(source_account)
end

it 'follows found accounts from account' do
expect(source_account.following?(alice)).to be true
expect(source_account.following?(bob)).to be true
end

it 'does not follow suspended account' do
expect(source_account.following?(eve)).to be false
end
end
end
end

불러오는 중...
취소
저장