From cf3ec71aa564c7fe47ec79f8dd5f14e3bce0b85c Mon Sep 17 00:00:00 2001 From: multiple creatures Date: Mon, 15 Jul 2019 13:34:05 -0500 Subject: local visibility scope, chat scope+tags, unlisted tags --- app/services/fan_out_on_write_service.rb | 37 +++++++++------ app/services/post_status_service.rb | 78 +++++++++++++++++++++++++++----- app/services/process_hashtags_service.rb | 23 +++++++--- 3 files changed, 105 insertions(+), 33 deletions(-) (limited to 'app/services') 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? -- cgit