local visibility scope, chat scope+tags, unlisted tags

master
multiple creatures 2019-07-15 13:34:05 -05:00
parent 0a5eba734e
commit cf3ec71aa5
28 changed files with 297 additions and 80 deletions

View File

@ -23,7 +23,7 @@ class HomeController < ApplicationController
when 'statuses'
status = Status.find_by(id: matches[2])
if status && (status.public_visibility? || status.unlisted_visibility?)
if status && status.distributable?
redirect_to(ActivityPub::TagManager.instance.url_for(status))
return
end

View File

@ -202,6 +202,10 @@ module StreamEntriesHelper
fa_icon 'globe fw'
when 'unlisted'
fa_icon 'unlock fw'
when 'local'
fa_icon 'users fw'
when 'chat'
fa_icon 'paper-plane fw'
when 'private'
fa_icon 'lock fw'
when 'direct'

View File

@ -116,6 +116,10 @@ function statusToTextMentions(state, status) {
set = set.add(`@${status.getIn(['account', 'acct'])} `);
}
set = set.union(status.get('tags').filter(
tag => tag.get('name') && tag.get('name').startsWith("chat.")
).map(tag => `#${tag.get('name')} `));
return set.union(status.get('mentions').filterNot(mention => mention.get('id') === me).map(mention => `@${mention.get('acct')} `)).join('');
};
@ -126,6 +130,10 @@ function apiStatusToTextMentions (state, status) {
set = set.add(`@${status.account.acct} `);
}
set = set.union(status.tags.filter(
tag => tag.name && tag.name.startsWith("chat.")
).map(tag => `#${tag.name} `));
return set.union(status.mentions.filter(
mention => mention.id !== me
).map(

View File

@ -153,7 +153,9 @@ 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.distributable?
tag.chatters.find_or_create_by(account_id: status.account) if tag.chat?
next unless status.distributable? && !tag.chat?
TrendingTags.record_use!(tag, status.account, status.created_at)
end
@mentions.each do |mention|
@ -181,7 +183,15 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
hashtag = tag['name'].gsub(/\A#/, '').gsub(':', '.').mb_chars.downcase
return if !@options[:imported] && hashtag.starts_with?('self.', '_self.', 'local.', '_local.')
return if !@options[:imported] && (
hashtag.in?(%w(self .self local .local chat.local .chat.local)) ||
hashtag.starts_with?('self.', '.self', 'local.', '.local', 'chat.local.', '.chat.local.')
)
if tag['name'].starts_with?('chat.', '.chat.')
@params[:visibility] = :chat
@params[:thread] = nil
end
hashtag = Tag.where(name: hashtag).first_or_create!(name: hashtag)

View File

@ -62,9 +62,9 @@ class ActivityPub::TagManager
case status.visibility
when 'public'
[COLLECTIONS[:public]]
when 'unlisted', 'private'
when 'unlisted', 'private', 'local'
[account_followers_url(status.account)]
when 'direct', 'limited'
when 'direct', 'limited', 'chat'
if status.account.silenced?
# Only notify followers if the account is locally silenced
account_ids = status.active_mentions.pluck(:account_id)
@ -89,11 +89,11 @@ class ActivityPub::TagManager
case status.visibility
when 'public'
cc << account_followers_url(status.account)
when 'unlisted'
when 'unlisted', 'local'
cc << COLLECTIONS[:public]
end
unless status.direct_visibility? || status.limited_visibility?
unless status.direct_visibility? || status.limited_visibility? || status.chat_visibility?
if status.account.silenced?
# Only notify followers if the account is locally silenced
account_ids = status.active_mentions.pluck(:account_id)

View File

@ -452,8 +452,9 @@ class Bangtags
'group' => :private,
'unlisted' => :unlisted,
'local' => :unlisted,
'monsterpit' => :unlisted,
'local' => :local,
'monsterpit' => :local,
'public' => :public,
'world' => :public,
@ -552,9 +553,25 @@ class Bangtags
end
def add_tags(to_status, *tags)
records = []
valid_name = /^[[:word:]:_\-]*[[:alpha:]:_·\-][[:word:]:_\-]*$/
valid_name = /^[[:word:]:._\-]*[[:alpha:]:._·\-][[:word:]:._\-]*$/
tags = tags.select {|t| t.present? && valid_name.match?(t)}.uniq
ProcessHashtagsService.new.call(to_status, tags)
to_status.save
end
def del_tags(from_status, *tags)
valid_name = /^[[:word:]:._\-]*[[:alpha:]:._·\-][[:word:]:._\-]*$/
tags = tags.select {|t| t.present? && valid_name.match?(t)}.uniq
tags.map { |str| str.mb_chars.downcase }.uniq(&:to_s).each do |name|
name.gsub!(/[:.]+/, '.')
next if name.blank? || name == '.'
if name.ends_with?('.')
filtered_tags = from_status.tags.select { |t| t.name == name || t.name.starts_with?(name) }
else
filtered_tags = from_status.tags.select { |t| t.name == name }
end
from_status.tags.destroy(filtered_tags)
end
from_status.save
end
end

View File

@ -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?

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -14,7 +14,7 @@ class StatusRelationshipsPresenter
statuses = statuses.compact
status_ids = statuses.flat_map { |s| [s.id, s.reblog_of_id] }.uniq.compact
conversation_ids = statuses.map(&:conversation_id).compact.uniq
pinnable_status_ids = statuses.map(&:proper).select { |s| s.account_id == current_account_id && %w(public unlisted private).include?(s.visibility) }.map(&:id)
pinnable_status_ids = statuses.map(&:proper).select { |s| s.account_id == current_account_id && %w(public unlisted local private).include?(s.visibility) }.map(&:id)
@reblogs_map = Status.reblogs_map(status_ids, current_account_id).merge(options[:reblogs_map] || {})
@favourites_map = Status.favourites_map(status_ids, current_account_id).merge(options[:favourites_map] || {})

View File

@ -55,11 +55,10 @@ class REST::StatusSerializer < ActiveModel::Serializer
end
def visibility
# This visibility is masked behind "private"
# to avoid API changes because there are no
# UX differences
if object.limited_visibility?
'private'
elsif object.local_visibility? || object.chat_visibility?
'unlisted'
else
object.visibility
end
@ -121,7 +120,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
current_user? &&
current_user.account_id == object.account_id &&
!object.reblog? &&
%w(public unlisted private).include?(object.visibility)
%w(public unlisted local private).include?(object.visibility)
end
def source_requested?

View File

@ -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)
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.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
deliver_to_local(status)
end
return if status.reblog? && !Setting.show_reblogs_in_public_timelines
return if filtered?(status)
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
return unless status.network? && status.public_visibility? && !status.reblog
deliver_to_local(status)
end
private

View File

@ -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]
set_footer_from_i_am
extract_tags
set_chat
set_local_only
@visibility = @options[:visibility] || @account.user_default_visibility
@visibility = :unlisted if @visibility.in?([nil, 'public']) && @account.silenced? || @account.force_unlisted
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

View File

@ -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?

View File

@ -6,7 +6,7 @@ class StatusPinValidator < ActiveModel::Validator
def validate(pin)
pin.errors.add(:base, I18n.t('statuses.pin_errors.reblog')) if pin.status.reblog?
pin.errors.add(:base, I18n.t('statuses.pin_errors.ownership')) if pin.account_id != pin.status.account_id
pin.errors.add(:base, I18n.t('statuses.pin_errors.private')) unless %w(public unlisted private).include?(pin.status.visibility)
pin.errors.add(:base, I18n.t('statuses.pin_errors.private')) unless %w(public unlisted local private).include?(pin.status.visibility)
pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count >= MAX_PINNED && pin.account.local?
end
end

View File

@ -35,7 +35,7 @@ class ActivityPub::DistributePollUpdateWorker
end
end
@inboxes.concat(@account.followers.inboxes) unless @status.direct_visibility?
@inboxes.concat(@account.followers.inboxes) unless @status.direct_visibility? || @status.chat_visibility?
@inboxes.uniq!
@inboxes.compact!
@inboxes

View File

@ -23,7 +23,7 @@ class ActivityPub::DistributionWorker
private
def skip_distribution?
@status.direct_visibility? || @status.limited_visibility?
@status.direct_visibility? || @status.limited_visibility? || @status.chat_visibility?
end
def relayable?

View File

@ -0,0 +1,6 @@
class AddChatAndLocalIndexesToStatuses < ActiveRecord::Migration[5.2]
disable_ddl_transaction!
def change
add_index :statuses, [:account_id, :id, :visibility], where: 'visibility IN (0, 1, 2, 4, 5)', algorithm: :concurrently
end
end

View File

@ -0,0 +1,9 @@
class AddUnlistedToTags < ActiveRecord::Migration[5.2]
disable_ddl_transaction!
def change
safety_assured {
add_column :tags, :unlisted, :boolean, default: false, null: false
add_index :tags, :unlisted, where: :unlisted, algorithm: :concurrently
}
end
end

View File

@ -0,0 +1,5 @@
class MakePrivateTagsUnlisted < ActiveRecord::Migration[5.2]
def up
Tag.where(private: true).in_batches.update_all(unlisted: true)
end
end

View File

@ -0,0 +1,9 @@
class AddChatToTags < ActiveRecord::Migration[5.2]
disable_ddl_transaction!
def change
safety_assured {
add_column :tags, :chat, :boolean, default: false, null: false
add_index :tags, :chat, where: :chat, algorithm: :concurrently
}
end
end

View File

@ -0,0 +1,11 @@
class CreateChatAccounts < ActiveRecord::Migration[5.2]
def change
create_table :chat_accounts do |t|
t.references :account, foreign_key: { on_delete: :cascade }, null: false
t.references :tag, foreign_key: { on_delete: :cascade }, null: false
t.index [:account_id, :tag_id], unique: true
t.index [:tag_id, :account_id]
t.timestamps
end
end
end

View File

@ -208,6 +208,17 @@ ActiveRecord::Schema.define(version: 2019_05_21_003909) do
t.index ["status_id"], name: "index_bookmarks_on_status_id"
end
create_table "chat_accounts", force: :cascade do |t|
t.bigint "account_id", null: false
t.bigint "tag_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id", "tag_id"], name: "index_chat_accounts_on_account_id_and_tag_id", unique: true
t.index ["account_id"], name: "index_chat_accounts_on_account_id"
t.index ["tag_id", "account_id"], name: "index_chat_accounts_on_tag_id_and_account_id"
t.index ["tag_id"], name: "index_chat_accounts_on_tag_id"
end
create_table "conversation_mutes", force: :cascade do |t|
t.bigint "conversation_id", null: false
t.bigint "account_id", null: false
@ -687,8 +698,12 @@ ActiveRecord::Schema.define(version: 2019_05_21_003909) do
t.datetime "updated_at", null: false
t.boolean "local", default: false, null: false
t.boolean "private", default: false, null: false
t.boolean "unlisted", default: false, null: false
t.boolean "chat", default: false, null: false
t.index "lower((name)::text) text_pattern_ops", name: "hashtag_search_index"
t.index ["chat"], name: "index_tags_on_chat", where: "chat"
t.index ["name"], name: "index_tags_on_name", unique: true
t.index ["unlisted"], name: "index_tags_on_unlisted", where: "unlisted"
end
create_table "tombstones", force: :cascade do |t|
@ -791,6 +806,8 @@ ActiveRecord::Schema.define(version: 2019_05_21_003909) do
add_foreign_key "blocks", "accounts", name: "fk_4269e03e65", on_delete: :cascade
add_foreign_key "bookmarks", "accounts", on_delete: :cascade
add_foreign_key "bookmarks", "statuses", on_delete: :cascade
add_foreign_key "chat_accounts", "accounts", on_delete: :cascade
add_foreign_key "chat_accounts", "tags", on_delete: :cascade
add_foreign_key "conversation_mutes", "accounts", name: "fk_225b4212bb", on_delete: :cascade
add_foreign_key "conversation_mutes", "conversations", on_delete: :cascade
add_foreign_key "custom_filters", "accounts", on_delete: :cascade

View File

@ -0,0 +1,2 @@
Fabricator(:chat_account) do
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe ChatAccount, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end