about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/accounts_controller.rb4
-rw-r--r--app/controllers/api/v1/accounts/statuses_controller.rb4
-rw-r--r--app/lib/activitypub/activity/create.rb5
-rw-r--r--app/models/status.rb29
-rw-r--r--app/models/tag.rb15
-rw-r--r--app/serializers/activitypub/note_serializer.rb2
-rw-r--r--app/services/fan_out_on_write_service.rb2
-rw-r--r--app/services/hashtag_query_service.rb12
-rw-r--r--app/services/process_hashtags_service.rb19
-rw-r--r--db/migrate/20190505072439_add_private_to_tags.rb8
10 files changed, 80 insertions, 20 deletions
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index c4cf4a77b..5eedbe8c8 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -84,7 +84,9 @@ class AccountsController < ApplicationController
     tag = Tag.find_normalized(params[:tag])
 
     if tag
-      Status.tagged_with(tag.id)
+      return Status.none if !user_signed_in && (tag.local || tag.private) || tag.private && current_account.id != @account.id
+      scope = tag.private ? current_account.statuses : tag.local ? Status.local : Status
+      scope.tagged_with(tag.id)
     else
       Status.none
     end
diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb
index 8a216ce0d..11661ce08 100644
--- a/app/controllers/api/v1/accounts/statuses_controller.rb
+++ b/app/controllers/api/v1/accounts/statuses_controller.rb
@@ -72,7 +72,9 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
     tag = Tag.find_normalized(params[:tagged])
 
     if tag
-      Status.tagged_with(tag.id)
+      return Status.none if !user_signed_in && (tag.local || tag.private) || tag.private && current_account.id != @account.id
+      scope = tag.private ? current_account.statuses : tag.local ? Status.local : Status
+      scope.tagged_with(tag.id)
     else
       Status.none
     end
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index 62b8986a4..d52ec0e86 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -121,7 +121,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
   def attach_tags(status)
     @tags.each do |tag|
       status.tags << tag
-      TrendingTags.record_use!(tag, status.account, status.created_at) if status.public_visibility?
+      TrendingTags.record_use!(tag, status.account, status.created_at) if status.distributable?
     end
 
     @mentions.each do |mention|
@@ -148,6 +148,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     return if tag['name'].blank?
 
     hashtag = tag['name'].gsub(/\A#/, '').mb_chars.downcase
+
+    return if hashtag.starts_with?('self:', '_self:', 'local:', '_local:')
+
     hashtag = Tag.where(name: hashtag).first_or_create!(name: hashtag)
 
     return if @tags.include?(hashtag)
diff --git a/app/models/status.rb b/app/models/status.rb
index 0baa5c98b..6c16046a5 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -379,8 +379,8 @@ class Status < ApplicationRecord
       apply_timeline_filters(query, account, local_only)
     end
 
-    def as_tag_timeline(tag, account = nil, local_only = false)
-      query = browsable_timeline_scope(local_only).tagged_with(tag)
+    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)
     end
 
@@ -465,9 +465,28 @@ class Status < ApplicationRecord
 
     def browsable_timeline_scope(local_only = false)
       starting_scope = local_only ? Status.network : Status
-      starting_scope
-        .public_browsable
-        .without_reblogs
+      starting_scope = starting_scope.public_browsable
+      if Setting.show_reblogs_in_public_timelines
+        starting_scope
+      else
+        starting_scope.without_reblogs
+      end
+    end
+
+    def tag_timeline_scope(account = nil, local_only = false, priv = false)
+      if priv
+        return Status.none if account.nil?
+        starting_scope = account.statuses
+      else
+        starting_scope = local_only ? Status.network : Status
+        starting_scope = scope.public_browsable
+      end
+
+      if Setting.show_reblogs_in_public_timelines
+        starting_scope
+      else
+        starting_scope.without_reblogs
+      end
     end
 
     def apply_timeline_filters(query, account, local_only)
diff --git a/app/models/tag.rb b/app/models/tag.rb
index ecfe4f7e8..04b902885 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -7,6 +7,8 @@
 #  name       :string           default(""), not null
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
+#  local      :boolean          default(FALSE), not null
+#  private    :boolean          default(FALSE), not null
 #
 
 class Tag < ApplicationRecord
@@ -17,7 +19,7 @@ class Tag < ApplicationRecord
   has_many :featured_tags, dependent: :destroy, inverse_of: :tag
   has_one :account_tag_stat, dependent: :destroy
 
