Merge branch 'kb_development' into kb_lts
This commit is contained in:
commit
d78281fb1d
22 changed files with 412 additions and 38 deletions
|
@ -36,7 +36,7 @@ class Api::V1::AccountsController < Api::BaseController
|
||||||
|
|
||||||
def follow
|
def follow
|
||||||
follow = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, languages: params.key?(:languages) ? params[:languages] : nil, with_rate_limit: true)
|
follow = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, languages: params.key?(:languages) ? params[:languages] : nil, with_rate_limit: true)
|
||||||
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: follow.show_reblogs?, notify: follow.notify?, languages: follow.languages } }, requested_map: { @account.id => false } }
|
options = @account.locked? || current_user.account.silenced? || (current_user.account.bot? && @account.user&.setting_lock_follow_from_bot) ? {} : { following_map: { @account.id => { reblogs: follow.show_reblogs?, notify: follow.notify?, languages: follow.languages } }, requested_map: { @account.id => false } }
|
||||||
|
|
||||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(**options)
|
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(**options)
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,6 +29,7 @@ module ContextHelper
|
||||||
limited_scope: { 'kmyblue' => 'http://kmy.blue/ns#', 'limitedScope' => { '@id' => 'kmyblue:limitedScope', '@type' => '@id' } },
|
limited_scope: { 'kmyblue' => 'http://kmy.blue/ns#', 'limitedScope' => { '@id' => 'kmyblue:limitedScope', '@type' => '@id' } },
|
||||||
other_setting: { 'fedibird' => 'http://fedibird.com/ns#', 'otherSetting' => 'fedibird:otherSetting' },
|
other_setting: { 'fedibird' => 'http://fedibird.com/ns#', 'otherSetting' => 'fedibird:otherSetting' },
|
||||||
references: { 'fedibird' => 'http://fedibird.com/ns#', 'references' => { '@id' => 'fedibird:references', '@type' => '@id' } },
|
references: { 'fedibird' => 'http://fedibird.com/ns#', 'references' => { '@id' => 'fedibird:references', '@type' => '@id' } },
|
||||||
|
quote_uri: { 'fedibird' => 'http://fedibird.com/ns#', 'quoteUri' => 'fedibird:quoteUri' },
|
||||||
olm: {
|
olm: {
|
||||||
'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId',
|
'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId',
|
||||||
'claim' => { '@type' => '@id', '@id' => 'toot:claim' },
|
'claim' => { '@type' => '@id', '@id' => 'toot:claim' },
|
||||||
|
|
|
@ -30,7 +30,7 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
|
||||||
|
|
||||||
follow_request = FollowRequest.create!(account: @account, target_account: target_account, uri: @json['id'])
|
follow_request = FollowRequest.create!(account: @account, target_account: target_account, uri: @json['id'])
|
||||||
|
|
||||||
if target_account.locked? || @account.silenced? || block_straight_follow?
|
if target_account.locked? || @account.silenced? || block_straight_follow? || (@account.bot? && target_account.user&.setting_lock_follow_from_bot)
|
||||||
LocalNotificationWorker.perform_async(target_account.id, follow_request.id, 'FollowRequest', 'follow_request')
|
LocalNotificationWorker.perform_async(target_account.id, follow_request.id, 'FollowRequest', 'follow_request')
|
||||||
else
|
else
|
||||||
AuthorizeFollowService.new.call(@account, target_account)
|
AuthorizeFollowService.new.call(@account, target_account)
|
||||||
|
|
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
module ActivityPub::CaseTransform
|
module ActivityPub::CaseTransform
|
||||||
class << self
|
class << self
|
||||||
|
NO_CONVERT_VALUES = %w(
|
||||||
|
_misskey_content
|
||||||
|
_misskey_quote
|
||||||
|
).freeze
|
||||||
|
|
||||||
def camel_lower_cache
|
def camel_lower_cache
|
||||||
@camel_lower_cache ||= {}
|
@camel_lower_cache ||= {}
|
||||||
end
|
end
|
||||||
|
@ -12,7 +17,9 @@ module ActivityPub::CaseTransform
|
||||||
when Hash then value.deep_transform_keys! { |key| camel_lower(key) }
|
when Hash then value.deep_transform_keys! { |key| camel_lower(key) }
|
||||||
when Symbol then camel_lower(value.to_s).to_sym
|
when Symbol then camel_lower(value.to_s).to_sym
|
||||||
when String
|
when String
|
||||||
camel_lower_cache[value] ||= if value.start_with?('_:')
|
camel_lower_cache[value] ||= if NO_CONVERT_VALUES.include?(value)
|
||||||
|
value
|
||||||
|
elsif value.start_with?('_:')
|
||||||
"_:#{value.delete_prefix('_:').underscore.camelize(:lower)}"
|
"_:#{value.delete_prefix('_:').underscore.camelize(:lower)}"
|
||||||
else
|
else
|
||||||
value.underscore.camelize(:lower)
|
value.underscore.camelize(:lower)
|
||||||
|
|
|
@ -127,6 +127,10 @@ module HasUserSettings
|
||||||
settings['link_preview']
|
settings['link_preview']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def setting_single_ref_to_quote
|
||||||
|
settings['single_ref_to_quote']
|
||||||
|
end
|
||||||
|
|
||||||
def setting_dtl_force_with_tag
|
def setting_dtl_force_with_tag
|
||||||
settings['dtl_force_with_tag']&.to_sym || :none
|
settings['dtl_force_with_tag']&.to_sym || :none
|
||||||
end
|
end
|
||||||
|
@ -235,6 +239,10 @@ module HasUserSettings
|
||||||
settings['disallow_unlisted_public_searchability']
|
settings['disallow_unlisted_public_searchability']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def setting_lock_follow_from_bot
|
||||||
|
settings['lock_follow_from_bot']
|
||||||
|
end
|
||||||
|
|
||||||
def allows_report_emails?
|
def allows_report_emails?
|
||||||
settings['notification_emails.report']
|
settings['notification_emails.report']
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,15 +17,10 @@ class StatusReference < ApplicationRecord
|
||||||
|
|
||||||
has_one :notification, as: :activity, dependent: :destroy
|
has_one :notification, as: :activity, dependent: :destroy
|
||||||
|
|
||||||
validate :validate_status_visibilities
|
|
||||||
after_commit :reset_parent_cache
|
after_commit :reset_parent_cache
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def validate_status_visibilities
|
|
||||||
raise Mastodon::ValidationError, I18n.t('status_references.errors.invalid_status_visibilities') if [:public, :public_unlisted, :unlisted, :login].exclude?(target_status.visibility.to_sym)
|
|
||||||
end
|
|
||||||
|
|
||||||
def reset_parent_cache
|
def reset_parent_cache
|
||||||
Rails.cache.delete("statuses/#{status_id}")
|
Rails.cache.delete("statuses/#{status_id}")
|
||||||
Rails.cache.delete("statuses/#{target_status_id}")
|
Rails.cache.delete("statuses/#{target_status_id}")
|
||||||
|
|
|
@ -40,6 +40,8 @@ class UserSettings
|
||||||
setting :unsafe_limited_distribution, default: false
|
setting :unsafe_limited_distribution, default: false
|
||||||
setting :dtl_force_with_tag, default: :none, in: %w(full searchability none)
|
setting :dtl_force_with_tag, default: :none, in: %w(full searchability none)
|
||||||
setting :dtl_force_subscribable, default: false
|
setting :dtl_force_subscribable, default: false
|
||||||
|
setting :lock_follow_from_bot, default: false
|
||||||
|
setting :single_ref_to_quote, default: false
|
||||||
|
|
||||||
setting_inverse_alias :indexable, :noindex
|
setting_inverse_alias :indexable, :noindex
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
||||||
include FormattingHelper
|
include FormattingHelper
|
||||||
|
|
||||||
context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :searchable_by, :references, :limited_scope
|
context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :searchable_by, :references, :limited_scope, :quote_uri
|
||||||
|
|
||||||
attributes :id, :type, :summary,
|
attributes :id, :type, :summary,
|
||||||
:in_reply_to, :published, :url,
|
:in_reply_to, :published, :url,
|
||||||
|
@ -11,16 +11,19 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
||||||
:atom_uri, :in_reply_to_atom_uri,
|
:atom_uri, :in_reply_to_atom_uri,
|
||||||
:conversation, :searchable_by, :limited_scope
|
:conversation, :searchable_by, :limited_scope
|
||||||
|
|
||||||
attribute :references, if: :not_private_post?
|
|
||||||
|
|
||||||
attribute :content
|
attribute :content
|
||||||
attribute :content_map, if: :language?
|
attribute :content_map, if: :language?
|
||||||
attribute :updated, if: :edited?
|
attribute :updated, if: :edited?
|
||||||
|
|
||||||
|
attribute :quote_uri, if: :quote?
|
||||||
|
attribute :misskey_quote, key: :_misskey_quote, if: :quote?
|
||||||
|
attribute :misskey_content, key: :_misskey_content, if: :quote?
|
||||||
|
|
||||||
has_many :virtual_attachments, key: :attachment
|
has_many :virtual_attachments, key: :attachment
|
||||||
has_many :virtual_tags, key: :tag
|
has_many :virtual_tags, key: :tag
|
||||||
|
|
||||||
has_one :replies, serializer: ActivityPub::CollectionSerializer, if: :local?
|
has_one :replies, serializer: ActivityPub::CollectionSerializer, if: :local?
|
||||||
|
has_one :references, serializer: ActivityPub::CollectionSerializer
|
||||||
|
|
||||||
has_many :poll_options, key: :one_of, if: :poll_and_not_multiple?
|
has_many :poll_options, key: :one_of, if: :poll_and_not_multiple?
|
||||||
has_many :poll_options, key: :any_of, if: :poll_and_multiple?
|
has_many :poll_options, key: :any_of, if: :poll_and_multiple?
|
||||||
|
@ -67,7 +70,19 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
||||||
end
|
end
|
||||||
|
|
||||||
def references
|
def references
|
||||||
ActivityPub::TagManager.instance.references_uri_for(object)
|
refs = object.references.reorder(id: :asc).take(5).pluck(:id, :uri)
|
||||||
|
last_id = refs.last&.first
|
||||||
|
|
||||||
|
ActivityPub::CollectionPresenter.new(
|
||||||
|
type: :unordered,
|
||||||
|
id: ActivityPub::TagManager.instance.references_uri_for(object),
|
||||||
|
first: ActivityPub::CollectionPresenter.new(
|
||||||
|
type: :unordered,
|
||||||
|
part_of: ActivityPub::TagManager.instance.references_uri_for(object),
|
||||||
|
items: refs.map(&:second),
|
||||||
|
next: last_id ? ActivityPub::TagManager.instance.references_uri_for(object, page: true, min_id: last_id) : ActivityPub::TagManager.instance.references_uri_for(object, page: true, only_other_accounts: true)
|
||||||
|
)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def language?
|
def language?
|
||||||
|
@ -156,8 +171,20 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
||||||
object.account.local?
|
object.account.local?
|
||||||
end
|
end
|
||||||
|
|
||||||
def not_private_post?
|
def quote?
|
||||||
!object.private_visibility? && !object.direct_visibility? && !object.limited_visibility?
|
object.references.count == 1 && object.account.user&.settings&.[]('single_ref_to_quote')
|
||||||
|
end
|
||||||
|
|
||||||
|
def quote_uri
|
||||||
|
ActivityPub::TagManager.instance.uri_for(object.references.first)
|
||||||
|
end
|
||||||
|
|
||||||
|
def misskey_quote
|
||||||
|
quote_uri
|
||||||
|
end
|
||||||
|
|
||||||
|
def misskey_content
|
||||||
|
object.text
|
||||||
end
|
end
|
||||||
|
|
||||||
def poll_options
|
def poll_options
|
||||||
|
|
|
@ -36,7 +36,7 @@ class FollowService < BaseService
|
||||||
# and the feeds are being merged
|
# and the feeds are being merged
|
||||||
mark_home_feed_as_partial! if @source_account.not_following_anyone?
|
mark_home_feed_as_partial! if @source_account.not_following_anyone?
|
||||||
|
|
||||||
if (@target_account.locked? && !@options[:bypass_locked]) || @source_account.silenced? || @target_account.activitypub?
|
if (@target_account.locked? && !@options[:bypass_locked]) || @source_account.silenced? || @target_account.activitypub? || (@source_account.bot? && @target_account.user&.setting_lock_follow_from_bot)
|
||||||
request_follow!
|
request_follow!
|
||||||
elsif @target_account.local?
|
elsif @target_account.local?
|
||||||
direct_follow!
|
direct_follow!
|
||||||
|
|
|
@ -9,6 +9,7 @@ class NotifyService < BaseService
|
||||||
update
|
update
|
||||||
poll
|
poll
|
||||||
emoji_reaction
|
emoji_reaction
|
||||||
|
status_reference
|
||||||
warning
|
warning
|
||||||
).freeze
|
).freeze
|
||||||
|
|
||||||
|
|
|
@ -188,7 +188,7 @@ class PostStatusService < BaseService
|
||||||
|
|
||||||
process_hashtags_service.call(@status)
|
process_hashtags_service.call(@status)
|
||||||
Trends.tags.register(@status)
|
Trends.tags.register(@status)
|
||||||
ProcessReferencesService.perform_worker_async(@status, @reference_ids, [])
|
ProcessReferencesService.call_service(@status, @reference_ids, [])
|
||||||
LinkCrawlWorker.perform_async(@status.id)
|
LinkCrawlWorker.perform_async(@status.id)
|
||||||
DistributionWorker.perform_async(@status.id)
|
DistributionWorker.perform_async(@status.id)
|
||||||
ActivityPub::DistributionWorker.perform_async(@status.id)
|
ActivityPub::DistributionWorker.perform_async(@status.id)
|
||||||
|
|
|
@ -3,20 +3,25 @@
|
||||||
class ProcessReferencesService < BaseService
|
class ProcessReferencesService < BaseService
|
||||||
include Payloadable
|
include Payloadable
|
||||||
include FormattingHelper
|
include FormattingHelper
|
||||||
|
include Redisable
|
||||||
|
include Lockable
|
||||||
|
|
||||||
DOMAIN = ENV['WEB_DOMAIN'] || ENV.fetch('LOCAL_DOMAIN', nil)
|
DOMAIN = ENV['WEB_DOMAIN'] || ENV.fetch('LOCAL_DOMAIN', nil)
|
||||||
REFURL_EXP = /(RT|QT|BT|RN|RE)((:|;)?\s+|:|;)(#{URI::DEFAULT_PARSER.make_regexp(%w(http https))})/
|
REFURL_EXP = /(RT|QT|BT|RN|RE)((:|;)?\s+|:|;)(#{URI::DEFAULT_PARSER.make_regexp(%w(http https))})/
|
||||||
MAX_REFERENCES = 5
|
MAX_REFERENCES = 5
|
||||||
|
|
||||||
def call(status, reference_parameters, urls: nil)
|
def call(status, reference_parameters, urls: nil, fetch_remote: true, no_fetch_urls: nil)
|
||||||
@status = status
|
@status = status
|
||||||
@reference_parameters = reference_parameters || []
|
@reference_parameters = reference_parameters || []
|
||||||
@urls = urls || []
|
@urls = urls || []
|
||||||
|
@no_fetch_urls = no_fetch_urls || []
|
||||||
|
@fetch_remote = fetch_remote
|
||||||
|
@again = false
|
||||||
|
|
||||||
|
with_redis_lock("process_status_refs:#{@status.id}") do
|
||||||
@references_count = old_references.size
|
@references_count = old_references.size
|
||||||
|
|
||||||
return unless added_references.size.positive? || removed_references.size.positive?
|
if added_references.size.positive? || removed_references.size.positive?
|
||||||
|
|
||||||
StatusReference.transaction do
|
StatusReference.transaction do
|
||||||
remove_old_references
|
remove_old_references
|
||||||
add_references
|
add_references
|
||||||
|
@ -24,11 +29,15 @@ class ProcessReferencesService < BaseService
|
||||||
@status.save!
|
@status.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
Rails.cache.delete("status_reference:#{@status.id}")
|
|
||||||
|
|
||||||
create_notifications!
|
create_notifications!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Rails.cache.delete("status_reference:#{@status.id}")
|
||||||
|
end
|
||||||
|
|
||||||
|
launch_worker if @again
|
||||||
|
end
|
||||||
|
|
||||||
def self.need_process?(status, reference_parameters, urls)
|
def self.need_process?(status, reference_parameters, urls)
|
||||||
reference_parameters.any? || (urls || []).any? || FormattingHelper.extract_status_plain_text(status).scan(REFURL_EXP).pluck(3).uniq.any?
|
reference_parameters.any? || (urls || []).any? || FormattingHelper.extract_status_plain_text(status).scan(REFURL_EXP).pluck(3).uniq.any?
|
||||||
end
|
end
|
||||||
|
@ -37,7 +46,13 @@ class ProcessReferencesService < BaseService
|
||||||
return unless need_process?(status, reference_parameters, urls)
|
return unless need_process?(status, reference_parameters, urls)
|
||||||
|
|
||||||
Rails.cache.write("status_reference:#{status.id}", true, expires_in: 10.minutes)
|
Rails.cache.write("status_reference:#{status.id}", true, expires_in: 10.minutes)
|
||||||
ProcessReferencesWorker.perform_async(status.id, reference_parameters, urls)
|
ProcessReferencesWorker.perform_async(status.id, reference_parameters, urls, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.call_service(status, reference_parameters, urls)
|
||||||
|
return unless need_process?(status, reference_parameters, urls)
|
||||||
|
|
||||||
|
ProcessReferencesService.new.call(status, reference_parameters || [], urls: urls || [], fetch_remote: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -59,14 +74,22 @@ class ProcessReferencesService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def scan_text!
|
def scan_text!
|
||||||
text = @status.account.local? ? @status.text : @status.text.gsub(%r{</?[^>]*>}, '')
|
text = extract_status_plain_text(@status)
|
||||||
@scan_text = fetch_statuses!(text.scan(REFURL_EXP).pluck(3).uniq).map(&:id).uniq.filter { |status_id| !status_id.zero? }
|
statuses = fetch_statuses!(text.scan(REFURL_EXP).pluck(3).uniq)
|
||||||
|
|
||||||
|
@again = true if !@fetch_remote && statuses.any?(&:nil?)
|
||||||
|
|
||||||
|
@scan_text = statuses.compact.map(&:id).uniq.filter { |status_id| !status_id.zero? }
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_statuses!(urls)
|
def fetch_statuses!(urls)
|
||||||
(urls + @urls)
|
target_urls = urls + @urls
|
||||||
.map { |url| ResolveURLService.new.call(url, on_behalf_of: @status.account) }
|
|
||||||
.filter { |status| status }
|
target_urls.map do |url|
|
||||||
|
status = ResolveURLService.new.call(url, on_behalf_of: @status.account, fetch_remote: @fetch_remote && @no_fetch_urls.exclude?(url))
|
||||||
|
@no_fetch_urls << url if !@fetch_remote && status.present?
|
||||||
|
status
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_references
|
def add_references
|
||||||
|
@ -106,4 +129,8 @@ class ProcessReferencesService < BaseService
|
||||||
@references_count -= 1
|
@references_count -= 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def launch_worker
|
||||||
|
ProcessReferencesWorker.perform_async(@status.id, @reference_parameters, @urls, @no_fetch_urls)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,13 +6,13 @@ class ResolveURLService < BaseService
|
||||||
|
|
||||||
USERNAME_STATUS_RE = %r{/@(?<username>#{Account::USERNAME_RE})/(?<status_id>[0-9]+)\Z}
|
USERNAME_STATUS_RE = %r{/@(?<username>#{Account::USERNAME_RE})/(?<status_id>[0-9]+)\Z}
|
||||||
|
|
||||||
def call(url, on_behalf_of: nil)
|
def call(url, on_behalf_of: nil, fetch_remote: true)
|
||||||
@url = url
|
@url = url
|
||||||
@on_behalf_of = on_behalf_of
|
@on_behalf_of = on_behalf_of
|
||||||
|
|
||||||
if local_url?
|
if local_url?
|
||||||
process_local_url
|
process_local_url
|
||||||
elsif !fetched_resource.nil?
|
elsif fetch_remote && !fetched_resource.nil?
|
||||||
process_url
|
process_url
|
||||||
else
|
else
|
||||||
process_url_from_db
|
process_url_from_db
|
||||||
|
|
|
@ -162,7 +162,7 @@ class UpdateStatusService < BaseService
|
||||||
def update_references!
|
def update_references!
|
||||||
reference_ids = (@options[:status_reference_ids] || []).map(&:to_i).filter(&:positive?)
|
reference_ids = (@options[:status_reference_ids] || []).map(&:to_i).filter(&:positive?)
|
||||||
|
|
||||||
ProcessReferencesService.perform_worker_async(@status, reference_ids, [])
|
ProcessReferencesService.call_service(@status, reference_ids, [])
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_metadata!
|
def update_metadata!
|
||||||
|
|
|
@ -11,6 +11,12 @@
|
||||||
.fields-group
|
.fields-group
|
||||||
= ff.input :aggregate_reblogs, wrapper: :with_label, recommended: true, label: I18n.t('simple_form.labels.defaults.setting_aggregate_reblogs'), hint: I18n.t('simple_form.hints.defaults.setting_aggregate_reblogs')
|
= ff.input :aggregate_reblogs, wrapper: :with_label, recommended: true, label: I18n.t('simple_form.labels.defaults.setting_aggregate_reblogs'), hint: I18n.t('simple_form.hints.defaults.setting_aggregate_reblogs')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= ff.input :lock_follow_from_bot, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_lock_follow_from_bot')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= ff.input :single_ref_to_quote, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_single_ref_to_quote'), hint: I18n.t('simple_form.hints.defaults.setting_single_ref_to_quote')
|
||||||
|
|
||||||
%h4= t 'preferences.posting_defaults'
|
%h4= t 'preferences.posting_defaults'
|
||||||
|
|
||||||
.fields-row
|
.fields-row
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
class ProcessReferencesWorker
|
class ProcessReferencesWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
def perform(status_id, ids, urls)
|
def perform(status_id, ids, urls, no_fetch_urls)
|
||||||
ProcessReferencesService.new.call(Status.find(status_id), ids || [], urls: urls || [])
|
ProcessReferencesService.new.call(Status.find(status_id), ids || [], urls: urls || [], no_fetch_urls: no_fetch_urls)
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
|
@ -71,6 +71,7 @@ en:
|
||||||
setting_dtl_menu: Show DTL menu on web
|
setting_dtl_menu: Show DTL menu on web
|
||||||
setting_emoji_reaction_policy: Even with this setting, users on other servers are free to put their stamp on the post and share it within the same server. If you simply want to remove the stamp from your own screen, you can disable it from the appearance settings
|
setting_emoji_reaction_policy: Even with this setting, users on other servers are free to put their stamp on the post and share it within the same server. If you simply want to remove the stamp from your own screen, you can disable it from the appearance settings
|
||||||
setting_enable_emoji_reaction: If turn off, other users still can react your posts
|
setting_enable_emoji_reaction: If turn off, other users still can react your posts
|
||||||
|
setting_single_ref_to_quote: If this server does not have target post, target server maybe cannot read your quote
|
||||||
setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details
|
setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details
|
||||||
setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed
|
setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed
|
||||||
username: You can use letters, numbers, and underscores
|
username: You can use letters, numbers, and underscores
|
||||||
|
@ -262,6 +263,7 @@ en:
|
||||||
setting_hide_recent_emojis: Hide recent emojis
|
setting_hide_recent_emojis: Hide recent emojis
|
||||||
setting_hide_statuses_count: Hide statuses count
|
setting_hide_statuses_count: Hide statuses count
|
||||||
setting_link_preview: Generate post link preview card
|
setting_link_preview: Generate post link preview card
|
||||||
|
setting_lock_follow_from_bot: Request approval about bot follow
|
||||||
setting_noai: Set noai meta tags
|
setting_noai: Set noai meta tags
|
||||||
setting_public_post_to_unlisted: Convert public post to public unlisted if not using Web app
|
setting_public_post_to_unlisted: Convert public post to public unlisted if not using Web app
|
||||||
setting_reduce_motion: Reduce motion in animations
|
setting_reduce_motion: Reduce motion in animations
|
||||||
|
@ -271,6 +273,7 @@ en:
|
||||||
setting_show_application: Disclose application used to send posts
|
setting_show_application: Disclose application used to send posts
|
||||||
setting_show_emoji_reaction_on_timeline: Show all stamps on timeline
|
setting_show_emoji_reaction_on_timeline: Show all stamps on timeline
|
||||||
setting_simple_timeline_menu: Reduce post menu on timeline
|
setting_simple_timeline_menu: Reduce post menu on timeline
|
||||||
|
setting_single_ref_to_quote: Deliver single reference to other server as quote
|
||||||
setting_stay_privacy: Not change privacy after post
|
setting_stay_privacy: Not change privacy after post
|
||||||
setting_stop_emoji_reaction_streaming: Disable stamp streamings
|
setting_stop_emoji_reaction_streaming: Disable stamp streamings
|
||||||
setting_system_font_ui: Use system's default font
|
setting_system_font_ui: Use system's default font
|
||||||
|
|
|
@ -82,6 +82,7 @@ ja:
|
||||||
setting_public_post_to_unlisted: 未対応のサードパーティアプリからもローカル公開で投稿できますが、公開投稿はWeb以外できなくなります
|
setting_public_post_to_unlisted: 未対応のサードパーティアプリからもローカル公開で投稿できますが、公開投稿はWeb以外できなくなります
|
||||||
setting_reject_unlisted_subscription: Misskeyやそのフォーク(Calckeyなど)は、フォローしていないアカウントの「未収載」投稿を **購読・検索** することができます。これはkmyblueの挙動と異なります。そのようなサーバーに、指定した公開範囲の投稿を「フォロワーのみ」として配送します。ただし構造上、完璧な対応は困難でたまに未収載として配信されること、ご理解ください
|
setting_reject_unlisted_subscription: Misskeyやそのフォーク(Calckeyなど)は、フォローしていないアカウントの「未収載」投稿を **購読・検索** することができます。これはkmyblueの挙動と異なります。そのようなサーバーに、指定した公開範囲の投稿を「フォロワーのみ」として配送します。ただし構造上、完璧な対応は困難でたまに未収載として配信されること、ご理解ください
|
||||||
setting_show_application: 投稿するのに使用したアプリが投稿の詳細ビューに表示されるようになります
|
setting_show_application: 投稿するのに使用したアプリが投稿の詳細ビューに表示されるようになります
|
||||||
|
setting_single_ref_to_quote: 当サーバーがまだ対象投稿を取り込んでいない場合、引用が相手に正常に認識されない場合があります
|
||||||
setting_stop_emoji_reaction_streaming: 通信容量の節約に役立ちます
|
setting_stop_emoji_reaction_streaming: 通信容量の節約に役立ちます
|
||||||
setting_unsafe_limited_distribution: Mastodon 3.5、4.0、4.1のサーバーにも限定投稿(相互のみ)が届くようになりますが、安全でない方法で送信します
|
setting_unsafe_limited_distribution: Mastodon 3.5、4.0、4.1のサーバーにも限定投稿(相互のみ)が届くようになりますが、安全でない方法で送信します
|
||||||
setting_use_blurhash: ぼかしはメディアの色を元に生成されますが、細部は見えにくくなっています
|
setting_use_blurhash: ぼかしはメディアの色を元に生成されますが、細部は見えにくくなっています
|
||||||
|
@ -276,6 +277,7 @@ ja:
|
||||||
setting_hide_recent_emojis: 絵文字ピッカーで最近使用した絵文字を隠す(リアクションデッキのみを表示する)
|
setting_hide_recent_emojis: 絵文字ピッカーで最近使用した絵文字を隠す(リアクションデッキのみを表示する)
|
||||||
setting_hide_statuses_count: 投稿数を隠す
|
setting_hide_statuses_count: 投稿数を隠す
|
||||||
setting_link_preview: リンクのプレビューを生成する
|
setting_link_preview: リンクのプレビューを生成する
|
||||||
|
setting_lock_follow_from_bot: botからのフォローを承認制にする
|
||||||
setting_stay_privacy: 投稿時に公開範囲を保存する
|
setting_stay_privacy: 投稿時に公開範囲を保存する
|
||||||
setting_noai: 自分のコンテンツのAI学習利用に対して不快感を表明する
|
setting_noai: 自分のコンテンツのAI学習利用に対して不快感を表明する
|
||||||
setting_public_post_to_unlisted: サードパーティから公開範囲「公開」で投稿した場合、「ローカル公開」に変更する
|
setting_public_post_to_unlisted: サードパーティから公開範囲「公開」で投稿した場合、「ローカル公開」に変更する
|
||||||
|
@ -286,6 +288,7 @@ ja:
|
||||||
setting_show_application: 送信したアプリを開示する
|
setting_show_application: 送信したアプリを開示する
|
||||||
setting_show_emoji_reaction_on_timeline: タイムライン上に他の人のつけたスタンプを表示する
|
setting_show_emoji_reaction_on_timeline: タイムライン上に他の人のつけたスタンプを表示する
|
||||||
setting_simple_timeline_menu: タイムライン上でメニューの項目を減らす
|
setting_simple_timeline_menu: タイムライン上でメニューの項目を減らす
|
||||||
|
setting_single_ref_to_quote: 参照が1つしかない投稿は、他のサーバーには引用として配信する
|
||||||
setting_stay_privacy: 投稿時に公開範囲を保存する
|
setting_stay_privacy: 投稿時に公開範囲を保存する
|
||||||
setting_stop_emoji_reaction_streaming: スタンプのストリーミングを停止する
|
setting_stop_emoji_reaction_streaming: スタンプのストリーミングを停止する
|
||||||
setting_system_font_ui: システムのデフォルトフォントを使う
|
setting_system_font_ui: システムのデフォルトフォントを使う
|
||||||
|
|
|
@ -49,10 +49,16 @@ RSpec.describe Api::V1::AccountsController do
|
||||||
|
|
||||||
describe 'POST #follow' do
|
describe 'POST #follow' do
|
||||||
let(:scopes) { 'write:follows' }
|
let(:scopes) { 'write:follows' }
|
||||||
|
let(:my_actor_type) { 'Person' }
|
||||||
|
let(:lock_follow_from_bot) { false }
|
||||||
let(:other_account) { Fabricate(:account, username: 'bob', locked: locked) }
|
let(:other_account) { Fabricate(:account, username: 'bob', locked: locked) }
|
||||||
|
|
||||||
context 'when posting to an other account' do
|
context 'when posting to an other account' do
|
||||||
before do
|
before do
|
||||||
|
other_account.user.settings['lock_follow_from_bot'] = lock_follow_from_bot
|
||||||
|
other_account.user.save!
|
||||||
|
user.account.update!(actor_type: my_actor_type)
|
||||||
|
|
||||||
post :follow, params: { id: other_account.id }
|
post :follow, params: { id: other_account.id }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -97,6 +103,29 @@ RSpec.describe Api::V1::AccountsController do
|
||||||
|
|
||||||
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
|
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with unlocked account from bot' do
|
||||||
|
let(:locked) { false }
|
||||||
|
let(:lock_follow_from_bot) { true }
|
||||||
|
let(:my_actor_type) { 'Service' }
|
||||||
|
|
||||||
|
it 'returns http success' do
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns JSON with following=false and requested=true' do
|
||||||
|
json = body_as_json
|
||||||
|
|
||||||
|
expect(json[:following]).to be false
|
||||||
|
expect(json[:requested]).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a follow request relation between user and target user' do
|
||||||
|
expect(user.account.requested?(other_account)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when modifying follow options' do
|
context 'when modifying follow options' do
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe ActivityPub::Activity::Follow do
|
RSpec.describe ActivityPub::Activity::Follow do
|
||||||
let(:sender) { Fabricate(:account, domain: 'example.com', inbox_url: 'https://example.com/inbox') }
|
let(:actor_type) { 'Person' }
|
||||||
|
let(:sender) { Fabricate(:account, domain: 'example.com', inbox_url: 'https://example.com/inbox', actor_type: actor_type) }
|
||||||
let(:recipient) { Fabricate(:account) }
|
let(:recipient) { Fabricate(:account) }
|
||||||
|
|
||||||
let(:json) do
|
let(:json) do
|
||||||
|
@ -83,6 +84,25 @@ RSpec.describe ActivityPub::Activity::Follow do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when unlocked account but locked from bot' do
|
||||||
|
let(:actor_type) { 'Service' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
recipient.user.settings['lock_follow_from_bot'] = true
|
||||||
|
recipient.user.save!
|
||||||
|
subject.perform
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not create a follow from sender to recipient' do
|
||||||
|
expect(sender.following?(recipient)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a follow request' do
|
||||||
|
expect(sender.requested?(recipient)).to be true
|
||||||
|
expect(sender.follow_requests.find_by(target_account: recipient).uri).to eq 'foo'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when domain block reject_straight_follow' do
|
context 'when domain block reject_straight_follow' do
|
||||||
before do
|
before do
|
||||||
Fabricate(:domain_block, domain: 'example.com', reject_straight_follow: true)
|
Fabricate(:domain_block, domain: 'example.com', reject_straight_follow: true)
|
||||||
|
|
|
@ -13,8 +13,14 @@ describe ActivityPub::NoteSerializer do
|
||||||
let!(:reply_by_other_first) { Fabricate(:status, account: other, thread: parent, visibility: :public) }
|
let!(:reply_by_other_first) { Fabricate(:status, account: other, thread: parent, visibility: :public) }
|
||||||
let!(:reply_by_account_third) { Fabricate(:status, account: account, thread: parent, visibility: :public) }
|
let!(:reply_by_account_third) { Fabricate(:status, account: account, thread: parent, visibility: :public) }
|
||||||
let!(:reply_by_account_visibility_direct) { Fabricate(:status, account: account, thread: parent, visibility: :direct) }
|
let!(:reply_by_account_visibility_direct) { Fabricate(:status, account: account, thread: parent, visibility: :direct) }
|
||||||
|
let!(:referred) { nil }
|
||||||
|
let!(:referred2) { nil }
|
||||||
|
let(:convert_to_quote) { false }
|
||||||
|
|
||||||
before(:each) do
|
before(:each) do
|
||||||
|
parent.references << referred if referred.present?
|
||||||
|
parent.references << referred2 if referred2.present?
|
||||||
|
account.user&.settings&.[]=('single_ref_to_quote', true) if convert_to_quote
|
||||||
@serialization = ActiveModelSerializers::SerializableResource.new(parent, serializer: described_class, adapter: ActivityPub::Adapter)
|
@serialization = ActiveModelSerializers::SerializableResource.new(parent, serializer: described_class, adapter: ActivityPub::Adapter)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -41,4 +47,46 @@ describe ActivityPub::NoteSerializer do
|
||||||
it 'does not include replies with direct visibility in its replies collection' do
|
it 'does not include replies with direct visibility in its replies collection' do
|
||||||
expect(subject['replies']['first']['items']).to_not include(reply_by_account_visibility_direct.uri)
|
expect(subject['replies']['first']['items']).to_not include(reply_by_account_visibility_direct.uri)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when has quote but no_convert setting' do
|
||||||
|
let(:referred) { Fabricate(:status) }
|
||||||
|
|
||||||
|
it 'has a references collection' do
|
||||||
|
expect(subject['references']['type']).to eql('Collection')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a references collection with a first Page' do
|
||||||
|
expect(subject['references']['first']['type']).to eql('CollectionPage')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has as reference' do
|
||||||
|
expect(subject['quoteUri']).to be_nil
|
||||||
|
expect(subject['references']['first']['items']).to include referred.uri
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when has quote and convert setting' do
|
||||||
|
let(:referred) { Fabricate(:status) }
|
||||||
|
let(:convert_to_quote) { true }
|
||||||
|
|
||||||
|
it 'has as quote' do
|
||||||
|
expect(subject['quoteUri']).to_not be_nil
|
||||||
|
expect(subject['quoteUri']).to eq referred.uri
|
||||||
|
expect(subject['_misskey_quote']).to eq referred.uri
|
||||||
|
expect(subject['_misskey_content']).to eq referred.text
|
||||||
|
expect(subject['references']['first']['items']).to include referred.uri
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when has multiple references and convert setting' do
|
||||||
|
let(:referred) { Fabricate(:status) }
|
||||||
|
let(:referred2) { Fabricate(:status) }
|
||||||
|
let(:convert_to_quote) { true }
|
||||||
|
|
||||||
|
it 'has as quote' do
|
||||||
|
expect(subject['quoteUri']).to be_nil
|
||||||
|
expect(subject['references']['first']['items']).to include referred.uri
|
||||||
|
expect(subject['references']['first']['items']).to include referred2.uri
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
197
spec/services/process_references_service_spec.rb
Normal file
197
spec/services/process_references_service_spec.rb
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe ProcessReferencesService, type: :service do
|
||||||
|
let(:text) { 'Hello' }
|
||||||
|
let(:account) { Fabricate(:user).account }
|
||||||
|
let(:visibility) { :public }
|
||||||
|
let(:status) { Fabricate(:status, account: account, text: text, visibility: visibility) }
|
||||||
|
let(:target_status) { Fabricate(:status, account: Fabricate(:user).account) }
|
||||||
|
let(:target_status_uri) { ActivityPub::TagManager.instance.uri_for(target_status) }
|
||||||
|
|
||||||
|
describe 'posting new status' do
|
||||||
|
subject do
|
||||||
|
described_class.new.call(status, reference_parameters, urls: urls, fetch_remote: fetch_remote)
|
||||||
|
status.references.pluck(:id)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:reference_parameters) { [] }
|
||||||
|
let(:urls) { [] }
|
||||||
|
let(:fetch_remote) { true }
|
||||||
|
|
||||||
|
context 'when a simple case' do
|
||||||
|
let(:text) { "Hello RT #{target_status_uri}" }
|
||||||
|
|
||||||
|
it 'post status' do
|
||||||
|
ids = subject
|
||||||
|
expect(ids.size).to eq 1
|
||||||
|
expect(ids).to include target_status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when multiple references' do
|
||||||
|
let(:target_status2) { Fabricate(:status) }
|
||||||
|
let(:target_status2_uri) { ActivityPub::TagManager.instance.uri_for(target_status2) }
|
||||||
|
let(:text) { "Hello RT #{target_status_uri}\nBT #{target_status2_uri}" }
|
||||||
|
|
||||||
|
it 'post status' do
|
||||||
|
ids = subject
|
||||||
|
expect(ids.size).to eq 2
|
||||||
|
expect(ids).to include target_status.id
|
||||||
|
expect(ids).to include target_status2.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when url only' do
|
||||||
|
let(:text) { "Hello #{target_status_uri}" }
|
||||||
|
|
||||||
|
it 'post status' do
|
||||||
|
ids = subject
|
||||||
|
expect(ids.size).to eq 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when unfetched remote post' do
|
||||||
|
let(:account) { Fabricate(:account, followers_url: 'http://example.com/followers', domain: 'example.com', uri: 'https://example.com/actor') }
|
||||||
|
let(:object_json) do
|
||||||
|
{
|
||||||
|
id: 'https://example.com/test_post',
|
||||||
|
to: ActivityPub::TagManager::COLLECTIONS[:public],
|
||||||
|
'@context': ActivityPub::TagManager::CONTEXT,
|
||||||
|
type: 'Note',
|
||||||
|
actor: account.uri,
|
||||||
|
attributedTo: account.uri,
|
||||||
|
content: 'Lorem ipsum',
|
||||||
|
published: '2022-01-22T15:00:00Z',
|
||||||
|
updated: '2022-01-22T16:00:00Z',
|
||||||
|
}
|
||||||
|
end
|
||||||
|
let(:text) { 'BT https://example.com/test_post' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:get, 'https://example.com/test_post').to_return(status: 200, body: Oj.dump(object_json), headers: { 'Content-Type' => 'application/activity+json' })
|
||||||
|
stub_request(:get, 'https://example.com/not_found').to_return(status: 404)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'reference it' do
|
||||||
|
ids = subject
|
||||||
|
expect(ids.size).to eq 1
|
||||||
|
|
||||||
|
status = Status.find_by(id: ids[0])
|
||||||
|
expect(status).to_not be_nil
|
||||||
|
expect(status.url).to eq 'https://example.com/test_post'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with fetch_remote later' do
|
||||||
|
let(:fetch_remote) { false }
|
||||||
|
|
||||||
|
it 'reference it' do
|
||||||
|
ids = subject
|
||||||
|
expect(ids.size).to eq 1
|
||||||
|
|
||||||
|
status = Status.find_by(id: ids[0])
|
||||||
|
expect(status).to_not be_nil
|
||||||
|
expect(status.url).to eq 'https://example.com/test_post'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with fetch_remote later with has existing reference' do
|
||||||
|
let(:fetch_remote) { false }
|
||||||
|
let(:text) { "RT #{ActivityPub::TagManager.instance.uri_for(target_status)} BT https://example.com/test_post" }
|
||||||
|
|
||||||
|
it 'reference it' do
|
||||||
|
ids = subject
|
||||||
|
expect(ids.size).to eq 2
|
||||||
|
expect(ids).to include target_status.id
|
||||||
|
|
||||||
|
status = Status.find_by(id: ids, uri: 'https://example.com/test_post')
|
||||||
|
expect(status).to_not be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with not exists reference' do
|
||||||
|
let(:text) { 'BT https://example.com/not_found' }
|
||||||
|
|
||||||
|
it 'reference it' do
|
||||||
|
ids = subject
|
||||||
|
expect(ids.size).to eq 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'editing new status' do
|
||||||
|
subject do
|
||||||
|
status.update!(text: new_text)
|
||||||
|
described_class.new.call(status, reference_parameters, urls: urls, fetch_remote: fetch_remote)
|
||||||
|
status.references.pluck(:id)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:target_status2) { Fabricate(:status, account: Fabricate(:user).account) }
|
||||||
|
let(:target_status2_uri) { ActivityPub::TagManager.instance.uri_for(target_status2) }
|
||||||
|
|
||||||
|
let(:new_text) { 'Hello' }
|
||||||
|
let(:reference_parameters) { [] }
|
||||||
|
let(:urls) { [] }
|
||||||
|
let(:fetch_remote) { true }
|
||||||
|
|
||||||
|
before do
|
||||||
|
described_class.new.call(status, reference_parameters, urls: urls, fetch_remote: fetch_remote)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when add reference to empty' do
|
||||||
|
let(:new_text) { "BT #{target_status_uri}" }
|
||||||
|
|
||||||
|
it 'post status' do
|
||||||
|
ids = subject
|
||||||
|
expect(ids.size).to eq 1
|
||||||
|
expect(ids).to include target_status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when add reference to have anyone' do
|
||||||
|
let(:text) { "BT #{target_status_uri}" }
|
||||||
|
let(:new_text) { "BT #{target_status_uri}\nBT #{target_status2_uri}" }
|
||||||
|
|
||||||
|
it 'post status' do
|
||||||
|
ids = subject
|
||||||
|
expect(ids.size).to eq 2
|
||||||
|
expect(ids).to include target_status.id
|
||||||
|
expect(ids).to include target_status2.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when add reference but has same' do
|
||||||
|
let(:text) { "BT #{target_status_uri}" }
|
||||||
|
let(:new_text) { "BT #{target_status_uri}\nBT #{target_status_uri}" }
|
||||||
|
|
||||||
|
it 'post status' do
|
||||||
|
ids = subject
|
||||||
|
expect(ids.size).to eq 1
|
||||||
|
expect(ids).to include target_status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when remove reference' do
|
||||||
|
let(:text) { "BT #{target_status_uri}" }
|
||||||
|
let(:new_text) { 'Hello' }
|
||||||
|
|
||||||
|
it 'post status' do
|
||||||
|
ids = subject
|
||||||
|
expect(ids.size).to eq 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when change reference' do
|
||||||
|
let(:text) { "BT #{target_status_uri}" }
|
||||||
|
let(:new_text) { "BT #{target_status2_uri}" }
|
||||||
|
|
||||||
|
it 'post status' do
|
||||||
|
ids = subject
|
||||||
|
expect(ids.size).to eq 1
|
||||||
|
expect(ids).to include target_status2.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Add table
Add a link
Reference in a new issue