about summary refs log tree commit diff
path: root/app/services
diff options
context:
space:
mode:
authormultiple creatures <dev@multiple-creature.party>2019-07-15 13:34:05 -0500
committermultiple creatures <dev@multiple-creature.party>2019-07-15 14:12:24 -0500
commitcf3ec71aa564c7fe47ec79f8dd5f14e3bce0b85c (patch)
tree49a3356c4177157b377aeca223a7d1c1e2e3dc17 /app/services
parent0a5eba734e6aa6a6e7e8f64b022af8ea129c9f5d (diff)
local visibility scope, chat scope+tags, unlisted tags
Diffstat (limited to 'app/services')
-rw-r--r--app/services/fan_out_on_write_service.rb37
-rw-r--r--app/services/post_status_service.rb78
-rw-r--r--app/services/process_hashtags_service.rb23
3 files changed, 105 insertions, 33 deletions
diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb
index 4db3d4cf4..f726dba18 100644
--- a/app/services/fan_out_on_write_service.rb
+++ b/app/services/fan_out_on_write_service.rb
@@ -6,7 +6,7 @@ class FanOutOnWriteService < BaseService
   def call(status)
     raise Mastodon::RaceConditionError if status.visibility.nil?
 
-    deliver_to_self(status) if status.account.local?
+    deliver_to_self(status) if status.account.local? && !status.chat_visibility?
 
     render_anonymous_payload(status)
 
@@ -16,28 +16,35 @@ class FanOutOnWriteService < BaseService
       deliver_to_own_conversation(status)
     elsif status.limited_visibility?
       deliver_to_mentioned_followers(status)
+    elsif status.chat_visibility?
+      deliver_to_mentioned_followers(status)
+      deliver_to_hashtags(status)
+    elsif status.local_visibility?
+      deliver_to_followers(status)
+      deliver_to_lists(status)
+      deliver_to_local(status) unless filtered?(status)
     else
       deliver_to_followers(status)
       deliver_to_lists(status)
-    end
 
-    return if status.reblog? && !Setting.show_reblogs_in_public_timelines
-    return if filtered?(status)
+      return if status.reblog? && !Setting.show_reblogs_in_public_timelines
+      return if filtered?(status) || (status.reblog? && filtered?(status.reblog))
 
-    if !status.reblog? && status.distributable?
-      deliver_to_hashtags(status)
-      deliver_to_public(status) if status.curated
-    end
+      if !status.reblog? && status.distributable?
+        deliver_to_hashtags(status)
+        deliver_to_public(status) if status.curated
+      end
 
-    if status.relayed?
-      status = Status.find(status.reblog_of_id)
-      return if filtered?(status)
-      render_anonymous_payload(status)
-    end
+      if status.relayed?
+        status = Status.find(status.reblog_of_id)
+        return if filtered?(status)
+        render_anonymous_payload(status)
+      end
 
-    return unless status.network? && status.public_visibility? && !status.reblog
+      return unless status.network? && status.public_visibility? && !status.reblog
 
-    deliver_to_local(status)
+      deliver_to_local(status)
+    end
   end
 
   private
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index 9650aedc8..e34430f09 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -4,6 +4,15 @@ class PostStatusService < BaseService
   include Redisable
 
   MIN_SCHEDULE_OFFSET = 5.minutes.freeze
+  VISIBILITY_RANK = {
+    'public'    => 0,
+    'unlisted'  => 1,
+    'local'     => 1,
+    'private'   => 2,
+    'direct'    => 3,
+    'limited'   => 3,
+    'chat'      => 4
+  }
 
   # Post a text status update, fetch and notify remote users mentioned
   # @param [Account] account Account from which to post
@@ -30,9 +39,10 @@ class PostStatusService < BaseService
     @text        = @options[:text] || ''
     @footer      = @options[:footer]
     @in_reply_to = @options[:thread]
-    @tags        = @options[:tags]
+    @tags        = @options[:tags] || []
     @local_only  = @options[:local_only]
     @sensitive   = (@account.force_sensitive? ? true : @options[:sensitive])
+    @preloaded_tags = @options[:preloaded_tags] || []
 
     return idempotency_duplicate if idempotency_given? && idempotency_duplicate?
 
@@ -55,9 +65,54 @@ class PostStatusService < BaseService
   private
 
   def set_footer_from_i_am
+    return if @footer.nil? || @options[:no_footer]
     name = @account.vars['_they:are']
     return if name.blank?
