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.rb9
-rw-r--r--app/models/chat_account.rb17
-rw-r--r--app/models/concerns/status_threading_concern.rb2
-rw-r--r--app/models/featured_tag.rb6
-rw-r--r--app/models/status.rb30
-rw-r--r--app/models/tag.rb27
6 files changed, 67 insertions, 24 deletions
diff --git a/app/models/account.rb b/app/models/account.rb
index 97ebd14d3..7040f138b 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -71,7 +71,8 @@ class Account < ApplicationRecord
 
   LOCAL_DOMAINS = ENV.fetch('LOCAL_DOMAINS', '').chomp.split(/\.?\s+/).freeze
 
-  enum protocol: [:ostatus, :activitypub]
+  has_many :chat_accounts, dependent: :destroy, inverse_of: :account
+  has_many :chat_tags, through: :chat_accounts, source: :tag
 
   validates :username, presence: true
 
@@ -545,6 +546,7 @@ class Account < ApplicationRecord
 
   before_create :generate_keys
   before_create :set_domain_from_inbox_url
+  before_create :set_chat_support
   before_validation :prepare_contents, if: :local?
   before_validation :prepare_username, on: :create
   before_destroy :clean_feed_manager
@@ -567,6 +569,11 @@ class Account < ApplicationRecord
     nil
   end
 
+  def set_chat_support
+    return unless local?
+    self.supports_chat = true
+  end
+
   def generate_keys
     return unless local? && !Rails.env.test?
 
diff --git a/app/models/chat_account.rb b/app/models/chat_account.rb
new file mode 100644
index 000000000..41589a395
--- /dev/null
+++ b/app/models/chat_account.rb
@@ -0,0 +1,17 @@
+# == Schema Information
+#
+# Table name: chat_accounts
+#
+#  id         :bigint(8)        not null, primary key
+#  account_id :bigint(8)        not null
+#  tag_id     :bigint(8)        not null
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#
+
+class ChatAccount < ApplicationRecord
+  belongs_to :account, inverse_of: :chat_accounts
+  belongs_to :tag, inverse_of: :chat_accounts
+
+  validates :account_id, uniqueness: { scope: :tag_id }
+end
diff --git a/app/models/concerns/status_threading_concern.rb b/app/models/concerns/status_threading_concern.rb
index 15eb695cd..1e5c52c46 100644
--- a/app/models/concerns/status_threading_concern.rb
+++ b/app/models/concerns/status_threading_concern.rb
@@ -12,7 +12,7 @@ module StatusThreadingConcern
   end
 
   def self_replies(limit)
-    account.statuses.where(in_reply_to_id: id, visibility: [:public, :unlisted]).reorder(id: :asc).limit(limit)
+    account.statuses.where(in_reply_to_id: id, visibility: [:public, :unlisted, :local]).reorder(id: :asc).limit(limit)
   end
 
   private
diff --git a/app/models/featured_tag.rb b/app/models/featured_tag.rb
index d06ae26a8..f4015fb07 100644
--- a/app/models/featured_tag.rb
+++ b/app/models/featured_tag.rb
@@ -31,12 +31,12 @@ class FeaturedTag < ApplicationRecord
   end
 
   def decrement(deleted_status_id)
-    update(statuses_count: [0, statuses_count - 1].max, last_status_at: account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).where.not(id: deleted_status_id).select(:created_at).first&.created_at)
+    update(statuses_count: [0, statuses_count - 1].max, last_status_at: account.statuses.where(visibility: %i(public unlisted local)).tagged_with(tag).where.not(id: deleted_status_id).select(:created_at).first&.created_at)
   end
 
   def reset_data
-    self.statuses_count = account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).count
-    self.last_status_at = account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).select(:created_at).first&.created_at
+    self.statuses_count = account.statuses.where(visibility: %i(public unlisted local)).tagged_with(tag).count
+    self.last_status_at = account.statuses.where(visibility: %i(public unlisted local)).tagged_with(tag).select(:created_at).first&.created_at
   end
 
   private
