about summary refs log tree commit diff
path: root/app/models/status.rb
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/status.rb')
-rw-r--r--app/models/status.rb48
1 files changed, 28 insertions, 20 deletions
diff --git a/app/models/status.rb b/app/models/status.rb
index 46d92ea33..81b26fd14 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -6,15 +6,15 @@ class Status < ApplicationRecord
   include Streamable
   include Cacheable
 
-  enum visibility: [:public, :unlisted, :private], _suffix: :visibility
+  enum visibility: [:public, :unlisted, :private, :direct], _suffix: :visibility
 
   belongs_to :application, class_name: 'Doorkeeper::Application'
 
-  belongs_to :account, inverse_of: :statuses
+  belongs_to :account, inverse_of: :statuses, counter_cache: true
   belongs_to :in_reply_to_account, foreign_key: 'in_reply_to_account_id', class_name: 'Account'
 
   belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies
-  belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs
+  belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs, counter_cache: :reblogs_count
 
   has_many :favourites, inverse_of: :status, dependent: :destroy
   has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy
@@ -37,6 +37,9 @@ class Status < ApplicationRecord
   scope :remote, -> { where.not(uri: nil) }
   scope :local, -> { where(uri: nil) }
 
+  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') }
+
   cache_associated :account, :application, :media_attachments, :tags, :stream_entry, mentions: :account, reblog: [:account, :application, :stream_entry, :tags, :media_attachments, mentions: :account], thread: :account
 
   def reply?
@@ -72,12 +75,14 @@ class Status < ApplicationRecord
   end
 
   def hidden?
-    private_visibility?
+    private_visibility? || direct_visibility?
   end
 
   def permitted?(other_account = nil)
-    if private_visibility?
-      (account.id == other_account&.id || other_account&.following?(account) || mentions.where(account: other_account).exists?)
+    if direct_visibility?
+      account.id == other_account&.id || mentions.where(account: other_account).exists?
+    elsif private_visibility?
+      account.id == other_account&.id || other_account&.following?(account) || mentions.where(account: other_account).exists?
     else
       other_account.nil? || !account.blocking?(other_account)
     end
@@ -109,8 +114,8 @@ class Status < ApplicationRecord
     def as_public_timeline(account = nil, local_only = false)
       query = joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id')
               .where(visibility: :public)
-              .where('(statuses.reply = false OR statuses.in_reply_to_account_id = statuses.account_id)')
-              .where('statuses.reblog_of_id IS NULL')
+              .without_replies
+              .without_reblogs
 
       query = query.where('accounts.domain IS NULL') if local_only
 
@@ -121,7 +126,7 @@ class Status < ApplicationRecord
       query = tag.statuses
                  .joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id')
                  .where(visibility: :public)
-                 .where('statuses.reblog_of_id IS NULL')
+                 .without_reblogs
 
       query = query.where('accounts.domain IS NULL') if local_only
 
@@ -153,24 +158,27 @@ class Status < ApplicationRecord
     end
 
     def permitted_for(target_account, account)
-      if account&.id == target_account.id || account&.following?(target_account)
-        where('1 = 1')
-      elsif !account.nil? && target_account.blocking?(account)
+      return where.not(visibility: [:private, :direct]) if account.nil?
+
+      if target_account.blocking?(account) # get rid of blocked peeps
         where('1 = 0')
-      elsif !account.nil?
+      elsif account.id == target_account.id # author can see own stuff
+        where('1 = 1')
+      elsif account.following?(target_account) # followers can see followers-only stuff, but also things they are mentioned in
+        joins('LEFT OUTER JOIN mentions ON statuses.id = mentions.status_id AND mentions.account_id = ' + account.id.to_s)
+          .where('statuses.visibility != ? OR mentions.id IS NOT NULL', Status.visibilities[:direct])
+      else # non-followers can see everything that isn't private/direct, but can see stuff they are mentioned in
         joins('LEFT OUTER JOIN mentions ON statuses.id = mentions.status_id AND mentions.account_id = ' + account.id.to_s)
-          .where('statuses.visibility != ? OR mentions.id IS NOT NULL', Status.visibilities[:private])
-      else
-        where.not(visibility: :private)
+          .where('statuses.visibility NOT IN (?) OR mentions.id IS NOT NULL', [Status.visibilities[:direct], Status.visibilities[:private]])
       end
     end
 
     private
 
     def filter_timeline(query, account)
-      blocked = Block.where(account: account).pluck(:target_account_id) + Block.where(target_account: account).pluck(:account_id)
-      query   = query.where('statuses.account_id NOT IN (?)', blocked) unless blocked.empty?
-      query   = query.where('accounts.silenced = TRUE') if account.silenced?
+      blocked = Block.where(account: account).pluck(:target_account_id) + Block.where(target_account: account).pluck(:account_id) + Mute.where(account: account).pluck(:target_account_id)
+      query   = query.where('statuses.account_id NOT IN (?)', blocked) unless blocked.empty?  # Only give us statuses from people we haven't blocked, or muted, or that have blocked us
+      query   = query.where('accounts.silenced = TRUE') if account.silenced?                  # and if we're hellbanned, only people who are also hellbanned
       query
     end
 
@@ -192,6 +200,6 @@ class Status < ApplicationRecord
   private
 
   def filter_from_context?(status, account)
-    account&.blocking?(status.account_id) || !status.permitted?(account)
+    account&.blocking?(status.account_id) || account&.muting?(status.account_id) || (status.account.silenced? && !account&.following?(status.account_id)) || !status.permitted?(account)
   end
 end