2016-11-15 17:56:29 +02:00
# frozen_string_literal: true
2016-09-09 21:04:34 +03:00
require 'singleton'
2016-03-25 03:13:30 +02:00
class FeedManager
2016-09-09 21:04:34 +03:00
include Singleton
2017-04-04 14:58:34 +03:00
MAX_ITEMS = 400
2016-03-25 03:13:30 +02:00
2016-09-09 21:04:34 +03:00
def key ( type , id )
2016-03-25 03:13:30 +02:00
" feed: #{ type } : #{ id } "
end
2017-04-04 20:21:37 +03:00
def filter? ( timeline_type , status , receiver_id )
2016-10-02 16:28:47 +03:00
if timeline_type == :home
2017-04-04 20:21:37 +03:00
filter_from_home? ( status , receiver_id )
2016-11-08 00:20:52 +02:00
elsif timeline_type == :mentions
2017-04-04 20:21:37 +03:00
filter_from_mentions? ( status , receiver_id )
2016-11-08 00:20:52 +02:00
else
false
2016-10-02 16:28:47 +03:00
end
2016-03-25 03:13:30 +02:00
end
2016-09-10 19:36:48 +03:00
def push ( timeline_type , account , status )
2017-03-02 19:49:32 +02:00
timeline_key = key ( timeline_type , account . id )
if status . reblog?
# If the original status is within 40 statuses from top, do not re-insert it into the feed
rank = redis . zrevrank ( timeline_key , status . reblog_of_id )
return if ! rank . nil? && rank < 40
redis . zadd ( timeline_key , status . id , status . reblog_of_id )
else
redis . zadd ( timeline_key , status . id , status . id )
trim ( timeline_type , account . id )
end
2017-04-05 20:45:18 +03:00
PushUpdateWorker . perform_async ( account . id , status . id )
2016-09-12 19:22:43 +03:00
end
2016-09-10 19:36:48 +03:00
def trim ( type , account_id )
return unless redis . zcard ( key ( type , account_id ) ) > FeedManager :: MAX_ITEMS
last = redis . zrevrange ( key ( type , account_id ) , FeedManager :: MAX_ITEMS - 1 , FeedManager :: MAX_ITEMS - 1 )
redis . zremrangebyscore ( key ( type , account_id ) , '-inf' , " ( #{ last . last } " )
end
2016-12-23 00:03:57 +02:00
def merge_into_timeline ( from_account , into_account )
timeline_key = key ( :home , into_account . id )
2017-04-04 14:58:34 +03:00
query = from_account . statuses . limit ( FeedManager :: MAX_ITEMS / 4 )
2017-04-04 14:43:36 +03:00
2017-04-04 14:58:34 +03:00
if redis . zcard ( timeline_key ) > = FeedManager :: MAX_ITEMS / 4
2017-04-04 14:43:36 +03:00
oldest_home_score = redis . zrange ( timeline_key , 0 , 0 , with_scores : true ) & . first & . last & . to_i || 0
query = query . where ( 'id > ?' , oldest_home_score )
end
2016-12-23 00:03:57 +02:00
2017-04-04 14:01:14 +03:00
redis . pipelined do
2017-04-04 14:43:36 +03:00
query . each do | status |
2017-04-04 14:01:14 +03:00
next if status . direct_visibility? || filter? ( :home , status , into_account )
redis . zadd ( timeline_key , status . id , status . id )
end
2016-12-23 00:03:57 +02:00
end
trim ( :home , into_account . id )
end
2017-01-23 22:29:34 +02:00
def unmerge_from_timeline ( from_account , into_account )
timeline_key = key ( :home , into_account . id )
2017-04-04 14:43:36 +03:00
oldest_home_score = redis . zrange ( timeline_key , 0 , 0 , with_scores : true ) & . first & . last & . to_i || 0
2017-01-23 22:29:34 +02:00
2017-04-11 01:38:34 +03:00
from_account . statuses . select ( 'id' ) . where ( 'id > ?' , oldest_home_score ) . reorder ( nil ) . find_in_batches do | statuses |
2017-04-04 14:01:14 +03:00
redis . pipelined do
statuses . each do | status |
redis . zrem ( timeline_key , status . id )
redis . zremrangebyscore ( timeline_key , status . id , status . id )
end
end
2017-01-23 22:29:34 +02:00
end
end
2017-05-06 17:31:07 +03:00
def clear_from_timeline ( account , target_account )
timeline_key = key ( :home , account . id )
timeline_status_ids = redis . zrange ( timeline_key , 0 , - 1 )
target_status_ids = Status . where ( id : timeline_status_ids , account : target_account ) . ids
redis . zrem ( timeline_key , target_status_ids ) if target_status_ids . present?
end
2016-10-07 17:00:11 +03:00
private
def redis
2016-11-15 17:56:29 +02:00
Redis . current
2016-10-07 17:00:11 +03:00
end
2017-04-04 20:21:37 +03:00
def filter_from_home? ( status , receiver_id )
2017-04-04 14:01:14 +03:00
return true if status . reply? && status . in_reply_to_id . nil?
check_for_mutes = [ status . account_id ]
check_for_mutes . concat ( [ status . reblog . account_id ] ) if status . reblog?
2017-04-04 20:21:37 +03:00
return true if Mute . where ( account_id : receiver_id , target_account_id : check_for_mutes ) . any?
2017-03-02 19:49:32 +02:00
2017-04-04 14:01:14 +03:00
check_for_blocks = status . mentions . map ( & :account_id )
check_for_blocks . concat ( [ status . reblog . account_id ] ) if status . reblog?
2016-11-08 00:20:52 +02:00
2017-04-04 20:21:37 +03:00
return true if Block . where ( account_id : receiver_id , target_account_id : check_for_blocks ) . any?
2017-04-04 14:01:14 +03:00
2017-04-04 20:21:37 +03:00
if status . reply? && ! status . in_reply_to_account_id . nil? # Filter out if it's a reply
should_filter = ! Follow . where ( account_id : receiver_id , target_account_id : status . in_reply_to_account_id ) . exists? # and I'm not following the person it's a reply to
should_filter && = ! ( receiver_id == status . in_reply_to_account_id ) # and it's not a reply to me
should_filter && = ! ( status . account_id == status . in_reply_to_account_id ) # and it's not a self-reply
2017-04-04 14:01:14 +03:00
return should_filter
2017-04-04 20:21:37 +03:00
elsif status . reblog? # Filter out a reblog
return Block . where ( account_id : status . reblog . account_id , target_account_id : receiver_id ) . exists? # or if the author of the reblogged status is blocking me
2016-11-08 00:20:52 +02:00
end
2017-04-04 14:01:14 +03:00
false
2016-10-07 17:00:11 +03:00
end
2017-04-04 20:21:37 +03:00
def filter_from_mentions? ( status , receiver_id )
2017-04-04 14:01:14 +03:00
check_for_blocks = [ status . account_id ]
2017-04-04 19:04:07 +03:00
check_for_blocks . concat ( status . mentions . pluck ( :account_id ) )
2017-04-04 14:01:14 +03:00
check_for_blocks . concat ( [ status . in_reply_to_account ] ) if status . reply? && ! status . in_reply_to_account_id . nil?
2017-04-04 20:21:37 +03:00
should_filter = receiver_id == status . account_id # Filter if I'm mentioning myself
should_filter || = Block . where ( account_id : receiver_id , target_account_id : check_for_blocks ) . any? # or it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked
should_filter || = ( status . account . silenced? && ! Follow . where ( account_id : receiver_id , target_account_id : status . account_id ) . exists? ) # of if the account is silenced and I'm not following them
2016-11-13 22:11:45 +02:00
2016-11-08 00:20:52 +02:00
should_filter
2016-10-07 17:00:11 +03:00
end
2016-03-25 03:13:30 +02:00
end