2016-11-15 15:56:29 +00:00
# frozen_string_literal: true
2017-05-02 00:14:47 +00:00
# == Schema Information
#
# Table name: statuses
#
2018-04-23 09:29:17 +00:00
# id :bigint(8) not null, primary key
2017-05-02 00:14:47 +00:00
# uri :string
# text :text default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
2018-04-23 09:29:17 +00:00
# in_reply_to_id :bigint(8)
# reblog_of_id :bigint(8)
2017-05-02 00:14:47 +00:00
# url :string
2017-07-13 01:12:25 +00:00
# sensitive :boolean default(FALSE), not null
2017-05-02 00:14:47 +00:00
# visibility :integer default("public"), not null
# spoiler_text :text default(""), not null
2017-07-13 01:12:25 +00:00
# reply :boolean default(FALSE), not null
2017-06-09 16:09:37 +00:00
# language :string
2018-04-23 09:29:17 +00:00
# conversation_id :bigint(8)
2017-09-06 18:57:52 +00:00
# local :boolean
2018-04-23 09:29:17 +00:00
# account_id :bigint(8) not null
# application_id :bigint(8)
# in_reply_to_account_id :bigint(8)
2019-03-03 21:18:23 +00:00
# poll_id :bigint(8)
2020-06-15 22:56:38 +00:00
# deleted_at :datetime
2021-07-12 07:39:07 +00:00
# quote_id :bigint(8)
# expired_at :datetime
2017-05-02 00:14:47 +00:00
#
2016-11-15 15:56:29 +00:00
2016-08-17 15:56:23 +00:00
class Status < ApplicationRecord
2018-10-07 21:44:58 +00:00
before_destroy :unlink_from_conversations
2019-08-22 19:55:56 +00:00
include Discard :: Model
2016-03-24 12:21:53 +00:00
include Paginable
2016-11-30 14:57:56 +00:00
include Cacheable
2017-06-05 14:07:44 +00:00
include StatusThreadingConcern
2020-03-08 14:17:39 +00:00
include RateLimitable
2020-09-05 07:33:17 +00:00
include Redisable
2020-03-08 14:17:39 +00:00
rate_limit by : :account , family : :statuses
2016-03-24 12:21:53 +00:00
2019-08-22 19:55:56 +00:00
self . discard_column = :deleted_at
2018-05-03 21:02:46 +00: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
2020-09-05 07:33:17 +00:00
attr_accessor :circle
2021-07-16 06:38:50 +00:00
attr_accessor :expires_at , :expires_action
2020-09-05 07:33:17 +00:00
2021-07-12 07:39:07 +00:00
update_index ( 'statuses' , :proper )
2018-02-09 22:04:47 +00:00
2021-02-10 00:06:37 +00:00
enum visibility : [ :public , :unlisted , :private , :direct , :limited , :mutual ] , _suffix : :visibility
2016-11-30 20:32:11 +00:00
2018-01-19 19:56:47 +00:00
belongs_to :application , class_name : 'Doorkeeper::Application' , optional : true
2017-01-14 21:58:50 +00:00
2018-05-30 00:50:23 +00:00
belongs_to :account , inverse_of : :statuses
2018-01-19 19:56:47 +00:00
belongs_to :in_reply_to_account , foreign_key : 'in_reply_to_account_id' , class_name : 'Account' , optional : true
2020-09-05 07:33:17 +00:00
belongs_to :conversation , optional : true , inverse_of : :statuses
2019-03-28 03:44:59 +00:00
belongs_to :preloadable_poll , class_name : 'Poll' , foreign_key : 'poll_id' , optional : true
2016-02-22 15:00:20 +00:00
2020-09-05 07:33:17 +00:00
has_one :owned_conversation , class_name : 'Conversation' , foreign_key : 'parent_status_id' , inverse_of : :parent_status
2018-01-19 19:56:47 +00:00
belongs_to :thread , foreign_key : 'in_reply_to_id' , class_name : 'Status' , inverse_of : :replies , optional : true
2018-05-30 00:50:23 +00:00
belongs_to :reblog , foreign_key : 'reblog_of_id' , class_name : 'Status' , inverse_of : :reblogs , optional : true
2019-09-29 04:41:03 +00:00
belongs_to :quote , foreign_key : 'quote_id' , class_name : 'Status' , inverse_of : :quoted , optional : true
2016-02-23 18:17:37 +00:00
2020-12-21 17:22:17 +00:00
has_many :favourites , inverse_of : :status , dependent : :destroy
2019-11-13 22:02:10 +00:00
has_many :bookmarks , inverse_of : :status , dependent : :destroy
2021-05-19 05:58:27 +00:00
has_many :emoji_reactions , inverse_of : :status , dependent : :destroy
2016-03-16 09:46:15 +00:00
has_many :reblogs , foreign_key : 'reblog_of_id' , class_name : 'Status' , inverse_of : :reblog , dependent : :destroy
2016-03-06 11:34:39 +00:00
has_many :replies , foreign_key : 'in_reply_to_id' , class_name : 'Status' , inverse_of : :thread
2018-10-17 15:13:04 +00:00
has_many :mentions , dependent : :destroy , inverse_of : :status
has_many :active_mentions , - > { active } , class_name : 'Mention' , inverse_of : :status
2018-06-04 22:17:38 +00:00
has_many :media_attachments , dependent : :nullify
2019-09-29 04:41:03 +00:00
has_many :quoted , foreign_key : 'quote_id' , class_name : 'Status' , inverse_of : :quote , dependent : :nullify
2020-09-05 07:33:17 +00:00
has_many :capability_tokens , class_name : 'StatusCapabilityToken' , inverse_of : :status , dependent : :destroy
2017-09-01 14:20:16 +00:00
2016-11-05 14:20:05 +00:00
has_and_belongs_to_many :tags
2017-09-01 14:20:16 +00:00
has_and_belongs_to_many :preview_cards
2016-02-23 18:17:37 +00:00
2016-11-21 13:59:13 +00:00
has_one :notification , as : :activity , dependent : :destroy
2018-08-14 17:19:32 +00:00
has_one :status_stat , inverse_of : :status
2019-03-28 03:44:59 +00:00
has_one :poll , inverse_of : :status , dependent : :destroy
2021-07-16 06:38:50 +00:00
has_one :status_expire , inverse_of : :status
2016-11-21 13:59:13 +00:00
2017-09-17 13:21:57 +00:00
validates :uri , uniqueness : true , presence : true , unless : :local?
2018-03-07 07:28:52 +00:00
validates :text , presence : true , unless : - > { with_media? || reblog? }
2017-01-13 04:54:26 +00:00
validates_with StatusLengthValidator
2018-04-23 21:52:58 +00:00
validates_with DisallowedHashtagsValidator
2017-05-19 01:11:23 +00:00
validates :reblog , uniqueness : { scope : :account } , if : :reblog?
2021-02-12 11:32:55 +00:00
validates :visibility , exclusion : { in : %w( direct ) } , if : :reblog?
2020-02-27 22:45:54 +00:00
validates :quote_visibility , inclusion : { in : %w( public unlisted ) } , if : :quote?
2020-06-15 22:56:38 +00:00
validates_with ExpiresValidator , on : :create , if : :local?
2019-03-15 12:36:38 +00:00
2019-03-28 03:44:59 +00:00
accepts_nested_attributes_for :poll
2016-02-23 18:17:37 +00:00
2020-06-15 22:56:38 +00:00
default_scope { recent . kept . not_expired }
2016-10-09 13:15:21 +00:00
2017-05-23 00:53:01 +00:00
scope :recent , - > { reorder ( id : :desc ) }
2019-07-08 16:17:22 +00:00
scope :remote , - > { where ( local : false ) . where . not ( uri : nil ) }
2017-09-07 18:18:34 +00:00
scope :local , - > { where ( local : true ) . or ( where ( uri : nil ) ) }
2020-06-15 22:56:38 +00:00
2021-07-16 06:38:50 +00:00
scope :not_expired , - > { where ( expired_at : nil ) }
2021-06-28 05:56:26 +00:00
scope :include_expired , - > { unscoped . recent . kept }
2020-01-11 10:55:33 +00:00
scope :with_accounts , - > ( ids ) { where ( id : ids ) . includes ( :account ) }
2017-03-05 16:27:17 +00: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' ) }
2017-04-28 13:10:41 +00:00
scope :with_public_visibility , - > { where ( visibility : :public ) }
2020-09-23 14:01:54 +00:00
scope :tagged_with , - > ( tag_ids ) { joins ( :statuses_tags ) . where ( statuses_tags : { tag_id : tag_ids } ) }
2020-09-07 09:02:04 +00:00
scope :in_chosen_languages , - > ( account ) { where ( language : nil ) . or where ( language : account . chosen_languages ) }
2020-07-12 13:00:23 +00:00
scope :mentioned_with , - > ( account ) { joins ( :mentions ) . where ( mentions : { account_id : account } ) }
2019-05-14 17:05:02 +00: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 19:05:32 +00:00
scope :not_excluded_by_account , - > ( account ) { where . not ( account_id : account . excluded_from_timeline_account_ids ) }
2017-05-20 11:38:13 +00: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 ) }
2020-09-23 14:01:54 +00:00
scope :tagged_with_all , - > ( tag_ids ) {
Array ( tag_ids ) . reduce ( self ) do | result , id |
2018-11-05 17:53:25 +00:00
result . joins ( " INNER JOIN statuses_tags t #{ id } ON t #{ id } .status_id = statuses.id AND t #{ id } .tag_id = #{ id } " )
end
}
2020-09-23 14:01:54 +00:00
scope :tagged_with_none , - > ( tag_ids ) {
Array ( tag_ids ) . reduce ( self ) do | result , id |
2018-11-05 17:53:25 +00:00
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 16:27:17 +00:00
2018-11-18 23:43:52 +00:00
cache_associated :application ,
2018-08-14 17:19:32 +00:00
:media_attachments ,
:conversation ,
:status_stat ,
2021-07-16 06:38:50 +00:00
:status_expire ,
2018-08-14 17:19:32 +00:00
:tags ,
2018-10-28 05:35:03 +00:00
:preview_cards ,
2019-03-28 03:44:59 +00:00
:preloadable_poll ,
2021-01-31 20:24:17 +00:00
account : [ :account_stat , :user ] ,
2018-11-18 23:43:52 +00:00
active_mentions : { account : :account_stat } ,
2018-08-14 17:19:32 +00:00
reblog : [
:application ,
:tags ,
2018-10-28 05:35:03 +00:00
:preview_cards ,
2018-08-14 17:19:32 +00:00
:media_attachments ,
:conversation ,
:status_stat ,
2021-07-16 06:38:50 +00:00
:status_expire ,
2019-03-28 03:44:59 +00:00
:preloadable_poll ,
2021-01-31 20:24:17 +00:00
account : [ :account_stat , :user ] ,
2018-11-18 23:43:52 +00:00
active_mentions : { account : :account_stat } ,
2018-08-14 17:19:32 +00:00
] ,
2018-11-18 23:43:52 +00:00
thread : { account : :account_stat }
2016-03-11 15:47:36 +00:00
2017-05-31 18:38:17 +00:00
delegate :domain , to : :account , prefix : true
2018-02-17 13:28:48 +00:00
REAL_TIME_WINDOW = 6 . hours
2018-02-09 22:04:47 +00:00
def searchable_by ( preloaded = nil )
2019-10-02 18:04:46 +00:00
ids = [ ]
ids << account_id if local?
2018-02-09 22:04:47 +00:00
if preloaded . nil?
2020-05-23 03:47:25 +00:00
ids += mentions . where ( account : Account . local , silent : false ) . pluck ( :account_id )
2019-10-02 18:04:46 +00:00
ids += favourites . where ( account : Account . local ) . pluck ( :account_id )
ids += reblogs . where ( account : Account . local ) . pluck ( :account_id )
2020-03-21 02:14:10 +00:00
ids += bookmarks . where ( account : Account . local ) . pluck ( :account_id )
2021-05-19 05:58:27 +00:00
ids += emoji_reactions . where ( account : Account . local ) . pluck ( :account_id )
2018-02-09 22:04:47 +00:00
else
ids += preloaded . mentions [ id ] || [ ]
ids += preloaded . favourites [ id ] || [ ]
ids += preloaded . reblogs [ id ] || [ ]
2020-03-21 02:14:10 +00:00
ids += preloaded . bookmarks [ id ] || [ ]
2021-05-19 05:58:27 +00:00
ids += preloaded . emoji_reactions [ id ] || [ ]
2018-02-09 22:04:47 +00:00
end
ids . uniq
end
2017-02-09 19:25:39 +00:00
def reply?
2017-05-15 19:20:55 +00:00
! in_reply_to_id . nil? || attributes [ 'reply' ]
2017-02-09 19:25:39 +00:00
end
2016-02-23 18:17:37 +00:00
def local?
2017-09-06 17:01:28 +00:00
attributes [ 'local' ] || uri . nil?
2016-02-23 18:17:37 +00:00
end
2021-04-24 11:35:39 +00:00
def in_reply_to_local_account?
reply? && thread & . account & . local?
end
2016-02-23 18:17:37 +00:00
def reblog?
2016-09-29 19:28:21 +00:00
! reblog_of_id . nil?
2016-02-23 18:17:37 +00:00
end
2019-09-29 04:41:03 +00:00
def quote?
! quote_id . nil? && quote
end
2020-02-27 22:45:54 +00:00
def quote_visibility
quote & . visibility
end
2020-07-12 13:00:23 +00:00
def mentioning? ( source_account_id )
source_account_id = source_account_id . id if source_account_id . is_a? ( Account )
2020-10-06 12:04:40 +00:00
mentions . where ( account_id : source_account_id ) . exists?
2020-07-12 13:00:23 +00:00
end
2018-02-17 13:28:48 +00:00
def within_realtime_window?
created_at > = REAL_TIME_WINDOW . ago
end
2016-02-22 17:10:30 +00:00
def verb
2017-08-29 14:11:05 +00:00
if destroyed?
:delete
else
reblog? ? :share : :post
end
2016-02-22 17:10:30 +00:00
end
def object_type
2016-02-23 18:17:37 +00:00
reply? ? :comment : :note
2016-02-22 17:10:30 +00:00
end
2017-04-07 18:18:30 +00:00
def proper
reblog? ? reblog : self
end
2016-02-22 17:10:30 +00:00
def content
2017-04-07 18:18:30 +00:00
proper . text
2016-02-23 18:17:37 +00:00
end
def target
2016-09-29 19:28:21 +00:00
reblog
2016-02-22 17:10:30 +00:00
end
2018-10-28 05:35:03 +00:00
def preview_card
preview_cards . first
end
2016-12-21 19:00:18 +00:00
def hidden?
2019-07-08 10:03:45 +00:00
! distributable?
2018-10-17 15:13:04 +00:00
end
def distributable?
public_visibility? || unlisted_visibility?
2016-03-12 15:09:46 +00:00
end
2020-09-05 07:33:17 +00:00
def sign?
distributable? || limited_visibility?
end
2019-06-04 21:11:18 +00:00
2018-03-07 07:28:52 +00:00
def with_media?
media_attachments . any?
end
2021-07-16 06:38:50 +00:00
def expired?
! expired_at . nil?
end
2021-07-05 03:09:22 +00:00
def expires?
2021-07-16 06:38:50 +00:00
status_expire . present?
end
def expiry
expires? && status_expire & . expires_mark? && status_expire & . expires_at || expired_at
2021-07-05 03:09:22 +00:00
end
2017-04-16 14:38:02 +00:00
def non_sensitive_with_media?
2018-03-07 07:28:52 +00:00
! sensitive? && with_media?
2017-04-16 14:38:02 +00:00
end
2019-09-11 14:32:44 +00:00
def reported?
@reported || = Report . where ( target_account : account ) . unresolved . where ( '? = ANY(status_ids)' , id ) . exists?
end
2017-09-19 00:42:40 +00:00
def emojis
2019-03-20 16:29:12 +00:00
return @emojis if defined? ( @emojis )
2019-03-28 03:44:59 +00:00
fields = [ spoiler_text , text ]
fields += preloadable_poll . options unless preloadable_poll . nil?
2019-09-29 04:41:03 +00:00
@emojis = CustomEmoji . from_text ( fields . join ( ' ' ) , account . domain ) + ( quote? ? CustomEmoji . from_text ( [ quote . spoiler_text , quote . text ] . join ( ' ' ) , quote . account . domain ) : [ ] )
2017-09-19 00:42:40 +00:00
end
2019-08-30 08:02:41 +00:00
def index_text
2020-06-03 20:10:21 +00:00
@index_text || = [ spoiler_text , Formatter . instance . plaintext ( self ) ] . concat ( media_attachments . map ( & :description ) ) . concat ( preloadable_poll ? preloadable_poll . options : [ ] ) . concat ( quote? ? [ " QT: [ #{ quote . url || ActivityPub :: TagManager . instance . url_for ( quote ) } ] " ] : [ ] ) . filter ( & :present? ) . join ( " \n \n " )
2019-08-30 08:02:41 +00:00
end
2018-08-14 17:19:32 +00: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
2021-06-28 22:15:41 +00:00
def grouped_emoji_reactions ( account = nil )
( Oj . load ( status_stat & . emoji_reactions_cache || '' , mode : :strict ) || [ ] ) . tap do | emoji_reactions |
if account . present?
emoji_reactions . each do | emoji_reaction |
emoji_reaction [ 'me' ] = emoji_reaction [ 'account_ids' ] . include? ( account . id . to_s )
end
2021-05-19 05:58:27 +00:00
end
end
2021-06-28 22:15:41 +00:00
end
def generate_grouped_emoji_reactions
2021-09-12 16:24:47 +00:00
records = emoji_reactions . group ( :name ) . order ( Arel . sql ( 'MIN(created_at) ASC' ) ) . select ( 'name, min(custom_emoji_id) as custom_emoji_id, count(*) as count, array_agg(account_id::text order by created_at) as account_ids' )
2021-09-06 14:37:35 +00:00
Oj . dump ( ActiveModelSerializers :: SerializableResource . new ( records , each_serializer : REST :: EmojiReactionSerializer , scope : nil , scope_name : :current_user ) )
2021-06-28 22:15:41 +00:00
end
def refresh_grouped_emoji_reactions!
generate_grouped_emoji_reactions . tap do | emoji_reactions_cache |
update_status_stat! ( emoji_reactions_cache : emoji_reactions_cache )
end
2021-05-19 05:58:27 +00:00
end
2018-08-14 17:19:32 +00:00
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 21:51:12 +00:00
after_create_commit :increment_counter_caches
after_destroy_commit :decrement_counter_caches
2018-05-30 00:50:23 +00:00
2017-10-13 00:52:09 +00:00
after_create_commit :store_uri , if : :local?
2017-12-29 18:52:04 +00:00
after_create_commit :update_statistics , if : :local?
2021-07-16 06:38:50 +00:00
after_create_commit :set_status_expire , if : - > { expires_at . present? }
after_update :update_status_expire , if : - > { expires_at . present? }
2017-09-06 17:01:28 +00:00
2017-10-08 15:34:34 +00:00
around_create Mastodon :: Snowflake :: Callbacks
2020-09-05 07:33:17 +00:00
before_validation :prepare_contents , on : :create , if : :local?
before_validation :set_reblog , on : :create
before_validation :set_visibility , on : :create
before_validation :set_conversation , on : :create
before_validation :set_local , on : :create
2017-05-12 17:09:21 +00:00
2019-03-03 23:39:06 +00:00
after_create :set_poll_id
2020-09-05 07:33:17 +00:00
after_create :set_circle
2019-03-03 21:18:23 +00:00
2016-11-05 14:20:05 +00:00
class << self
2018-10-17 20:04:40 +00:00
def selectable_visibilities
visibilities . keys - %w( direct limited )
end
2016-11-05 14:20:05 +00:00
def favourites_map ( status_ids , account_id )
2018-11-16 14:02:18 +00: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 14:20:05 +00:00
end
2019-11-13 22:02:10 +00: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
2021-05-19 05:58:27 +00:00
def emoji_reactions_map ( status_ids , account_id )
EmojiReaction . 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 14:20:05 +00:00
def reblogs_map ( status_ids , account_id )
2019-09-28 03:23:32 +00:00
unscoped . select ( 'reblog_of_id' ) . where ( reblog_of_id : status_ids ) . where ( account_id : account_id ) . each_with_object ( { } ) { | s , h | h [ s . reblog_of_id ] = true }
2016-11-05 14:20:05 +00:00
end
2016-11-09 23:03:33 +00: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-15 01:04:13 +00:00
def mutes_map ( conversation_ids , account_id )
2018-11-16 14:02:18 +00: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-15 01:04:13 +00:00
end
2017-08-24 23:41:18 +00:00
def pins_map ( status_ids , account_id )
2018-11-16 14:02:18 +00: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 23:41:18 +00:00
end
2016-12-03 17:21:26 +00: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 14:44:29 +00:00
account_ids . uniq!
return if account_ids . empty?
2021-01-31 20:24:17 +00:00
accounts = Account . where ( id : account_ids ) . includes ( :account_stat , :user ) . index_by ( & :id )
2016-12-03 17:21:26 +00: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 18:13:56 +00:00
def permitted_for ( target_account , account )
2017-05-16 00:54:17 +00:00
visibility = [ :public , :unlisted ]
2017-03-15 21:52:57 +00:00
2017-05-16 00:54:17 +00:00
if account . nil?
where ( visibility : visibility )
2020-05-03 14:30:36 +00:00
elsif target_account . blocking? ( account ) || ( account . domain . present? && target_account . domain_blocking? ( account . domain ) ) # get rid of blocked peeps
2017-04-05 06:02:58 +00:00
none
2017-03-15 21:52:57 +00:00
elsif account . id == target_account . id # author can see own stuff
2017-04-05 06:02:58 +00:00
all
2017-05-16 00:54:17 +00: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.
visibility . push ( :private ) if account . following? ( target_account )
2018-06-06 19:13:30 +00:00
scope = left_outer_joins ( :reblog )
scope . where ( visibility : visibility )
. or ( scope . where ( id : account . mentions . select ( :status_id ) ) )
. merge ( scope . where ( reblog_of_id : nil ) . or ( scope . where . not ( reblogs_statuses : { account_id : account . excluded_from_timeline_account_ids } ) ) )
2016-12-26 18:13:56 +00:00
end
end
2020-03-08 15:10:48 +00:00
def from_text ( text )
return [ ] if text . blank?
2021-01-09 23:32:01 +00:00
text . scan ( FetchLinkCardService :: URL_PATTERN ) . map ( & :first ) . uniq . filter_map do | url |
2020-03-08 15:10:48 +00:00
status = begin
if TagManager . instance . local_url? ( url )
ActivityPub :: TagManager . instance . uri_to_resource ( url , Status )
else
2020-04-05 10:51:22 +00:00
EntityCache . instance . status ( url )
2020-03-08 15:10:48 +00:00
end
end
status & . distributable? ? status : nil
2021-01-09 23:32:01 +00:00
end
2020-03-08 15:10:48 +00:00
end
2016-09-08 19:23:29 +00:00
end
2016-09-22 19:39:53 +00:00
2019-08-18 12:55:03 +00:00
def status_stat
super || build_status_stat
end
2017-05-12 17:09:21 +00:00
private
2021-07-16 06:38:50 +00:00
def set_status_expire
create_status_expire ( expires_at : expires_at , action : expires_action )
end
def update_status_expire
status_expire & . update ( expires_at : expires_at , action : expires_action ) || set_status_expire
end
2018-08-14 17:19:32 +00:00
def update_status_stat! ( attrs )
2018-08-18 01:03:23 +00:00
return if marked_for_destruction? || destroyed?
2019-08-18 12:55:03 +00:00
status_stat . update ( attrs )
2018-08-14 17:19:32 +00:00
end
2017-09-06 17:01:28 +00:00
def store_uri
2018-12-05 21:51:12 +00:00
update_column ( :uri , ActivityPub :: TagManager . instance . uri_for ( self ) ) if uri . nil?
2017-09-06 17:01:28 +00:00
end
2017-05-12 17:09:21 +00:00
def prepare_contents
2017-04-03 23:33:34 +00:00
text & . strip!
2017-01-24 23:49:08 +00:00
spoiler_text & . strip!
2017-05-12 17:09:21 +00:00
end
2016-12-31 13:35:08 +00:00
2017-05-12 17:09:21 +00:00
def set_reblog
self . reblog = reblog . reblog if reblog? && reblog . reblog?
2016-12-21 19:00:18 +00:00
end
2019-03-03 21:18:23 +00:00
def set_poll_id
2019-03-28 03:44:59 +00:00
update_column ( :poll_id , poll . id ) unless poll . nil?
2019-03-03 21:18:23 +00:00
end
2017-05-12 17:09:21 +00:00
def set_visibility
2019-03-17 13:54:09 +00:00
self . visibility = reblog . visibility if reblog? && visibility . nil?
2017-05-12 17:09:21 +00:00
self . visibility = ( account . locked? ? :private : :public ) if visibility . nil?
2017-07-13 01:12:25 +00:00
self . sensitive = false if sensitive . nil?
2017-05-12 17:09:21 +00:00
end
def set_conversation
2018-11-25 15:35:21 +00:00
self . thread = thread . reblog if thread & . reblog?
2017-05-12 17:09:21 +00: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
end
2020-09-05 07:33:17 +00:00
if conversation_id . nil?
if reply? && ! thread . nil? && circle . nil?
self . conversation_id = thread . conversation_id
else
build_owned_conversation
end
end
end
def set_circle
redis . setex ( circle_id_key , 3 . days . seconds , circle . id ) if circle . present?
end
def circle_id_key
" statuses/ #{ id } /circle_id "
2017-05-12 17:09:21 +00:00
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 17:01:28 +00:00
def set_local
self . local = account . local?
end
2017-12-29 18:52:04 +00:00
def update_statistics
2019-07-08 10:03:45 +00:00
return unless distributable?
2017-12-29 18:52:04 +00:00
ActivityTracker . increment ( 'activity:statuses:local' )
end
2018-05-30 00:50:23 +00:00
def increment_counter_caches
return if direct_visibility?
2018-11-18 23:43:52 +00:00
account & . increment_count! ( :statuses_count )
2019-06-04 21:11:44 +00:00
reblog & . increment_count! ( :reblogs_count ) if reblog?
2019-07-08 10:03:45 +00:00
thread & . increment_count! ( :replies_count ) if in_reply_to_id . present? && distributable?
2018-05-30 00:50:23 +00:00
end
def decrement_counter_caches
2020-12-22 16:13:55 +00:00
return if direct_visibility?
2018-05-30 00:50:23 +00:00
2018-11-18 23:43:52 +00:00
account & . decrement_count! ( :statuses_count )
2019-06-04 21:11:44 +00:00
reblog & . decrement_count! ( :reblogs_count ) if reblog?
2019-07-08 10:03:45 +00:00
thread & . decrement_count! ( :replies_count ) if in_reply_to_id . present? && distributable?
2018-05-30 00:50:23 +00:00
end
2018-10-07 21:44:58 +00:00
def unlink_from_conversations
return unless direct_visibility?
2020-12-22 16:13:55 +00:00
mentioned_accounts = ( association ( :mentions ) . loaded? ? mentions : mentions . includes ( :account ) ) . map ( & :account )
2018-10-07 21:44:58 +00:00
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 21:53:20 +00:00
end