2016-11-15 16:56:29 +01:00
# frozen_string_literal: true
2016-09-09 20:04:34 +02:00
require 'singleton'
2016-03-25 02:13:30 +01:00
class FeedManager
2016-09-09 20:04:34 +02:00
include Singleton
2017-04-04 13:58:34 +02:00
MAX_ITEMS = 400
2016-03-25 02:13:30 +01:00
2016-09-09 20:04:34 +02:00
def key ( type , id )
2016-03-25 02:13:30 +01:00
" feed: #{ type } : #{ id } "
end
2017-04-04 19:21:37 +02:00
def filter? ( timeline_type , status , receiver_id )
2016-10-02 15:28:47 +02:00
if timeline_type == :home
2017-04-04 19:21:37 +02:00
filter_from_home? ( status , receiver_id )
2016-11-07 23:20:52 +01:00
elsif timeline_type == :mentions
2017-04-04 19:21:37 +02:00
filter_from_mentions? ( status , receiver_id )
2016-11-07 23:20:52 +01:00
else
false
2016-10-02 15:28:47 +02:00
end
2016-03-25 02:13:30 +01:00
end
2016-09-10 18:36:48 +02:00
def push ( timeline_type , account , status )
2017-03-02 18:49:32 +01: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-06-03 20:50:53 +02:00
PushUpdateWorker . perform_async ( account . id , status . id ) if push_update_required? ( timeline_type , account . id )
2016-09-12 18:22:43 +02:00
end
2016-09-10 18:36:48 +02: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
2017-06-03 20:50:53 +02:00
def push_update_required? ( timeline_type , account_id )
timeline_type != :home || redis . get ( " subscribed:timeline: #{ account_id } " ) . present?
end
2016-12-22 23:03:57 +01:00
def merge_into_timeline ( from_account , into_account )
timeline_key = key ( :home , into_account . id )
2017-04-04 13:58:34 +02:00
query = from_account . statuses . limit ( FeedManager :: MAX_ITEMS / 4 )
2017-04-04 13:43:36 +02:00
2017-04-04 13:58:34 +02:00
if redis . zcard ( timeline_key ) > = FeedManager :: MAX_ITEMS / 4
2017-04-04 13:43:36 +02: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-22 23:03:57 +01:00
2017-04-04 13:01:14 +02:00
redis . pipelined do
2017-04-04 13:43:36 +02:00
query . each do | status |
2017-04-04 13:01:14 +02:00
next if status . direct_visibility? || filter? ( :home , status , into_account )
redis . zadd ( timeline_key , status . id , status . id )
end
2016-12-22 23:03:57 +01:00
end
trim ( :home , into_account . id )
end
2017-01-23 21:29:34 +01:00
def unmerge_from_timeline ( from_account , into_account )
timeline_key = key ( :home , into_account . id )
2017-04-04 13:43:36 +02:00
oldest_home_score = redis . zrange ( timeline_key , 0 , 0 , with_scores : true ) & . first & . last & . to_i || 0
2017-01-23 21:29:34 +01:00
2017-04-11 00:38:34 +02:00
from_account . statuses . select ( 'id' ) . where ( 'id > ?' , oldest_home_score ) . reorder ( nil ) . find_in_batches do | statuses |
2017-04-04 13:01:14 +02: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 21:29:34 +01:00
end
end
2017-05-06 16:31:07 +02: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 16:00:11 +02:00
private
def redis
2016-11-15 16:56:29 +01:00
Redis . current
2016-10-07 16:00:11 +02:00
end
2017-04-04 19:21:37 +02:00
def filter_from_home? ( status , receiver_id )
2017-04-04 13:01:14 +02: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 19:21:37 +02:00
return true if Mute . where ( account_id : receiver_id , target_account_id : check_for_mutes ) . any?
2017-03-02 18:49:32 +01:00
Account domain blocks (#2381)
* Add <ostatus:conversation /> tag to Atom input/output
Only uses ref attribute (not href) because href would be
the alternate link that's always included also.
Creates new conversation for every non-reply status. Carries
over conversation for every reply. Keeps remote URIs verbatim,
generates local URIs on the fly like the rest of them.
* Conversation muting - prevents notifications that reference a conversation
(including replies, favourites, reblogs) from being created. API endpoints
/api/v1/statuses/:id/mute and /api/v1/statuses/:id/unmute
Currently no way to tell when a status/conversation is muted, so the web UI
only has a "disable notifications" button, doesn't work as a toggle
* Display "Dismiss notifications" on all statuses in notifications column, not just own
* Add "muted" as a boolean attribute on statuses JSON
For now always false on contained reblogs, since it's only relevant for
statuses returned from the notifications endpoint, which are not nested
Remove "Disable notifications" from detailed status view, since it's
only relevant in the notifications column
* Up max class length
* Remove pending test for conversation mute
* Add tests, clean up
* Rename to "mute conversation" and "unmute conversation"
* Raise validation error when trying to mute/unmute status without conversation
* Adding account domain blocks that filter notifications and public timelines
* Add tests for domain blocks in notifications, public timelines
Filter reblogs of blocked domains from home
* Add API for listing and creating account domain blocks
* API for creating/deleting domain blocks, tests for Status#ancestors
and Status#descendants, filter domain blocks from them
* Filter domains in streaming API
* Update account_domain_block_spec.rb
2017-05-19 01:14:30 +02:00
check_for_blocks = status . mentions . pluck ( :account_id )
2017-04-04 13:01:14 +02:00
check_for_blocks . concat ( [ status . reblog . account_id ] ) if status . reblog?
2016-11-07 23:20:52 +01:00
2017-04-04 19:21:37 +02:00
return true if Block . where ( account_id : receiver_id , target_account_id : check_for_blocks ) . any?
2017-04-04 13:01:14 +02:00
2017-04-04 19:21:37 +02: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
2017-06-08 13:24:28 +02:00
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 13:01:14 +02:00
return should_filter
2017-04-04 19:21:37 +02:00
elsif status . reblog? # Filter out a reblog
Account domain blocks (#2381)
* Add <ostatus:conversation /> tag to Atom input/output
Only uses ref attribute (not href) because href would be
the alternate link that's always included also.
Creates new conversation for every non-reply status. Carries
over conversation for every reply. Keeps remote URIs verbatim,
generates local URIs on the fly like the rest of them.
* Conversation muting - prevents notifications that reference a conversation
(including replies, favourites, reblogs) from being created. API endpoints
/api/v1/statuses/:id/mute and /api/v1/statuses/:id/unmute
Currently no way to tell when a status/conversation is muted, so the web UI
only has a "disable notifications" button, doesn't work as a toggle
* Display "Dismiss notifications" on all statuses in notifications column, not just own
* Add "muted" as a boolean attribute on statuses JSON
For now always false on contained reblogs, since it's only relevant for
statuses returned from the notifications endpoint, which are not nested
Remove "Disable notifications" from detailed status view, since it's
only relevant in the notifications column
* Up max class length
* Remove pending test for conversation mute
* Add tests, clean up
* Rename to "mute conversation" and "unmute conversation"
* Raise validation error when trying to mute/unmute status without conversation
* Adding account domain blocks that filter notifications and public timelines
* Add tests for domain blocks in notifications, public timelines
Filter reblogs of blocked domains from home
* Add API for listing and creating account domain blocks
* API for creating/deleting domain blocks, tests for Status#ancestors
and Status#descendants, filter domain blocks from them
* Filter domains in streaming API
* Update account_domain_block_spec.rb
2017-05-19 01:14:30 +02:00
should_filter = 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
should_filter || = AccountDomainBlock . where ( account_id : receiver_id , domain : status . reblog . account . domain ) . exists? # or the author's domain is blocked
return should_filter
2016-11-07 23:20:52 +01:00
end
2017-04-04 13:01:14 +02:00
false
2016-10-07 16:00:11 +02:00
end
2017-04-04 19:21:37 +02:00
def filter_from_mentions? ( status , receiver_id )
2017-04-04 13:01:14 +02:00
check_for_blocks = [ status . account_id ]
2017-04-04 18:04:07 +02:00
check_for_blocks . concat ( status . mentions . pluck ( :account_id ) )
2017-04-04 13:01:14 +02:00
check_for_blocks . concat ( [ status . in_reply_to_account ] ) if status . reply? && ! status . in_reply_to_account_id . nil?
2017-04-04 19:21:37 +02: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 21:11:45 +01:00
2016-11-07 23:20:52 +01:00
should_filter
2016-10-07 16:00:11 +02:00
end
2016-03-25 02:13:30 +01:00
end