-  HASHTAG_NAME_RE = '[[:word:]_\-]*[[:alpha:]_·\-][[:word:]_\-]*'
+  HASHTAG_NAME_RE = '[[:word:]:_\-]*[[:alpha:]:_·\-][[:word:]:_\-]*'
   HASHTAG_RE = /(?:^|[^\/\)\w])#(#{HASHTAG_NAME_RE})/i
 
   validates :name, presence: true, uniqueness: true, format: { with: /\A#{HASHTAG_NAME_RE}\z/i }
@@ -26,6 +28,11 @@ 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_private, -> { where(private: true) }
+  scope :only_public, -> { where(private: false) }
+
   delegate :accounts_count,
            :accounts_count=,
            :increment_count!,
@@ -33,6 +40,7 @@ class Tag < ApplicationRecord
            :hidden?,
            to: :account_tag_stat
 
+  before_create :set_scope
   after_save :save_account_tag_stat
 
   def account_tag_stat
@@ -88,4 +96,9 @@ class Tag < ApplicationRecord
     return unless account_tag_stat&.changed?
     account_tag_stat.save
   end
+
+  def set_scope
+    self.private = true if name.starts_with?('self', '_self')
+    self.local = true if self.private || name.starts_with?('local', '_local')
+  end
 end
diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb
index 0fe7b0524..d05c9c4f8 100644
--- a/app/serializers/activitypub/note_serializer.rb
+++ b/app/serializers/activitypub/note_serializer.rb
@@ -99,7 +99,7 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
   end
 
   def virtual_tags
-    object.active_mentions.to_a.sort_by(&:id) + object.tags + object.emojis
+    object.active_mentions.to_a.sort_by(&:id) + object.tags.reject { |t| t.local || t.private } + object.emojis
   end
 
   def atom_uri
diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb
index 2efd51445..4db3d4cf4 100644
--- a/app/services/fan_out_on_write_service.rb
+++ b/app/services/fan_out_on_write_service.rb
@@ -93,7 +93,7 @@ class FanOutOnWriteService < BaseService
   def deliver_to_hashtags(status)
     Rails.logger.debug "Delivering status #{status.id} to hashtags"
 
-    status.tags.pluck(:name).each do |hashtag|
+    status.tags.reject { |t| t.private }.pluck(:name).each do |hashtag|
       Redis.current.publish("timeline:hashtag:#{hashtag}", @payload)
       Redis.current.publish("timeline:hashtag:#{hashtag}:local", @payload) if status.local?
     end
diff --git a/app/services/hashtag_query_service.rb b/app/services/hashtag_query_service.rb
index 5773d78c6..5a1464805 100644
--- a/app/services/hashtag_query_service.rb
+++ b/app/services/hashtag_query_service.rb
@@ -1,13 +1,19 @@
 # frozen_string_literal: true
 
 class HashtagQueryService < BaseService
-  def call(tag, params, account = nil, local = false)
-    tags = tags_for(Array(tag.name) | Array(params[:any])).pluck(:id)
+  def call(tag, params, account = nil, local = false, priv = false)
+    tags = tags_for(Array(tag.name) | Array(params[:any]))
     all  = tags_for(params[:all])
     none = tags_for(params[:none])
 
+    all_tags = Array(tags) | Array(all) | Array(none)
+    local = all_tags.any? { |t| t.local } unless local
+    priv = all_tags.any? { |t| t.private } unless priv
+
+    tags = tags.pluck(:id)
+
     Status.distinct
-          .as_tag_timeline(tags, account, local)
+          .as_tag_timeline(tags, account, local, priv)
           .tagged_with_all(all)
           .tagged_with_none(none)
   end
diff --git a/app/services/process_hashtags_service.rb b/app/services/process_hashtags_service.rb
index fb89f6c5b..4257d9231 100644
--- a/app/services/process_hashtags_service.rb
+++ b/app/services/process_hashtags_service.rb
@@ -2,19 +2,26 @@
 
 class ProcessHashtagsService < BaseService
   def call(status, tags = [])
-    tags    = Extractor.extract_hashtags(status.text) if status.network?
+    tags    = Extractor.extract_hashtags(status.text) if tags.blank? && status.local?
     records = []
 
     tags.map { |str| str.mb_chars.downcase }.uniq(&:to_s).each do |name|
-      tag = Tag.where(name: name).first_or_create(name: name)
+      component_indices = name.size.times.select {|i| name[i] == ':'}
+      component_indices << name.size - 1
+      component_indices.each do |i|
+        frag = name[0..i]
+        tag = Tag.where(name: frag).first_or_create(name: frag)
 
-      status.tags << tag
-      records << tag
+        status.tags << tag
 
-      TrendingTags.record_use!(tag, status.account, status.created_at) if status.distributable?
+        next if tag.local || tag.private
+
+        records << tag
+        TrendingTags.record_use!(tag, status.account, status.created_at) if status.distributable?
+      end
     end
 
-    return unless status.public_visibility? || status.unlisted_visibility?
+    return unless status.distributable?
 
     status.account.featured_tags.where(tag_id: records.map(&:id)).each do |featured_tag|
       featured_tag.increment(status.created_at)
diff --git a/db/migrate/20190505072439_add_private_to_tags.rb b/db/migrate/20190505072439_add_private_to_tags.rb
new file mode 100644
index 000000000..b9a29fe9e
--- /dev/null
+++ b/db/migrate/20190505072439_add_private_to_tags.rb
@@ -0,0 +1,8 @@
+class AddPrivateToTags < ActiveRecord::Migration[5.2]
+  def change
+    safety_assured {
+      add_column :tags, :local, :boolean, default: false, null: false
+      add_column :tags, :private, :boolean, default: false, null: false
+    }
+  end
+end