diff --git a/app/models/status.rb b/app/models/status.rb
index 30af341cc..8315491f7 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -53,7 +53,7 @@ class Status < ApplicationRecord
 
   update_index('statuses#status', :proper) if Chewy.enabled?
 
-  enum visibility: [:public, :unlisted, :private, :direct, :limited], _suffix: :visibility
+  enum visibility: [:public, :unlisted, :private, :direct, :limited, :local, :chat], _suffix: :visibility
 
   belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
 
@@ -103,7 +103,8 @@ class Status < ApplicationRecord
   scope :without_reblogs, -> { where('statuses.reblog_of_id IS NULL') }
   scope :reblogs, -> { where('statuses.reblog_of_id IS NOT NULL') } # all reblogs
   scope :with_public_visibility, -> { where(visibility: :public) }
-  scope :public_browsable, -> { where(visibility: [:public, :unlisted]) }
+  scope :public_local_visibility, -> { where(visibility: [:public, :local]) }
+  scope :public_browsable, -> { where(visibility: [:public, :unlisted, :local, :chat]) }
   scope :tagged_with, ->(tag) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag }) }
   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 }) }
@@ -240,7 +241,7 @@ class Status < ApplicationRecord
   end
 
   def distributable?
-    public_visibility? || unlisted_visibility?
+    public_visibility? || unlisted_visibility? || local_visibility?
   end
 
   def with_media?
@@ -261,6 +262,11 @@ class Status < ApplicationRecord
     @emojis = CustomEmoji.from_text(fields.join(' '), account.domain)
   end
 
+  def chat_tags
+    return @chat_tags if defined?(@chat_tags)
+    @chat_tags = tags.only_chat
+  end
+
   def mark_for_mass_destruction!
     @marked_for_mass_destruction = true
   end
@@ -313,7 +319,7 @@ class Status < ApplicationRecord
       pattern = sanitize_sql_like(term)
       pattern = "#{pattern}"
       scope = Status.where("tsv @@ plainto_tsquery('english', ?)", pattern)
-      query = scope.where(visibility: :public)
+      query = scope.public_local_visibility
       if account.present?
         query = query
           .or(scope.where(account: account))
@@ -333,7 +339,7 @@ class Status < ApplicationRecord
     end
 
     def as_home_timeline(account)
-      where(account: [account] + account.following).where(visibility: [:public, :unlisted, :private])
+      where(account: [account] + account.following, visibility: [:public, :unlisted, :local, :private])
     end
 
     def as_direct_timeline(account, limit = 20, max_id = nil, since_id = nil, cache_ids = false)
@@ -390,7 +396,7 @@ class Status < ApplicationRecord
 
     def as_tag_timeline(tag, account = nil, local_only = false, priv = false)
       query = tag_timeline_scope(account, local_only, priv).tagged_with(tag)
-      apply_timeline_filters(query, account, local_only)
+      apply_timeline_filters(query, account, local_only, true)
     end
 
     def as_outbox_timeline(account)
@@ -438,7 +444,7 @@ class Status < ApplicationRecord
     end
 
     def permitted_for(target_account, account)
-      visibility = [:public, :unlisted]
+      visibility = [:public, :unlisted, :local]
 
       if account.nil?
         query = where(visibility: visibility).not_local_only
@@ -464,7 +470,7 @@ class Status < ApplicationRecord
 
     def timeline_scope(local_only = false)
       starting_scope = local_only ? Status.network : Status
-      starting_scope = starting_scope.with_public_visibility
+      starting_scope = local_only ? starting_scope.public_local_visibility : starting_scope.with_public_visibility
       if Setting.show_reblogs_in_public_timelines
         starting_scope
       else
@@ -498,19 +504,19 @@ class Status < ApplicationRecord
       end
     end
 
-    def apply_timeline_filters(query, account, local_only)
+    def apply_timeline_filters(query, account = nil, local_only = false, tag_timeline = false)
       if account.nil?
         filter_timeline_default(query)
       else
