Change algorithm of follow recommendations (#28314)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
parent
b7bdcd4f39
commit
b5ac61b2c5
35 changed files with 297 additions and 292 deletions
37
app/models/account_suggestions/friends_of_friends_source.rb
Normal file
37
app/models/account_suggestions/friends_of_friends_source.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountSuggestions::FriendsOfFriendsSource < AccountSuggestions::Source
|
||||
def get(account, limit: 10)
|
||||
Account.find_by_sql([<<~SQL.squish, { id: account.id, limit: limit }]).map { |row| [row.id, key] }
|
||||
WITH first_degree AS (
|
||||
SELECT target_account_id
|
||||
FROM follows
|
||||
JOIN accounts AS target_accounts ON follows.target_account_id = target_accounts.id
|
||||
WHERE account_id = :id
|
||||
AND NOT target_accounts.hide_collections
|
||||
)
|
||||
SELECT accounts.id, COUNT(*) AS frequency
|
||||
FROM accounts
|
||||
JOIN follows ON follows.target_account_id = accounts.id
|
||||
JOIN account_stats ON account_stats.account_id = accounts.id
|
||||
LEFT OUTER JOIN follow_recommendation_mutes ON follow_recommendation_mutes.target_account_id = accounts.id AND follow_recommendation_mutes.account_id = :id
|
||||
WHERE follows.account_id IN (SELECT * FROM first_degree)
|
||||
AND follows.target_account_id NOT IN (SELECT * FROM first_degree)
|
||||
AND follows.target_account_id <> :id
|
||||
AND accounts.discoverable
|
||||
AND accounts.suspended_at IS NULL
|
||||
AND accounts.silenced_at IS NULL
|
||||
AND accounts.moved_to_account_id IS NULL
|
||||
AND follow_recommendation_mutes.target_account_id IS NULL
|
||||
GROUP BY accounts.id, account_stats.id
|
||||
ORDER BY frequency DESC, account_stats.followers_count ASC
|
||||
LIMIT :limit
|
||||
SQL
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def key
|
||||
:friends_of_friends
|
||||
end
|
||||
end
|
|
@ -1,39 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountSuggestions::GlobalSource < AccountSuggestions::Source
|
||||
include Redisable
|
||||
|
||||
def key
|
||||
:global
|
||||
end
|
||||
|
||||
def get(account, skip_account_ids: [], limit: 40)
|
||||
account_ids = account_ids_for_locale(I18n.locale.to_s.split(/[_-]/).first) - [account.id] - skip_account_ids
|
||||
|
||||
as_ordered_suggestions(
|
||||
scope(account).where(id: account_ids),
|
||||
account_ids
|
||||
).take(limit)
|
||||
end
|
||||
|
||||
def remove(_account, _target_account_id)
|
||||
nil
|
||||
def get(account, limit: 10)
|
||||
FollowRecommendation.localized(content_locale).joins(:account).merge(base_account_scope(account)).order(rank: :desc).limit(limit).pluck(:account_id, :reason)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scope(account)
|
||||
Account.searchable
|
||||
.followable_by(account)
|
||||
.not_excluded_by_account(account)
|
||||
.not_domain_blocked_by_account(account)
|
||||
end
|
||||
|
||||
def account_ids_for_locale(locale)
|
||||
redis.zrevrange("follow_recommendations:#{locale}", 0, -1).map(&:to_i)
|
||||
end
|
||||
|
||||
def to_ordered_list_key(account)
|
||||
account.id
|
||||
def content_locale
|
||||
I18n.locale.to_s.split(/[_-]/).first
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountSuggestions::PastInteractionsSource < AccountSuggestions::Source
|
||||
include Redisable
|
||||
|
||||
def key
|
||||
:past_interactions
|
||||
end
|
||||
|
||||
def get(account, skip_account_ids: [], limit: 40)
|
||||
account_ids = account_ids_for_account(account.id, limit + skip_account_ids.size) - skip_account_ids
|
||||
|
||||
as_ordered_suggestions(
|
||||
scope.where(id: account_ids),
|
||||
account_ids
|
||||
).take(limit)
|
||||
end
|
||||
|
||||
def remove(account, target_account_id)
|
||||
redis.zrem("interactions:#{account.id}", target_account_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scope
|
||||
Account.searchable
|
||||
end
|
||||
|
||||
def account_ids_for_account(account_id, limit)
|
||||
redis.zrevrange("interactions:#{account_id}", 0, limit).map(&:to_i)
|
||||
end
|
||||
|
||||
def to_ordered_list_key(account)
|
||||
account.id
|
||||
end
|
||||
end
|
|
@ -1,32 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountSuggestions::SettingSource < AccountSuggestions::Source
|
||||
def key
|
||||
:staff
|
||||
end
|
||||
|
||||
def get(account, skip_account_ids: [], limit: 40)
|
||||
return [] unless setting_enabled?
|
||||
|
||||
as_ordered_suggestions(
|
||||
scope(account).where(setting_to_where_condition).where.not(id: skip_account_ids),
|
||||
usernames_and_domains
|
||||
).take(limit)
|
||||
end
|
||||
|
||||
def remove(_account, _target_account_id)
|
||||
nil
|
||||
def get(account, limit: 10)
|
||||
if setting_enabled?
|
||||
base_account_scope(account).where(setting_to_where_condition).limit(limit).pluck(:id).zip([key].cycle)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scope(account)
|
||||
Account.searchable
|
||||
.followable_by(account)
|
||||
.not_excluded_by_account(account)
|
||||
.not_domain_blocked_by_account(account)
|
||||
.where(locked: false)
|
||||
.where.not(id: account.id)
|
||||
def key
|
||||
:featured
|
||||
end
|
||||
|
||||
def usernames_and_domains
|
||||
|
@ -61,8 +47,4 @@ class AccountSuggestions::SettingSource < AccountSuggestions::Source
|
|||
def setting
|
||||
Setting.bootstrap_timeline_accounts
|
||||
end
|
||||
|
||||
def to_ordered_list_key(account)
|
||||
[account.username.downcase, account.domain&.downcase]
|
||||
end
|
||||
end
|
||||
|
|
67
app/models/account_suggestions/similar_profiles_source.rb
Normal file
67
app/models/account_suggestions/similar_profiles_source.rb
Normal file
|
@ -0,0 +1,67 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountSuggestions::SimilarProfilesSource < AccountSuggestions::Source
|
||||
class QueryBuilder < AccountSearchService::QueryBuilder
|
||||
def must_clauses
|
||||
[
|
||||
{
|
||||
more_like_this: {
|
||||
fields: %w(text text.stemmed),
|
||||
like: @query.map { |id| { _index: 'accounts', _id: id } },
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
term: {
|
||||
properties: 'discoverable',
|
||||
},
|
||||
},
|
||||
]
|
||||
end
|
||||
|
||||
def must_not_clauses
|
||||
[
|
||||
{
|
||||
terms: {
|
||||
id: following_ids,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
term: {
|
||||
properties: 'bot',
|
||||
},
|
||||
},
|
||||
]
|
||||
end
|
||||
|
||||
def should_clauses
|
||||
{
|
||||
term: {
|
||||
properties: {
|
||||
value: 'verified',
|
||||
boost: 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def get(account, limit: 10)
|
||||
recently_followed_account_ids = account.active_relationships.recent.limit(5).pluck(:target_account_id)
|
||||
|
||||
if Chewy.enabled? && !recently_followed_account_ids.empty?
|
||||
QueryBuilder.new(recently_followed_account_ids, account).build.limit(limit).hits.pluck('_id').map(&:to_i).zip([key].cycle)
|
||||
else
|
||||
[]
|
||||
end
|
||||
rescue Faraday::ConnectionFailed
|
||||
[]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def key
|
||||
:similar_to_recently_followed
|
||||
end
|
||||
end
|
|
@ -1,34 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountSuggestions::Source
|
||||
def key
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def get(_account, **kwargs)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def remove(_account, target_account_id)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def as_ordered_suggestions(scope, ordered_list)
|
||||
return [] if ordered_list.empty?
|
||||
|
||||
map = scope.index_by { |account| to_ordered_list_key(account) }
|
||||
|
||||
ordered_list.filter_map { |ordered_list_key| map[ordered_list_key] }.map do |account|
|
||||
AccountSuggestions::Suggestion.new(
|
||||
account: account,
|
||||
source: key
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def to_ordered_list_key(_account)
|
||||
raise NotImplementedError
|
||||
def base_account_scope(account)
|
||||
Account.searchable
|
||||
.followable_by(account)
|
||||
.not_excluded_by_account(account)
|
||||
.not_domain_blocked_by_account(account)
|
||||
.where.not(id: account.id)
|
||||
.joins("LEFT OUTER JOIN follow_recommendation_mutes ON follow_recommendation_mutes.target_account_id = accounts.id AND follow_recommendation_mutes.account_id = #{account.id}").where(follow_recommendation_mutes: { target_account_id: nil })
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue