about summary refs log tree commit diff
path: root/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/account.rb23
-rw-r--r--app/models/media_attachment.rb73
-rw-r--r--app/models/mute.rb11
-rw-r--r--app/models/setting.rb1
-rw-r--r--app/models/status.rb17
-rw-r--r--app/models/tag.rb2
-rw-r--r--app/models/user.rb7
7 files changed, 97 insertions, 37 deletions
diff --git a/app/models/account.rb b/app/models/account.rb
index a93a0668a..078078945 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -4,7 +4,7 @@ class Account < ApplicationRecord
   include Targetable
   include PgSearch
 
-  MENTION_RE = /(?:^|[^\/\w])@([a-z0-9_]+(?:@[a-z0-9\.\-]+)?)/i
+  MENTION_RE = /(?:^|[^\/\w])@([a-z0-9_]+(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
   IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze
 
   # Local users
@@ -46,6 +46,10 @@ class Account < ApplicationRecord
   has_many :block_relationships, class_name: 'Block', foreign_key: 'account_id', dependent: :destroy
   has_many :blocking, -> { order('blocks.id desc') }, through: :block_relationships, source: :target_account
 
+  # Mute relationships
+  has_many :mute_relationships, class_name: 'Mute', foreign_key: 'account_id', dependent: :destroy
+  has_many :muting, -> { order('mutes.id desc') }, through: :mute_relationships, source: :target_account
+
   # Media
   has_many :media_attachments, dependent: :destroy
 
@@ -73,6 +77,10 @@ class Account < ApplicationRecord
     block_relationships.where(target_account: other_account).first_or_create!(target_account: other_account)
   end
 
+  def mute!(other_account)
+    mute_relationships.where(target_account: other_account).first_or_create!(target_account: other_account)
+  end
+
   def unfollow!(other_account)
     follow = active_relationships.find_by(target_account: other_account)
     follow&.destroy
@@ -83,6 +91,11 @@ class Account < ApplicationRecord
     block&.destroy
   end
 
+  def unmute!(other_account)
+    mute = mute_relationships.find_by(target_account: other_account)
+    mute&.destroy
+  end
+
   def following?(other_account)
     following.include?(other_account)
   end
@@ -91,6 +104,10 @@ class Account < ApplicationRecord
     blocking.include?(other_account)
   end
 
+  def muting?(other_account)
+    muting.include?(other_account)
+  end
+
   def requested?(other_account)
     follow_requests.where(target_account: other_account).exists?
   end
@@ -188,6 +205,10 @@ class Account < ApplicationRecord
       follow_mapping(Block.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
     end
 
+    def muting_map(target_account_ids, account_id)
+      follow_mapping(Mute.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
+    end
+
     def requested_map(target_account_ids, account_id)
       follow_mapping(FollowRequest.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
     end
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index 6925f9b0d..818190214 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -1,15 +1,32 @@
 # frozen_string_literal: true
 
 class MediaAttachment < ApplicationRecord
+  self.inheritance_column = nil
+
+  enum type: [:image, :gifv, :video]
+
   IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze
   VIDEO_MIME_TYPES = ['video/webm', 'video/mp4'].freeze
 
+  IMAGE_STYLES = { original: '1280x1280>', small: '400x400>' }.freeze
+  VIDEO_STYLES = {
+    small: {
+      convert_options: {
+        output: {
+          vf: 'scale=\'min(400\, iw):min(400\, ih)\':force_original_aspect_ratio=decrease',
+        },
+      },
+      format: 'png',
+      time: 0,
+    },
+  }.freeze
+
   belongs_to :account, inverse_of: :media_attachments
   belongs_to :status,  inverse_of: :media_attachments
 
   has_attached_file :file,
-                    styles: -> (f) { file_styles f },
-                    processors: -> (f) { f.video? ? [:transcoder] : [:thumbnail] },
+                    styles: ->(f) { file_styles f },
+                    processors: ->(f) { file_processors f },
                     convert_options: { all: '-quality 90 -strip' }
   validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES
   validates_attachment_size :file, less_than: 8.megabytes
@@ -27,45 +44,49 @@ class MediaAttachment < ApplicationRecord
     self.file = URI.parse(url)
   end
 
-  def image?
-    IMAGE_MIME_TYPES.include? file_content_type
-  end
-
-  def video?
-    VIDEO_MIME_TYPES.include? file_content_type
-  end
-
-  def type
-    image? ? 'image' : 'video'
-  end
-
   def to_param
     shortcode
   end
 
   before_create :set_shortcode
+  before_post_process :set_type
 
   class << self
     private
 
     def file_styles(f)
-      if f.instance.image?
+      if f.instance.file_content_type == 'image/gif'
         {
-          original: '1280x1280>',
-          small: '400x400>',
-        }
-      else
-        {
-          small: {
+          small: IMAGE_STYLES[:small],
+          original: {
+            format: 'mp4',
             convert_options: {
               output: {
-                vf: 'scale=\'min(400\, iw):min(400\, ih)\':force_original_aspect_ratio=decrease',
+                'movflags' => 'faststart',
+                'pix_fmt'  => 'yuv420p',
+                'vf'       => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'',
+                'vsync'    => 'cfr',
+                'b:v'      => '1300K',
+                'maxrate'  => '500K',
+                'crf'      => 6,
               },
             },
-            format: 'png',
-            time: 1,
           },
         }
+      elsif IMAGE_MIME_TYPES.include? f.instance.file_content_type
+        IMAGE_STYLES
+      else
+        VIDEO_STYLES
+      end
+    end
+
+    def file_processors(f)
+      if f.file_content_type == 'image/gif'
+        [:gif_transcoder]
+      elsif VIDEO_MIME_TYPES.include? f.file_content_type
+        [:video_transcoder]
+      else
+        [:thumbnail]
       end
     end
   end
@@ -80,4 +101,8 @@ class MediaAttachment < ApplicationRecord
       break if MediaAttachment.find_by(shortcode: shortcode).nil?
     end
   end
+
+  def set_type
+    self.type = VIDEO_MIME_TYPES.include?(file_content_type) ? :video : :image
+  end
 end
diff --git a/app/models/mute.rb b/app/models/mute.rb
new file mode 100644
index 000000000..a5b334c85
--- /dev/null
+++ b/app/models/mute.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class Mute < ApplicationRecord
+  include Paginable
+
+  belongs_to :account
+  belongs_to :target_account, class_name: 'Account'
+
+  validates :account, :target_account, presence: true
+  validates :account_id, uniqueness: { scope: :target_account_id }
+end
diff --git a/app/models/setting.rb b/app/models/setting.rb
index 3796253d4..31e1ee198 100644
--- a/app/models/setting.rb
+++ b/app/models/setting.rb
@@ -2,7 +2,6 @@
 
 class Setting < RailsSettings::Base
   source Rails.root.join('config/settings.yml')
-  namespace Rails.env
 
   def to_param
     var
diff --git a/app/models/status.rb b/app/models/status.rb
index 1b40897f3..663ac1e34 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -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?
@@ -109,8 +112,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 +124,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
 
@@ -168,9 +171,9 @@ class Status < ApplicationRecord
     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 +195,6 @@ class Status < ApplicationRecord
   private
 
   def filter_from_context?(status, account)
-    account&.blocking?(status.account_id) || (status.account.silenced? && !account&.following?(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
diff --git a/app/models/tag.rb b/app/models/tag.rb
index 77a73cce8..0d2fe43b8 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -3,7 +3,7 @@
 class Tag < ApplicationRecord
   has_and_belongs_to_many :statuses
 
-  HASHTAG_RE = /(?:^|[^\/\w])#([[:word:]_]*[[:alpha:]_][[:word:]_]*)/i
+  HASHTAG_RE = /(?:^|[^\/\)\w])#([[:word:]_]*[[:alpha:]_][[:word:]_]*)/i
 
   validates :name, presence: true, uniqueness: true
 
diff --git a/app/models/user.rb b/app/models/user.rb
index 08aac2679..bf2916d90 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -14,9 +14,10 @@ class User < ApplicationRecord
   validates :locale, inclusion: I18n.available_locales.map(&:to_s), unless: 'locale.nil?'
   validates :email, email: true
 
-  scope :prolific, -> { joins('inner join statuses on statuses.account_id = users.account_id').select('users.*, count(statuses.id) as statuses_count').group('users.id').order('statuses_count desc') }
-  scope :recent,   -> { order('id desc') }
-  scope :admins,   -> { where(admin: true) }
+  scope :prolific,  -> { joins('inner join statuses on statuses.account_id = users.account_id').select('users.*, count(statuses.id) as statuses_count').group('users.id').order('statuses_count desc') }
+  scope :recent,    -> { order('id desc') }
+  scope :admins,    -> { where(admin: true) }
+  scope :confirmed, -> { where.not(confirmed_at: nil) }
 
   def send_devise_notification(notification, *args)
     devise_mailer.send(notification, self, *args).deliver_later