-        filter_timeline_for_account(query, account, local_only)
+        filter_timeline_for_account(query, account, local_only, tag_timeline)
       end
     end
 
-    def filter_timeline_for_account(query, account, local_only)
+    def filter_timeline_for_account(query, account, local_only, tag_timeline)
       query = query.not_excluded_by_account(account)
       query = query.not_domain_blocked_by_account(account) unless local_only
       query = query.in_chosen_languages(account) if account.chosen_languages.present?
-      query = query.reply_not_excluded_by_account(account)
+      query = query.reply_not_excluded_by_account(account) unless tag_timeline
       query = query.mention_not_excluded_by_account(account)
       query.merge(account_silencing_filter(account))
     end
diff --git a/app/models/tag.rb b/app/models/tag.rb
index d3511a54e..858f674c3 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -9,6 +9,8 @@
 #  updated_at :datetime         not null
 #  local      :boolean          default(FALSE), not null
 #  private    :boolean          default(FALSE), not null
+#  unlisted   :boolean          default(FALSE), not null
+#  chat       :boolean          default(FALSE), not null
 #
 
 class Tag < ApplicationRecord
@@ -17,6 +19,9 @@ class Tag < ApplicationRecord
   has_and_belongs_to_many :sample_accounts, -> { searchable.discoverable.popular.limit(3) }, class_name: 'Account'
 
   has_many :featured_tags, dependent: :destroy, inverse_of: :tag
+  has_many :chat_accounts, dependent: :destroy, inverse_of: :tag
+  has_many :chatters, through: :chat_accounts, source: :account
+
   has_one :account_tag_stat, dependent: :destroy
 
   HASHTAG_NAME_RE = '[[:word:]:._\-]*[[:alpha:]:._·\-][[:word:]:._\-]*'
@@ -28,10 +33,12 @@ class Tag < ApplicationRecord
   scope :hidden, -> { where(account_tag_stats: { hidden: true }) }
   scope :most_used, ->(account) { joins(:statuses).where(statuses: { account: account }).group(:id).order(Arel.sql('count(*) desc')) }
 
-  scope :only_local, -> { where(local: true) }
-  scope :only_global, -> { where(local: false) }
+  scope :only_local, -> { where(local: true, unlisted: false) }
+  scope :only_global, -> { where(local: false, unlisted: false) }
   scope :only_private, -> { where(private: true) }
-  scope :only_public, -> { where(private: false) }
+  scope :only_unlisted, -> { where(unlisted: true) }
+  scope :only_chat, -> { where(chat: true) }
+  scope :only_public, -> { where(unlisted: false) }
 
   delegate :accounts_count,
            :accounts_count=,
@@ -73,9 +80,11 @@ class Tag < ApplicationRecord
 
   class << self
     def search_for(term, limit = 5, offset = 0)
-      pattern = sanitize_sql_like(term.strip.gsub(':', '.')) + '%'
+      term = term.strip.gsub(':', '.')
+      pattern = sanitize_sql_like(term) + '%'
 
-      Tag.where('lower(name) like lower(?)', pattern)
+      Tag.only_public.where('lower(name) like lower(?)', pattern)
+         .or(Tag.only_unlisted.where(name: term))
          .order(:name)
          .limit(limit)
          .offset(offset)
@@ -98,7 +107,11 @@ class Tag < ApplicationRecord
   end
 
   def set_scope
-    self.private = true if name.in?(['self', '_self']) || name.starts_with?('self.', '_self.')
-    self.local = true if self.private || name.in?(['local', '_local']) || name.starts_with?('local.', '_local.')
+    self.private = true if name.in?(%w(self .self)) || name.starts_with?('self.', '.self.')
+    self.unlisted = true if self.private || name.starts_with?('.')
+    self.chat = true if name.starts_with?('chat.', '.chat')
+    self.local = true if self.private ||
+      name.in?(%w(local .local chat.local .chat.local)) ||
+      name.starts_with?('local.', '.local', 'chat.local.' '.chat.local')
   end
 end