2016-11-15 07:56:29 -08:00
# frozen_string_literal: true
2017-05-01 17:14:47 -07:00
# == Schema Information
#
# Table name: statuses
#
2018-04-23 02:29:17 -07:00
# id :bigint(8) not null, primary key
2017-05-01 17:14:47 -07:00
# uri :string
# text :text default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
2018-04-23 02:29:17 -07:00
# in_reply_to_id :bigint(8)
# reblog_of_id :bigint(8)
2017-05-01 17:14:47 -07:00
# url :string
2017-07-12 18:12:25 -07:00
# sensitive :boolean default(FALSE), not null
2017-05-01 17:14:47 -07:00
# visibility :integer default("public"), not null
# spoiler_text :text default(""), not null
2017-07-12 18:12:25 -07:00
# reply :boolean default(FALSE), not null
2017-06-09 09:09:37 -07:00
# language :string
2018-04-23 02:29:17 -07:00
# conversation_id :bigint(8)
2017-09-06 11:57:52 -07:00
# local :boolean
2018-04-23 02:29:17 -07:00
# account_id :bigint(8) not null
# application_id :bigint(8)
# in_reply_to_account_id :bigint(8)
2017-12-10 14:41:25 -08:00
# local_only :boolean
2019-03-03 13:18:23 -08:00
# poll_id :bigint(8)
2019-05-12 11:14:35 -07:00
# content_type :string
2017-05-01 17:14:47 -07:00
#
2016-11-15 07:56:29 -08:00
2016-08-17 08:56:23 -07:00
class Status < ApplicationRecord
2018-10-07 14:44:58 -07:00
before_destroy :unlink_from_conversations
2016-03-24 05:21:53 -07:00
include Paginable
2016-03-24 18:13:30 -07:00
include Streamable
2016-11-30 06:57:56 -08:00
include Cacheable
2017-06-05 07:07:44 -07:00
include StatusThreadingConcern
2016-03-24 05:21:53 -07:00
2019-02-08 15:09:47 -08:00
LOCAL_DOMAINS = ENV . fetch ( 'LOCAL_DOMAINS' , '' ) . chomp . split ( / \ .? \ s+ / ) . freeze
LOCAL_URIS = LOCAL_DOMAINS . map { | domain | " https:// #{ domain } /% " } . freeze
2019-02-11 22:56:36 -08:00
FORCE_SENSITIVE = ENV . fetch ( 'FORCE_SENSITIVE' , '' ) . chomp . split ( / \ .? \ s+ / ) . freeze
FORCE_UNLISTED = ENV . fetch ( 'FORCE_UNLISTED' , '' ) . chomp . split ( / \ .? \ s+ / ) . freeze
2019-02-08 15:09:47 -08:00
2018-05-03 14:02:46 -07:00
# If `override_timestamps` is set at creation time, Snowflake ID creation
# will be based on current time instead of `created_at`
attr_accessor :override_timestamps
2018-02-09 14:04:47 -08:00
update_index ( 'statuses#status' , :proper ) if Chewy . enabled?
2018-10-17 08:13:04 -07:00
enum visibility : [ :public , :unlisted , :private , :direct , :limited ] , _suffix : :visibility
2016-11-30 12:32:11 -08:00
2018-01-19 11:56:47 -08:00
belongs_to :application , class_name : 'Doorkeeper::Application' , optional : true
2017-01-14 13:58:50 -08:00
2018-05-29 17:50:23 -07:00
belongs_to :account , inverse_of : :statuses
2018-01-19 11:56:47 -08:00
belongs_to :in_reply_to_account , foreign_key : 'in_reply_to_account_id' , class_name : 'Account' , optional : true
belongs_to :conversation , optional : true
2019-03-27 20:44:59 -07:00
belongs_to :preloadable_poll , class_name : 'Poll' , foreign_key : 'poll_id' , optional : true
2016-02-22 07:00:20 -08:00
2018-01-19 11:56:47 -08:00
belongs_to :thread , foreign_key : 'in_reply_to_id' , class_name : 'Status' , inverse_of : :replies , optional : true
2018-05-29 17:50:23 -07:00
belongs_to :reblog , foreign_key : 'reblog_of_id' , class_name : 'Status' , inverse_of : :reblogs , optional : true
2016-02-23 10:17:37 -08:00
2016-02-24 03:57:29 -08:00
has_many :favourites , inverse_of : :status , dependent : :destroy
2018-04-10 13:13:15 -07:00
has_many :bookmarks , inverse_of : :status , dependent : :destroy
2016-03-16 02:46:15 -07:00
has_many :reblogs , foreign_key : 'reblog_of_id' , class_name : 'Status' , inverse_of : :reblog , dependent : :destroy
2016-03-06 03:34:39 -08:00
has_many :replies , foreign_key : 'in_reply_to_id' , class_name : 'Status' , inverse_of : :thread
2018-10-17 08:13:04 -07:00
has_many :mentions , dependent : :destroy , inverse_of : :status
has_many :active_mentions , - > { active } , class_name : 'Mention' , inverse_of : :status
2018-06-04 15:17:38 -07:00
has_many :media_attachments , dependent : :nullify
2017-09-01 07:20:16 -07:00
2016-11-05 07:20:05 -07:00
has_and_belongs_to_many :tags
2017-09-01 07:20:16 -07:00
has_and_belongs_to_many :preview_cards
2016-02-23 10:17:37 -08:00
2016-11-21 05:59:13 -08:00
has_one :notification , as : :activity , dependent : :destroy
2017-08-29 07:11:05 -07:00
has_one :stream_entry , as : :activity , inverse_of : :status
2018-08-14 10:19:32 -07:00
has_one :status_stat , inverse_of : :status
2019-03-27 20:44:59 -07:00
has_one :poll , inverse_of : :status , dependent : :destroy
2016-11-21 05:59:13 -08:00
2017-09-17 06:21:57 -07:00
validates :uri , uniqueness : true , presence : true , unless : :local?
2018-03-06 23:28:52 -08:00
validates :text , presence : true , unless : - > { with_media? || reblog? }
2017-01-12 20:54:26 -08:00
validates_with StatusLengthValidator
2018-04-23 14:52:58 -07:00
validates_with DisallowedHashtagsValidator
2017-05-18 18:11:23 -07:00
validates :reblog , uniqueness : { scope : :account } , if : :reblog?
2019-03-14 20:36:41 -07:00
validates :visibility , exclusion : { in : %w( direct limited ) } , if : :reblog?
2019-05-12 12:33:37 -07:00
validates :content_type , inclusion : { in : %w( text/plain text/markdown text/html ) } , allow_nil : true
2019-03-15 05:36:38 -07:00
2019-03-27 20:44:59 -07:00
accepts_nested_attributes_for :poll
2016-02-23 10:17:37 -08:00
2017-05-22 17:53:01 -07:00
default_scope { recent }
2016-10-09 06:15:21 -07:00
2017-05-22 17:53:01 -07:00
scope :recent , - > { reorder ( id : :desc ) }
2017-09-07 11:18:34 -07:00
scope :remote , - > { where ( local : false ) . or ( where . not ( uri : nil ) ) }
scope :local , - > { where ( local : true ) . or ( where ( uri : nil ) ) }
2019-02-08 15:09:47 -08:00
scope :network , - > { where ( local : true ) . or ( where ( uri : nil ) ) . or ( where ( 'statuses.uri LIKE ANY (array[?])' , LOCAL_URIS ) ) }
2016-11-30 06:57:56 -08:00
2017-03-05 08:27:17 -08:00
scope :without_replies , - > { where ( 'statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id' ) }
scope :without_reblogs , - > { where ( 'statuses.reblog_of_id IS NULL' ) }
2019-02-08 15:09:47 -08:00
scope :reblogs , - > { where ( 'statuses.reblog_of_id IS NOT NULL' ) } # all reblogs
2017-04-28 06:10:41 -07:00
scope :with_public_visibility , - > { where ( visibility : :public ) }
2019-02-10 15:38:29 -08:00
scope :public_browsable , - > { where ( visibility : [ :public , :unlisted ] ) }
scope :visible_to , - > ( account ) { where ( visibility : [ :public , :unlisted ] ) . or ( where ( account : [ account ] + account . following ) . where ( visibility : :private ) ) }
2017-04-28 06:10:41 -07:00
scope :tagged_with , - > ( tag ) { joins ( :statuses_tags ) . where ( statuses_tags : { tag_id : tag } ) }
2019-05-14 10:05:02 -07:00
scope :excluding_silenced_accounts , - > { left_outer_joins ( :account ) . where ( accounts : { silenced_at : nil } ) }
scope :including_silenced_accounts , - > { left_outer_joins ( :account ) . where . not ( accounts : { silenced_at : nil } ) }
2017-05-19 12:05:32 -07:00
scope :not_excluded_by_account , - > ( account ) { where . not ( account_id : account . excluded_from_timeline_account_ids ) }
2019-01-06 14:06:26 -08:00
scope :reply_not_excluded_by_account , - > ( account ) { where ( 'statuses.in_reply_to_account_id IS NULL OR statuses.in_reply_to_account_id NOT IN (?)' , account . excluded_from_timeline_account_ids ) }
scope :mention_not_excluded_by_account , - > ( account ) { left_outer_joins ( :mentions ) . where ( 'mentions.account_id IS NULL OR mentions.account_id NOT IN (?)' , account . excluded_from_timeline_account_ids ) }
2017-05-20 04:38:13 -07:00
scope :not_domain_blocked_by_account , - > ( account ) { account . excluded_from_timeline_domains . blank? ? left_outer_joins ( :account ) : left_outer_joins ( :account ) . where ( 'accounts.domain IS NULL OR accounts.domain NOT IN (?)' , account . excluded_from_timeline_domains ) }
2018-11-05 09:53:25 -08:00
scope :tagged_with_all , - > ( tags ) {
Array ( tags ) . map ( & :id ) . map ( & :to_i ) . reduce ( self ) do | result , id |
result . joins ( " INNER JOIN statuses_tags t #{ id } ON t #{ id } .status_id = statuses.id AND t #{ id } .tag_id = #{ id } " )
end
}
scope :tagged_with_none , - > ( tags ) {
Array ( tags ) . map ( & :id ) . map ( & :to_i ) . reduce ( self ) do | result , id |
result . joins ( " LEFT OUTER JOIN statuses_tags t #{ id } ON t #{ id } .status_id = statuses.id AND t #{ id } .tag_id = #{ id } " )
. where ( " t #{ id } .tag_id IS NULL " )
end
}
2017-03-05 08:27:17 -08:00
2017-12-10 17:41:33 -08:00
scope :not_local_only , - > { where ( local_only : [ false , nil ] ) }
2017-12-10 15:04:28 -08:00
2018-11-18 15:43:52 -08:00
cache_associated :application ,
2018-08-14 10:19:32 -07:00
:media_attachments ,
:conversation ,
:status_stat ,
:tags ,
2018-10-27 22:35:03 -07:00
:preview_cards ,
2018-08-14 10:19:32 -07:00
:stream_entry ,
2019-03-27 20:44:59 -07:00
:preloadable_poll ,
2018-11-18 15:43:52 -08:00
account : :account_stat ,
active_mentions : { account : :account_stat } ,
2018-08-14 10:19:32 -07:00
reblog : [
:application ,
:stream_entry ,
:tags ,
2018-10-27 22:35:03 -07:00
:preview_cards ,
2018-08-14 10:19:32 -07:00
:media_attachments ,
:conversation ,
:status_stat ,
2019-03-27 20:44:59 -07:00
:preloadable_poll ,
2018-11-18 15:43:52 -08:00
account : :account_stat ,
active_mentions : { account : :account_stat } ,
2018-08-14 10:19:32 -07:00
] ,
2018-11-18 15:43:52 -08:00
thread : { account : :account_stat }
2016-03-11 07:47:36 -08:00
2017-05-31 11:38:17 -07:00
delegate :domain , to : :account , prefix : true
2018-02-17 05:28:48 -08:00
REAL_TIME_WINDOW = 6 . hours
2018-02-09 14:04:47 -08:00
def searchable_by ( preloaded = nil )
ids = [ account_id ]
if preloaded . nil?
ids += mentions . pluck ( :account_id )
ids += favourites . pluck ( :account_id )
2018-12-08 11:05:37 -08:00
ids += bookmarks . pluck ( :account_id )
2018-02-09 14:04:47 -08:00
ids += reblogs . pluck ( :account_id )
else
ids += preloaded . mentions [ id ] || [ ]
ids += preloaded . favourites [ id ] || [ ]
2018-12-08 11:05:37 -08:00
ids += preloaded . bookmarks [ id ] || [ ]
2018-02-09 14:04:47 -08:00
ids += preloaded . reblogs [ id ] || [ ]
end
ids . uniq
end
2017-02-09 11:25:39 -08:00
def reply?
2017-05-15 12:20:55 -07:00
! in_reply_to_id . nil? || attributes [ 'reply' ]
2017-02-09 11:25:39 -08:00
end
2016-02-23 10:17:37 -08:00
def local?
2017-09-06 10:01:28 -07:00
attributes [ 'local' ] || uri . nil?
2016-02-23 10:17:37 -08:00
end
2019-02-08 15:09:47 -08:00
def network?
attributes [ 'local' ] || uri . nil? || account . domain . in? ( LOCAL_DOMAINS )
end
def relayed?
account . username == :relay && account . actor_type . in? ( Set [ :Application , :Service ] )
end
2016-02-23 10:17:37 -08:00
def reblog?
2016-09-29 12:28:21 -07:00
! reblog_of_id . nil?
2016-02-23 10:17:37 -08:00
end
2018-02-17 05:28:48 -08:00
def within_realtime_window?
created_at > = REAL_TIME_WINDOW . ago
end
2016-02-22 09:10:30 -08:00
def verb
2017-08-29 07:11:05 -07:00
if destroyed?
:delete
else
reblog? ? :share : :post
end
2016-02-22 09:10:30 -08:00
end
def object_type
2016-02-23 10:17:37 -08:00
reply? ? :comment : :note
2016-02-22 09:10:30 -08:00
end
2017-04-07 11:18:30 -07:00
def proper
reblog? ? reblog : self
end
2016-02-22 09:10:30 -08:00
def content
2017-04-07 11:18:30 -07:00
proper . text
2016-02-23 10:17:37 -08:00
end
def target
2016-09-29 12:28:21 -07:00
reblog
2016-02-22 09:10:30 -08:00
end
2018-10-27 22:35:03 -07:00
def preview_card
preview_cards . first
end
2016-02-22 09:10:30 -08:00
def title
2017-08-29 07:11:05 -07:00
if destroyed?
" #{ account . acct } deleted status "
else
reblog? ? " #{ account . acct } shared a status by #{ reblog . account . acct } " : " New status by #{ account . acct } "
end
2016-02-22 09:10:30 -08:00
end
2016-12-21 11:00:18 -08:00
def hidden?
2018-10-17 08:13:04 -07:00
private_visibility? || direct_visibility? || limited_visibility?
end
def distributable?
public_visibility? || unlisted_visibility?
2016-03-12 07:09:46 -08:00
end
2018-03-06 23:28:52 -08:00
def with_media?
media_attachments . any?
end
2017-04-16 07:38:02 -07:00
def non_sensitive_with_media?
2018-03-06 23:28:52 -08:00
! sensitive? && with_media?
2017-04-16 07:38:02 -07:00
end
2017-09-18 17:42:40 -07:00
def emojis
2019-03-20 09:29:12 -07:00
return @emojis if defined? ( @emojis )
2019-03-27 20:44:59 -07:00
fields = [ spoiler_text , text ]
fields += preloadable_poll . options unless preloadable_poll . nil?
2019-03-20 09:29:12 -07:00
@emojis = CustomEmoji . from_text ( fields . join ( ' ' ) , account . domain )
2017-09-18 17:42:40 -07:00
end
2018-05-29 17:50:23 -07:00
def mark_for_mass_destruction!
@marked_for_mass_destruction = true
end
def marked_for_mass_destruction?
@marked_for_mass_destruction
end
2018-08-14 10:19:32 -07:00
def replies_count
status_stat & . replies_count || 0
end
def reblogs_count
status_stat & . reblogs_count || 0
end
def favourites_count
status_stat & . favourites_count || 0
end
def increment_count! ( key )
update_status_stat! ( key = > public_send ( key ) + 1 )
end
def decrement_count! ( key )
update_status_stat! ( key = > [ public_send ( key ) - 1 , 0 ] . max )
end
2018-12-05 13:51:12 -08:00
after_create_commit :increment_counter_caches
after_destroy_commit :decrement_counter_caches
2018-05-29 17:50:23 -07:00
2017-10-12 17:52:09 -07:00
after_create_commit :store_uri , if : :local?
2017-12-29 10:52:04 -08:00
after_create_commit :update_statistics , if : :local?
2017-09-06 10:01:28 -07:00
2017-10-08 08:34:34 -07:00
around_create Mastodon :: Snowflake :: Callbacks
2017-12-10 19:39:27 -08:00
before_create :set_locality
2017-07-14 10:47:53 -07:00
before_validation :prepare_contents , if : :local?
2017-05-15 12:20:55 -07:00
before_validation :set_reblog
before_validation :set_visibility
before_validation :set_conversation
2017-09-06 10:01:28 -07:00
before_validation :set_local
2017-05-12 10:09:21 -07:00
2019-03-03 15:39:06 -08:00
after_create :set_poll_id
2019-04-02 11:42:24 -07:00
after_create :process_bangtags , if : :local?
2019-03-03 13:18:23 -08:00
2019-02-16 14:13:00 -08:00
after_find :limit_domain_visibility
2019-02-11 22:56:36 -08:00
2016-11-05 07:20:05 -07:00
class << self
2019-04-13 21:17:51 -07:00
def search_for ( term , limit = 33 , account = nil )
pattern = sanitize_sql_like ( term )
pattern = " #{ pattern } "
2019-04-13 21:32:41 -07:00
scope = Status . where ( " tsv @@ plainto_tsquery('english', ?) " , pattern )
query = scope . where ( visibility : :public )
if account . present?
query = query
. or ( scope . where ( account : account ) )
. or ( scope . where ( account : account . following , visibility : [ :unlisted , :private ] ) )
. or ( scope . where ( id : account . mentions . select ( :status_id ) ) )
end
query = query . where ( reblog_of_id : nil ) . limit ( limit )
apply_timeline_filters ( query , account , true )
2019-04-13 21:17:51 -07:00
end
2018-10-17 13:04:40 -07:00
def selectable_visibilities
visibilities . keys - %w( direct limited )
end
2018-06-17 04:54:02 -07:00
def in_chosen_languages ( account )
where ( language : nil ) . or where ( language : account . chosen_languages )
2017-05-01 08:42:13 -07:00
end
2016-11-05 07:20:05 -07:00
def as_home_timeline ( account )
2017-06-21 17:38:50 -07:00
where ( account : [ account ] + account . following ) . where ( visibility : [ :public , :unlisted , :private ] )
2016-11-05 07:20:05 -07:00
end
2018-05-28 02:04:06 -07:00
def as_direct_timeline ( account , limit = 20 , max_id = nil , since_id = nil , cache_ids = false )
# direct timeline is mix of direct message from_me and to_me.
2018-09-13 15:53:09 -07:00
# 2 queries are executed with pagination.
2018-05-28 02:04:06 -07:00
# constant expression using arel_table is required for partial index
# _from_me part does not require any timeline filters
query_from_me = where ( account_id : account . id )
. where ( Status . arel_table [ :visibility ] . eq ( 3 ) )
. limit ( limit )
. order ( 'statuses.id DESC' )
# _to_me part requires mute and block filter.
# FIXME: may we check mutes.hide_notifications?
query_to_me = Status
. joins ( :mentions )
. merge ( Mention . where ( account_id : account . id ) )
. where ( Status . arel_table [ :visibility ] . eq ( 3 ) )
. limit ( limit )
. order ( 'mentions.status_id DESC' )
. not_excluded_by_account ( account )
if max_id . present?
query_from_me = query_from_me . where ( 'statuses.id < ?' , max_id )
query_to_me = query_to_me . where ( 'mentions.status_id < ?' , max_id )
end
if since_id . present?
query_from_me = query_from_me . where ( 'statuses.id > ?' , since_id )
query_to_me = query_to_me . where ( 'mentions.status_id > ?' , since_id )
end
if cache_ids
# returns array of cache_ids object that have id and updated_at
( query_from_me . cache_ids . to_a + query_to_me . cache_ids . to_a ) . uniq ( & :id ) . sort_by ( & :id ) . reverse . take ( limit )
else
# returns ActiveRecord.Relation
items = ( query_from_me . select ( :id ) . to_a + query_to_me . select ( :id ) . to_a ) . uniq ( & :id ) . sort_by ( & :id ) . reverse . take ( limit )
Status . where ( id : items . map ( & :id ) )
end
2017-10-15 21:02:39 -07:00
end
2017-02-06 14:16:20 -08:00
def as_public_timeline ( account = nil , local_only = false )
2019-03-24 18:19:22 -07:00
if account . present? && account & . user & . setting_rawr_federated
query = timeline_scope ( local_only ) . without_replies
else
# instead of our ftl being a noisy irrelevant firehose
2019-04-06 09:51:54 -07:00
# only show public stuff boosted/faved by the community
2019-03-24 18:19:22 -07:00
query = Status . network
if local_only then
# we don't want to change the ltl
query = query
. with_public_visibility
. without_replies
. without_reblogs
else # but on the ftl
query = query . without_replies unless Setting . show_replies_in_public_timelines
2019-04-06 09:51:54 -07:00
# grab the stuff we faved
fav_query = Favourite . select ( 'status_id' )
. where ( account_id : Account . local )
. reorder ( :status_id )
. distinct
2019-04-13 23:06:10 -07:00
# We need to find a new way to do this because it's much too slow.
2019-03-24 18:19:22 -07:00
# grab the stuff we boosted
2019-04-13 23:06:10 -07:00
#boost_query = query.reblogs.select(:reblog_of_id)
# .reorder(nil)
# .distinct
2019-03-24 18:19:22 -07:00
# map those ids to actual statuses
2019-04-13 23:06:10 -07:00
query = Status . where ( id : fav_query )
2019-04-06 09:58:24 -07:00
. without_replies
2019-04-06 09:51:54 -07:00
. with_public_visibility
2019-03-24 18:19:22 -07:00
end
2019-02-08 15:09:47 -08:00
end
2016-12-05 13:59:30 -08:00
2017-05-19 12:05:32 -07:00
apply_timeline_filters ( query , account , local_only )
2016-11-05 07:20:05 -07:00
end
2017-02-06 14:16:20 -08:00
def as_tag_timeline ( tag , account = nil , local_only = false )
2019-02-10 15:38:29 -08:00
query = ( account . nil? ) ? browsable_timeline_scope ( local_only ) : user_timeline_scope ( account , local_only )
query = query . tagged_with ( tag )
2017-02-06 14:16:20 -08:00
2017-05-19 12:05:32 -07:00
apply_timeline_filters ( query , account , local_only )
2016-11-05 07:20:05 -07:00
end
2017-04-22 20:21:10 -07:00
def as_outbox_timeline ( account )
where ( account : account , visibility : :public )
end
2016-11-05 07:20:05 -07:00
def favourites_map ( status_ids , account_id )
2018-11-16 06:02:18 -08:00
Favourite . select ( 'status_id' ) . where ( status_id : status_ids ) . where ( account_id : account_id ) . each_with_object ( { } ) { | f , h | h [ f . status_id ] = true }
2016-11-05 07:20:05 -07:00
end
2018-04-10 13:13:15 -07:00
def bookmarks_map ( status_ids , account_id )
Bookmark . select ( 'status_id' ) . where ( status_id : status_ids ) . where ( account_id : account_id ) . map { | f | [ f . status_id , true ] } . to_h
end
2016-11-05 07:20:05 -07:00
def reblogs_map ( status_ids , account_id )
2018-11-16 06:02:18 -08:00
select ( 'reblog_of_id' ) . where ( reblog_of_id : status_ids ) . where ( account_id : account_id ) . reorder ( nil ) . each_with_object ( { } ) { | s , h | h [ s . reblog_of_id ] = true }
2016-11-05 07:20:05 -07:00
end
2016-11-09 15:03:33 -08:00
Feature conversations muting (#3017)
* 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
2017-05-14 18:04:13 -07:00
def mutes_map ( conversation_ids , account_id )
2018-11-16 06:02:18 -08:00
ConversationMute . select ( 'conversation_id' ) . where ( conversation_id : conversation_ids ) . where ( account_id : account_id ) . each_with_object ( { } ) { | m , h | h [ m . conversation_id ] = true }
Feature conversations muting (#3017)
* 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
2017-05-14 18:04:13 -07:00
end
2017-08-24 16:41:18 -07:00
def pins_map ( status_ids , account_id )
2018-11-16 06:02:18 -08:00
StatusPin . select ( 'status_id' ) . where ( status_id : status_ids ) . where ( account_id : account_id ) . each_with_object ( { } ) { | p , h | h [ p . status_id ] = true }
2017-08-24 16:41:18 -07:00
end
2016-12-03 09:21:26 -08:00
def reload_stale_associations! ( cached_items )
account_ids = [ ]
cached_items . each do | item |
account_ids << item . account_id
account_ids << item . reblog . account_id if item . reblog?
end
2017-10-13 07:44:29 -07:00
account_ids . uniq!
return if account_ids . empty?
2018-11-18 15:43:52 -08:00
accounts = Account . where ( id : account_ids ) . includes ( :account_stat ) . each_with_object ( { } ) { | a , h | h [ a . id ] = a }
2016-12-03 09:21:26 -08:00
cached_items . each do | item |
item . account = accounts [ item . account_id ]
item . reblog . account = accounts [ item . reblog . account_id ] if item . reblog?
end
end
2016-12-26 10:13:56 -08:00
def permitted_for ( target_account , account )
2017-05-15 17:54:17 -07:00
visibility = [ :public , :unlisted ]
2017-03-15 14:52:57 -07:00
2017-05-15 17:54:17 -07:00
if account . nil?
2017-12-11 13:28:04 -08:00
where ( visibility : visibility ) . not_local_only
2017-05-15 17:54:17 -07:00
elsif target_account . blocking? ( account ) # get rid of blocked peeps
2017-04-04 23:02:58 -07:00
none
2017-03-15 14:52:57 -07:00
elsif account . id == target_account . id # author can see own stuff
2017-04-04 23:02:58 -07:00
all
2017-05-15 17:54:17 -07:00
else
# followers can see followers-only stuff, but also things they are mentioned in.
# non-followers can see everything that isn't private/direct, but can see stuff they are mentioned in.
2018-06-06 12:13:30 -07:00
scope = left_outer_joins ( :reblog )
2019-02-20 21:59:14 -08:00
q uery = scope . where ( visibility : visibility ) . or ( scope . where ( id : account . mentions . select ( :status_id ) ) )
if account . following? ( target_account ) then
query = query . or ( scope . where ( visibility : :private ) . without_replies )
end
query . merge ( scope . where ( reblog_of_id : nil ) . or ( scope . where . not ( reblogs_statuses : { account_id : account . excluded_from_timeline_account_ids } ) ) )
2016-12-26 10:13:56 -08:00
end
end
2016-11-09 15:03:33 -08:00
private
2017-04-28 06:10:41 -07:00
def timeline_scope ( local_only = false )
2017-09-16 20:29:43 -07:00
starting_scope = local_only ? Status . local : Status
2019-04-30 12:51:24 -07:00
starting_scope = starting_scope . with_public_visibility
if Setting . show_reblogs_in_public_timelines
starting_scope
else
starting_scope . without_reblogs
end
2017-04-28 06:10:41 -07:00
end
2019-02-10 15:38:29 -08:00
def browsable_timeline_scope ( local_only = false )
starting_scope = local_only ? Status . local : Status
starting_scope
. public_browsable
. without_reblogs
end
def user_timeline_scope ( account , local_only = false )
starting_scope = local_only ? Status . local : Status
starting_scope
. visible_to ( account )
. without_reblogs
end
2017-05-19 12:05:32 -07:00
def apply_timeline_filters ( query , account , local_only )
2017-04-28 06:10:41 -07:00
if account . nil?
filter_timeline_default ( query )
else
2017-05-19 12:05:32 -07:00
filter_timeline_for_account ( query , account , local_only )
2017-04-28 06:10:41 -07:00
end
end
2017-05-19 12:05:32 -07:00
def filter_timeline_for_account ( query , account , local_only )
2017-04-28 06:10:41 -07:00
query = query . not_excluded_by_account ( account )
2017-05-19 12:05:32 -07:00
query = query . not_domain_blocked_by_account ( account ) unless local_only
2018-06-17 04:54:02 -07:00
query = query . in_chosen_languages ( account ) if account . chosen_languages . present?
2019-01-06 14:06:26 -08:00
query = query . reply_not_excluded_by_account ( account )
query = query . mention_not_excluded_by_account ( account )
2017-04-28 06:10:41 -07:00
query . merge ( account_silencing_filter ( account ) )
2016-12-05 13:59:30 -08:00
end
def filter_timeline_default ( query )
2017-12-10 15:23:01 -08:00
query . not_local_only . excluding_silenced_accounts
2017-04-28 06:10:41 -07:00
end
def account_silencing_filter ( account )
if account . silenced?
2018-08-22 04:20:50 -07:00
including_myself = left_outer_joins ( :account ) . where ( account_id : account . id ) . references ( :accounts )
excluding_silenced_accounts . or ( including_myself )
2017-04-28 06:10:41 -07:00
else
excluding_silenced_accounts
end
2016-11-09 15:03:33 -08:00
end
2016-09-08 12:23:29 -07:00
end
2016-09-22 12:39:53 -07:00
2017-12-10 15:04:28 -08:00
def marked_local_only?
2017-10-09 06:56:17 -07:00
# match both with and without U+FE0F (the emoji variation selector)
2019-03-09 18:43:18 -08:00
/ (?: # !| \ u{1f441} \ ufe0f?) \ u200b? \ z / . match? ( content )
2017-10-09 06:56:17 -07:00
end
2019-02-11 19:52:11 -08:00
def marked_no_replies?
/ (:ms_dont_at_me:|(don't|do not|no) ((at|@|mention) me)|reply|replies) / . match? ( spoiler_text )
end
2017-05-12 10:09:21 -07:00
private
2018-08-14 10:19:32 -07:00
def update_status_stat! ( attrs )
2018-08-17 18:03:23 -07:00
return if marked_for_destruction? || destroyed?
2018-08-14 10:19:32 -07:00
record = status_stat || build_status_stat
record . update ( attrs )
end
2017-09-06 10:01:28 -07:00
def store_uri
2018-12-05 13:51:12 -08:00
update_column ( :uri , ActivityPub :: TagManager . instance . uri_for ( self ) ) if uri . nil?
2017-09-06 10:01:28 -07:00
end
2017-05-12 10:09:21 -07:00
def prepare_contents
2017-04-03 16:33:34 -07:00
text & . strip!
2017-01-24 15:49:08 -08:00
spoiler_text & . strip!
2017-05-12 10:09:21 -07:00
end
2016-12-31 05:35:08 -08:00
2017-05-12 10:09:21 -07:00
def set_reblog
self . reblog = reblog . reblog if reblog? && reblog . reblog?
2016-12-21 11:00:18 -08:00
end
2019-03-03 13:18:23 -08:00
def set_poll_id
2019-03-27 20:44:59 -07:00
update_column ( :poll_id , poll . id ) unless poll . nil?
2019-03-03 13:18:23 -08:00
end
2017-05-12 10:09:21 -07:00
def set_visibility
2019-03-17 06:54:09 -07:00
self . visibility = reblog . visibility if reblog? && visibility . nil?
2017-05-12 10:09:21 -07:00
self . visibility = ( account . locked? ? :private : :public ) if visibility . nil?
2017-07-12 18:12:25 -07:00
self . sensitive = false if sensitive . nil?
2017-05-12 10:09:21 -07:00
end
2019-02-16 14:13:00 -08:00
def limit_domain_visibility
2019-02-11 22:56:36 -08:00
return unless has_attribute? ( :uri ) && ! uri . nil?
domain = Addressable :: URI . parse ( uri ) . host
self . sensitive = true if domain . in? ( FORCE_SENSITIVE )
2019-02-16 14:13:00 -08:00
self . visibility = :unlisted if public_visibility? && domain . in? ( FORCE_UNLISTED )
2019-02-11 22:56:36 -08:00
end
2017-12-10 19:39:27 -08:00
def set_locality
2017-12-14 00:27:42 -08:00
if account . domain . nil? && ! attribute_changed? ( :local_only )
2019-03-13 00:54:07 -07:00
self . local_only = marked_local_only?
self . local_only || = account . user . setting_always_local
self . local_only || = reply? && Status . where ( id : in_reply_to_id , local_only : true ) . exists?
2017-12-10 19:39:27 -08:00
end
end
2019-04-02 11:42:24 -07:00
def process_bangtags
return if text & . nil?
return unless '#!' . in? ( text )
2019-04-08 15:23:20 -07:00
text . gsub! ( '#!!' , " # \ u200c! " )
prefix_ns = {
'permalink' = > [ 'link' ] ,
'cloudroot' = > [ 'link' ] ,
'blogroot' = > [ 'link' ] ,
}
aliases = {
[ 'media' , 'end' ] = > [ 'var' , 'end' ] ,
[ 'media' , 'stop' ] = > [ 'var' , 'end' ] ,
[ 'media' , 'endall' ] = > [ 'var' , 'endall' ] ,
[ 'media' , 'stopall' ] = > [ 'var' , 'endall' ] ,
}
# sections of the final status text
2019-04-02 11:42:24 -07:00
chunks = [ ]
2019-04-08 15:23:20 -07:00
# list of transformation commands
tf_cmds = [ ]
# list of post-processing commands
post_cmds = [ ]
# hash of bangtag variables
vars = { }
# keep track of what variables we're appending the value of between chunks
vore_stack = [ ]
# keep track of what type of nested components are active so we can !end them in order
component_stack = [ ]
text . split ( / ( # !(?:.*:! # |{.*?}|[^ \ s # ]+)) / ) . each do | chunk |
if chunk . starts_with? ( " # ! " )
chunk . sub! ( / ( \\ :)?+:+?! # \ Z / , '\1' )
chunk . sub! ( / {(.*)} \ Z / , '\1' )
if vore_stack . last != '_comment'
cmd = chunk [ 2 .. - 1 ] . strip
next if cmd . blank?
cmd = cmd . split ( ':::' )
cmd = cmd [ 0 ] . split ( '::' ) + cmd [ 1 .. - 1 ]
cmd = cmd [ 0 ] . split ( ':' ) + cmd [ 1 .. - 1 ]
cmd . map! { | c | c . gsub ( / \\ : / , ':' ) . gsub ( / \\ \\ : / , '\:' ) }
prefix = prefix_ns [ cmd [ 0 ] ]
cmd = prefix + cmd unless prefix . nil?
aliases . each_key do | old_cmd |
cmd = aliases [ old_cmd ] + cmd . drop ( old_cmd . length ) if cmd . take ( old_cmd . length ) == old_cmd
end
elsif chunk . in? ( [ '#!comment:end' , '#!comment:stop' , '#!comment:endall' , '#!comment:stopall' ] )
vore_stack . pop
component_stack . pop
next
else
next
end
2019-04-06 14:16:55 -07:00
2019-04-08 15:23:20 -07:00
case cmd [ 0 ]
when 'var'
chunk = nil
case cmd [ 1 ]
when 'end' , 'stop'
vore_stack . pop
component_stack . pop
when 'endall' , 'stopall'
vore_stack = [ ]
component_stack . reject! { | c | c == :var }
else
var = cmd [ 1 ]
next if var . nil? || var . starts_with? ( '_' )
new_value = cmd [ 2 .. - 1 ]
if new_value . blank?
chunk = vars [ var ]
elsif new_value . length == 1 && new_value [ 0 ] == '-'
vore_stack . push ( var )
component_stack . push ( :var )
else
vars [ var ] = new_value . join ( ':' )
end
end
2019-04-06 14:16:55 -07:00
when 'tf'
2019-04-08 15:23:20 -07:00
chunk = nil
case cmd [ 1 ]
when 'end' , 'stop'
tf_cmds . pop
component_stack . pop
when 'endall' , 'stopall'
tf_cmds = [ ]
component_stack . reject! { | c | c == :tf }
else
tf_cmds . push ( cmd [ 1 .. - 1 ] )
component_stack . push ( :tf )
end
2019-04-06 14:16:55 -07:00
when 'end' , 'stop'
2019-04-08 15:23:20 -07:00
chunk = nil
case component_stack . pop
when :tf
tf_cmds . pop
when :var , :hide
vore_stack . pop
end
when 'endall' , 'stopall'
chunk = nil
tf_cmds = [ ]
vore_stack = [ ]
component_stack = [ ]
2019-04-12 00:22:56 -07:00
when 'emojify'
chunk = nil
next if cmd [ 1 ] . nil?
src_img = nil
shortcode = cmd [ 2 ]
case cmd [ 1 ]
when 'avatar'
src_img = account . avatar
when 'parent'
next unless cmd [ 3 ] . present? && reply?
shortcode = cmd [ 3 ]
parent_status = Status . where ( id : in_reply_to_id ) . first
next if parent_status . nil?
case cmd [ 2 ]
when 'avatar'
src_img = parent_status . account . avatar
end
end
next if src_img . nil? || shortcode . nil? || ! shortcode . match? ( / \ A \ w+ \ Z / )
chunk = " : #{ shortcode } : "
emoji = CustomEmoji . find_or_initialize_by ( shortcode : shortcode , domain : nil )
if emoji . id . nil?
emoji . image = src_img
emoji . save
end
2019-04-08 15:23:20 -07:00
when 'emoji'
next if cmd [ 1 ] . nil?
shortcode = cmd [ 1 ]
domain = ( cmd [ 2 ] . blank? ? nil : cmd [ 2 ] . downcase )
chunk = " : #{ shortcode } : "
ours = CustomEmoji . find_or_initialize_by ( shortcode : shortcode , domain : nil )
if ours . id . nil?
if domain . nil?
theirs = CustomEmoji . find_by ( shortcode : shortcode )
else
theirs = CustomEmoji . find_by ( shortcode : shortcode , domain : domain )
end
unless theirs . nil?
ours . image = theirs . image
ours . save
end
end
2019-04-06 14:16:55 -07:00
when 'char'
2019-04-08 15:23:20 -07:00
chunk = nil
2019-04-06 14:16:55 -07:00
charmap = {
2019-04-08 15:23:20 -07:00
'zws' = > " \ u200b " ,
'zwnj' = > " \ u200c " ,
'zwj' = > " \ u200d " ,
'\n' = > " \n " ,
'\r' = > " \r " ,
'\t' = > " \t " ,
'\T' = > ' '
2019-04-06 14:16:55 -07:00
}
2019-04-08 15:23:20 -07:00
cmd [ 1 .. - 1 ] . each do | c |
2019-04-06 14:16:55 -07:00
next if c . nil?
if c . in? ( charmap )
2019-04-08 15:23:20 -07:00
chunks << charmap [ cmd [ 1 ] ]
2019-04-06 14:16:55 -07:00
elsif ( / ^ \ h{1,5}$ / =~ c ) && c . to_i ( 16 ) > 0
begin
chunks << [ c . to_i ( 16 ) ] . pack ( 'U*' )
rescue
chunks << '?'
end
end
end
2019-04-08 15:23:20 -07:00
when 'link'
chunk = nil
case cmd [ 1 ]
when 'permalink' , 'self'
chunk = TagManager . instance . url_for ( self )
when 'cloudroot'
chunk = " https://monsterpit.cloud/~/ #{ account . username } "
when 'blogroot'
chunk = " https://monsterpit.blog/~/ #{ account . username } "
end
2019-04-06 14:16:55 -07:00
when 'ping'
2019-04-08 15:23:20 -07:00
mentions = [ ]
case cmd [ 1 ]
2019-04-06 14:16:55 -07:00
when 'admins'
mentions = User . admins . map { | u | " @ #{ u . account . username } " }
mentions . sort!
when 'mods'
mentions = User . moderators . map { | u | " @ #{ u . account . username } " }
mentions . sort!
when 'staff'
mentions = User . admins . map { | u | " @ #{ u . account . username } " }
mentions += User . moderators . map { | u | " @ #{ u . account . username } " }
mentions . uniq!
mentions . sort!
2019-04-08 15:23:20 -07:00
end
chunk = mentions . join ( ' ' )
when 'tag'
chunk = nil
records = [ ]
valid_name = / ^[[:word:]_ \ -]*[[:alpha:]_· \ -][[:word:]_ \ -]*$ /
cmd [ 1 .. - 1 ] . select { | t | t . present? && valid_name . match? ( t ) } . uniq . each do | name |
next if self . tags . where ( name : name ) . exists?
tag = Tag . where ( name : name ) . first_or_create ( name : name )
self . tags << tag
records << tag
TrendingTags . record_use! ( tag , account , created_at ) if distributable?
end
if public_visibility? || unlisted_visibility?
account . featured_tags . where ( tag_id : records . map ( & :id ) ) . each do | featured_tag |
featured_tag . increment ( created_at )
end
2019-04-02 15:11:05 -07:00
end
2019-04-06 14:16:55 -07:00
when 'thread'
2019-04-08 15:23:20 -07:00
chunk = nil
case cmd [ 1 ]
2019-04-06 14:16:55 -07:00
when 'reall'
if conversation_id . present?
mention_ids = Status . where ( conversation_id : conversation_id ) . flat_map { | s | s . mentions . pluck ( :account_id ) }
mention_ids . uniq!
mentions = Account . where ( id : mention_ids ) . map { | a | " @ #{ a . username } " }
2019-04-08 15:23:20 -07:00
chunk = mentions . join ( ' ' )
2019-04-06 14:16:55 -07:00
end
end
2019-04-08 15:23:20 -07:00
when 'parent'
chunk = nil
next unless reply?
parent_status = Status . where ( id : in_reply_to_id ) . first
next if parent_status . nil?
case cmd [ 1 ]
when 'edit'
next unless reply? && in_reply_to_account_id == account_id
when 'permalink'
chunk = TagManager . instance . url_for ( parent_status )
end
when 'media'
chunk = nil
media_idx = cmd [ 1 ]
media_cmd = cmd [ 2 ]
media_args = cmd [ 3 .. - 1 ]
next unless media_cmd . present? && media_idx . present? && media_idx . scan ( / \ D / ) . empty?
media_idx = media_idx . to_i
next if media_attachments [ media_idx - 1 ] . nil?
case media_cmd
when 'desc'
if media_args . present?
vars [ " media_ #{ media_idx } _desc " ] = media_args . join ( ':' )
2019-04-06 14:16:55 -07:00
else
2019-04-08 15:23:20 -07:00
vore_stack . push ( " media_ #{ media_idx } _desc " )
component_stack . push ( :var )
2019-04-06 14:16:55 -07:00
end
end
2019-04-08 15:23:20 -07:00
post_cmds . push ( [ 'media' , media_idx , media_cmd ] )
when 'bangtag'
chunk = chunk . sub ( 'bangtag:' , '' ) . gsub ( ':' , " : \ u200c " )
when 'join'
chunk = nil
next if cmd [ 1 ] . nil?
charmap = {
'zws' = > " \ u200b " ,
'zwnj' = > " \ u200c " ,
'zwj' = > " \ u200d " ,
'\n' = > " \n " ,
'\r' = > " \r " ,
'\t' = > " \t " ,
'\T' = > ' '
}
sep = charmap [ cmd [ 1 ] ]
chunk = cmd [ 2 .. - 1 ] . join ( sep . nil? ? cmd [ 1 ] : sep )
when 'hide'
chunk = nil
case cmd [ 1 ]
when 'end' , 'stop' , 'endall' , 'stopall'
vore_stack . reject! { | v | v == '_' }
compontent_stack . reject! { | c | c == :hide }
else
if cmd [ 1 ] . nil? && ! '_' . in? ( vore_stack )
vore_stack . push ( '_' )
component_stack . push ( :hide )
2019-04-06 14:16:55 -07:00
end
end
2019-04-08 15:23:20 -07:00
when 'comment'
chunk = nil
if cmd [ 1 ] . nil?
vore_stack . push ( '_comment' )
component_stack . push ( :var )
end
end
end
if chunk . present? && tf_cmds . present?
tf_cmds . each do | tf_cmd |
next if chunk . nil?
case tf_cmd [ 0 ]
when 'replace' , 'sub' , 's'
tf_cmd [ 1 .. - 1 ] . in_groups_of ( 2 ) do | args |
chunk . sub! ( * args ) if args . all?
end
when 'replaceall' , 'gsub' , 'gs'
tf_cmd [ 1 .. - 1 ] . in_groups_of ( 2 ) do | args |
chunk . gsub! ( * args ) if args . all?
end
end
end
end
unless chunk . blank? || vore_stack . empty?
var = vore_stack . last
next if var == '_'
if vars [ var ] . nil?
vars [ var ] = chunk . lstrip
else
vars [ var ] += chunk . rstrip
end
chunk = nil
end
chunks << chunk unless chunk . nil?
end
vars . transform_values! { | v | v . rstrip }
if post_cmds . present?
post_cmds . each do | post_cmd |
case post_cmd [ 0 ]
when 'media'
media_idx = post_cmd [ 1 ]
media_cmd = post_cmd [ 2 ]
media_args = post_cmd [ 3 .. - 1 ]
case media_cmd
when 'desc'
media_attachments [ media_idx - 1 ] . description = vars [ " media_ #{ media_idx } _desc " ]
media_attachments [ media_idx - 1 ] . save
end
2019-04-06 14:16:55 -07:00
end
2019-04-02 11:42:24 -07:00
end
end
2019-04-08 15:23:20 -07:00
2019-04-02 11:42:24 -07:00
self . text = chunks . join ( '' )
2019-04-08 15:23:20 -07:00
save
2019-04-02 11:42:24 -07:00
end
2017-05-12 10:09:21 -07:00
def set_conversation
2018-11-25 07:35:21 -08:00
self . thread = thread . reblog if thread & . reblog?
2017-05-12 10:09:21 -07:00
self . reply = ! ( in_reply_to_id . nil? && thread . nil? ) unless reply
if reply? && ! thread . nil?
self . in_reply_to_account_id = carried_over_reply_to_account_id
self . conversation_id = thread . conversation_id if conversation_id . nil?
elsif conversation_id . nil?
2018-04-12 05:45:17 -07:00
self . conversation = Conversation . new
2017-05-12 10:09:21 -07:00
end
end
def carried_over_reply_to_account_id
if thread . account_id == account_id && thread . reply?
thread . in_reply_to_account_id
else
thread . account_id
end
end
2017-09-06 10:01:28 -07:00
def set_local
self . local = account . local?
end
2017-12-29 10:52:04 -08:00
def update_statistics
return unless public_visibility? || unlisted_visibility?
ActivityTracker . increment ( 'activity:statuses:local' )
end
2018-05-29 17:50:23 -07:00
def increment_counter_caches
return if direct_visibility?
2018-11-18 15:43:52 -08:00
account & . increment_count! ( :statuses_count )
2019-01-18 06:56:21 -08:00
reblog & . increment_count! ( :reblogs_count ) if reblog? && ( public_visibility? || unlisted_visibility? )
2018-08-17 18:03:23 -07:00
thread & . increment_count! ( :replies_count ) if in_reply_to_id . present? && ( public_visibility? || unlisted_visibility? )
2018-05-29 17:50:23 -07:00
end
def decrement_counter_caches
return if direct_visibility? || marked_for_mass_destruction?
2018-11-18 15:43:52 -08:00
account & . decrement_count! ( :statuses_count )
2019-01-18 06:56:21 -08:00
reblog & . decrement_count! ( :reblogs_count ) if reblog? && ( public_visibility? || unlisted_visibility? )
2018-08-17 18:03:23 -07:00
thread & . decrement_count! ( :replies_count ) if in_reply_to_id . present? && ( public_visibility? || unlisted_visibility? )
2018-05-29 17:50:23 -07:00
end
2018-10-07 14:44:58 -07:00
def unlink_from_conversations
return unless direct_visibility?
mentioned_accounts = mentions . includes ( :account ) . map ( & :account )
inbox_owners = mentioned_accounts . select ( & :local? ) + ( account . local? ? [ account ] : [ ] )
inbox_owners . each do | inbox_owner |
AccountConversation . remove_status ( inbox_owner , self )
end
end
2016-02-20 13:53:20 -08:00
end