-    @account.vars["_they:are:#{name}"]
+    @footer = @account.vars["_they:are:#{name}"]
+  end
+
+  def set_initial_visibility
+    @visibility = @options[:visibility] || @account.user_default_visibility
+  end
+
+  def limit_visibility_if_silenced
+    @visibility = :unlisted if @visibility.in?([nil, 'public']) && @account.silenced? || @account.force_unlisted
+  end
+
+  def limit_visibility_to_reply
+    return if @in_reply_to.nil?
+    @visibility = @in_reply_to.visibility if @visibility.nil? ||
+      VISIBILITY_RANK[@visibility] < VISIBILITY_RANK[@in_reply_to.visibility]
+  end
+
+  def set_local_only
+    @local_only = true if @account.user_always_local_only? || @in_reply_to&.local_only
+  end
+
+  def set_chat
+    if @in_reply_to.present?
+      unless @in_reply_to.chat_tags.blank?
+        @preloaded_tags |= @in_reply_to.chat_tags
+        @visibility = :chat
+        @in_reply_to = nil
+      end
+    elsif @tags.present? && @tags.any? { |t| t.start_with?('chat.', '.chat.') }
+      @visibility = :chat
+      @local_only = true if @tags.any? { |t| t.in?(%w(chat.local .chat.local)) || t.start_with?('chat.local.', '.chat.local.') }
+    end
+  end
+
+  # move tags out of body so we can format them later
+  def extract_tags
+    chunks = []
+    @text.split(/^((?:#[\w:._·\-]*\s*)+)/).each do |chunk|
+      if chunk.start_with?('#')
+        @tags |= chunk[1..-1].split(/\s+/)
+      else
+        chunks << chunk
+      end
+    end
+    @text = chunks.join
   end
 
   def preprocess_attributes!
@@ -66,18 +121,17 @@ class PostStatusService < BaseService
      @text = @media.find(&:video?) ? '📹' : '🖼' if @media.size > 0
     end
 
-    @footer = set_footer_from_i_am if @footer.nil? && !@options[:no_footer]
-
-    @visibility   = @options[:visibility] || @account.user_default_visibility
-    @visibility   = :unlisted if @visibility.in?([nil, 'public']) && @account.silenced? || @account.force_unlisted
+    set_footer_from_i_am
+    extract_tags
+    set_chat
+    set_local_only
 
-    if @in_reply_to.present? && @in_reply_to.visibility.present?
-      v = %w(public unlisted private direct limited)
-      @visibility = @in_reply_to.visibility if @visibility.nil? || v.index(@visibility) < v.index(@in_reply_to.visibility)
+    unless @visibility == :chat
+      set_initial_visibility
+      limit_visibility_if_silenced
+      limit_visibility_to_reply
     end
 
-    @local_only = true if @account.user_always_local_only? || @in_reply_to&.local_only
-
     @sensitive = (@account.user_defaults_to_sensitive? || @options[:spoiler_text].present?) if @sensitive.nil?
 
     @scheduled_at = @options[:scheduled_at]&.to_datetime
@@ -94,7 +148,7 @@ class PostStatusService < BaseService
       @status = @account.statuses.create!(status_attributes)
     end
 
-    process_hashtags_service.call(@status, @tags)
+    process_hashtags_service.call(@status, @tags, @preloaded_tags)
     process_mentions_service.call(@status)
   end
 
diff --git a/app/services/process_hashtags_service.rb b/app/services/process_hashtags_service.rb
index 07806b4a7..fff4f5db1 100644
--- a/app/services/process_hashtags_service.rb
+++ b/app/services/process_hashtags_service.rb
@@ -1,24 +1,35 @@
 # frozen_string_literal: true
 
 class ProcessHashtagsService < BaseService
-  def call(status, tags = [])
+  def call(status, tags = [], preloaded_tags = [])
+    status.tags |= preloaded_tags unless preloaded_tags.blank?
+
     if status.local?
       tags = Extractor.extract_hashtags(status.text) | (tags.nil? ? [] : tags)
     end
     records = []
 
     tags.map { |str| str.mb_chars.downcase }.uniq(&:to_s).each do |name|
-      name = name.gsub(/[:.]+/, '.')
-      next if name.blank?
-      component_indices = name.size.times.select {|i| name[i] == '.'}
-      component_indices << name.size - 1
+      name.gsub!(/[:.]+/, '.')
+      next if name.blank? || name == '.'
+
+      chat = name.starts_with?('chat.', '.chat.')
+      if chat
+        component_indices = [name.size - 1]
+      else
+        component_indices = 1.upto(name.size).select { |i| name[i] == '.' }
+        component_indices << name.size - 1
+      end
+
       component_indices.take(6).each_with_index do |i, nest|
         frag = (nest != 5) ? name[0..i] : name
         tag = Tag.where(name: frag).first_or_create(name: frag)
 
+        tag.chatters.find_or_create_by(id: status.account_id) if chat
+
         next if status.tags.include?(tag)
         status.tags << tag
-        next if tag.local || tag.private
+        next if tag.unlisted || component_indices.size > 1
 
         records << tag
         TrendingTags.record_use!(tag, status.account, status.created_at) if status.distributable?